1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/mozapps/update/nsUpdateService.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,5009 @@ 1.4 +#filter substitution 1.5 + 1.6 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.7 +/* 1.8 +# This Source Code Form is subject to the terms of the Mozilla Public 1.9 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.10 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.11 +*/ 1.12 + 1.13 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); 1.14 +Components.utils.import("resource://gre/modules/FileUtils.jsm"); 1.15 +Components.utils.import("resource://gre/modules/AddonManager.jsm"); 1.16 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.17 +#ifdef BUILD_CTYPES 1.18 +#ifdef XP_WIN 1.19 +Components.utils.import("resource://gre/modules/ctypes.jsm"); 1.20 +#endif 1.21 +#endif 1.22 + 1.23 +const Cc = Components.classes; 1.24 +const Ci = Components.interfaces; 1.25 +const Cr = Components.results; 1.26 +const Cu = Components.utils; 1.27 + 1.28 +const UPDATESERVICE_CID = Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}"); 1.29 +const UPDATESERVICE_CONTRACTID = "@mozilla.org/updates/update-service;1"; 1.30 + 1.31 +const PREF_APP_UPDATE_ALTWINDOWTYPE = "app.update.altwindowtype"; 1.32 +const PREF_APP_UPDATE_AUTO = "app.update.auto"; 1.33 +const PREF_APP_UPDATE_BACKGROUND_INTERVAL = "app.update.download.backgroundInterval"; 1.34 +const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors"; 1.35 +const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors"; 1.36 +const PREF_APP_UPDATE_CERTS_BRANCH = "app.update.certs."; 1.37 +const PREF_APP_UPDATE_CERT_CHECKATTRS = "app.update.cert.checkAttributes"; 1.38 +const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors"; 1.39 +const PREF_APP_UPDATE_CERT_MAXERRORS = "app.update.cert.maxErrors"; 1.40 +const PREF_APP_UPDATE_CERT_REQUIREBUILTIN = "app.update.cert.requireBuiltIn"; 1.41 +const PREF_APP_UPDATE_CUSTOM = "app.update.custom"; 1.42 +const PREF_APP_UPDATE_ENABLED = "app.update.enabled"; 1.43 +const PREF_APP_UPDATE_METRO_ENABLED = "app.update.metro.enabled"; 1.44 +const PREF_APP_UPDATE_IDLETIME = "app.update.idletime"; 1.45 +const PREF_APP_UPDATE_INCOMPATIBLE_MODE = "app.update.incompatible.mode"; 1.46 +const PREF_APP_UPDATE_INTERVAL = "app.update.interval"; 1.47 +const PREF_APP_UPDATE_LASTUPDATETIME = "app.update.lastUpdateTime.background-update-timer"; 1.48 +const PREF_APP_UPDATE_LOG = "app.update.log"; 1.49 +const PREF_APP_UPDATE_MODE = "app.update.mode"; 1.50 +const PREF_APP_UPDATE_NEVER_BRANCH = "app.update.never."; 1.51 +const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported"; 1.52 +const PREF_APP_UPDATE_POSTUPDATE = "app.update.postupdate"; 1.53 +const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime"; 1.54 +const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI"; 1.55 +const PREF_APP_UPDATE_SILENT = "app.update.silent"; 1.56 +const PREF_APP_UPDATE_STAGING_ENABLED = "app.update.staging.enabled"; 1.57 +const PREF_APP_UPDATE_URL = "app.update.url"; 1.58 +const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details"; 1.59 +const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override"; 1.60 +const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled"; 1.61 +const PREF_APP_UPDATE_SERVICE_ERRORS = "app.update.service.errors"; 1.62 +const PREF_APP_UPDATE_SERVICE_MAX_ERRORS = "app.update.service.maxErrors"; 1.63 +const PREF_APP_UPDATE_SOCKET_ERRORS = "app.update.socket.maxErrors"; 1.64 +const PREF_APP_UPDATE_RETRY_TIMEOUT = "app.update.socket.retryTimeout"; 1.65 + 1.66 +const PREF_APP_DISTRIBUTION = "distribution.id"; 1.67 +const PREF_APP_DISTRIBUTION_VERSION = "distribution.version"; 1.68 + 1.69 +const PREF_APP_B2G_VERSION = "b2g.version"; 1.70 + 1.71 +const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; 1.72 + 1.73 +const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul"; 1.74 +const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul"; 1.75 +const URI_BRAND_PROPERTIES = "chrome://branding/locale/brand.properties"; 1.76 +const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties"; 1.77 +const URI_UPDATE_NS = "http://www.mozilla.org/2005/app-update"; 1.78 + 1.79 +const CATEGORY_UPDATE_TIMER = "update-timer"; 1.80 + 1.81 +const KEY_GRED = "GreD"; 1.82 +const KEY_UPDROOT = "UpdRootD"; 1.83 +const KEY_EXECUTABLE = "XREExeF"; 1.84 + 1.85 +#ifdef TOR_BROWSER_VERSION 1.86 +#expand const TOR_BROWSER_VERSION = __TOR_BROWSER_VERSION__; 1.87 +#endif 1.88 + 1.89 +#ifdef MOZ_WIDGET_GONK 1.90 +#define USE_UPDATE_ARCHIVE_DIR 1.91 +#endif 1.92 + 1.93 +#ifdef USE_UPDATE_ARCHIVE_DIR 1.94 +const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD" 1.95 +#endif 1.96 + 1.97 +#ifdef XP_WIN 1.98 +#define CHECK_CAN_USE_SERVICE 1.99 +#elifdef MOZ_WIDGET_GONK 1.100 +// In Gonk, the updater will remount the /system partition to move staged files 1.101 +// into place, so we skip the test here to keep things isolated. 1.102 +#define CHECK_CAN_USE_SERVICE 1.103 +#endif 1.104 + 1.105 +const DIR_UPDATES = "updates"; 1.106 +#ifdef XP_MACOSX 1.107 +const UPDATED_DIR = "Updated.app"; 1.108 +#else 1.109 +const UPDATED_DIR = "updated"; 1.110 +#endif 1.111 +const FILE_UPDATE_STATUS = "update.status"; 1.112 +const FILE_UPDATE_VERSION = "update.version"; 1.113 +#ifdef MOZ_WIDGET_ANDROID 1.114 +const FILE_UPDATE_ARCHIVE = "update.apk"; 1.115 +#else 1.116 +const FILE_UPDATE_ARCHIVE = "update.mar"; 1.117 +#endif 1.118 +const FILE_UPDATE_LINK = "update.link"; 1.119 +const FILE_UPDATE_LOG = "update.log"; 1.120 +const FILE_UPDATES_DB = "updates.xml"; 1.121 +const FILE_UPDATE_ACTIVE = "active-update.xml"; 1.122 +const FILE_PERMS_TEST = "update.test"; 1.123 +const FILE_LAST_LOG = "last-update.log"; 1.124 +const FILE_BACKUP_LOG = "backup-update.log"; 1.125 +const FILE_UPDATE_LOCALE = "update.locale"; 1.126 + 1.127 +const STATE_NONE = "null"; 1.128 +const STATE_DOWNLOADING = "downloading"; 1.129 +const STATE_PENDING = "pending"; 1.130 +const STATE_PENDING_SVC = "pending-service"; 1.131 +const STATE_APPLYING = "applying"; 1.132 +const STATE_APPLIED = "applied"; 1.133 +const STATE_APPLIED_OS = "applied-os"; 1.134 +const STATE_APPLIED_SVC = "applied-service"; 1.135 +const STATE_SUCCEEDED = "succeeded"; 1.136 +const STATE_DOWNLOAD_FAILED = "download-failed"; 1.137 +const STATE_FAILED = "failed"; 1.138 + 1.139 +// From updater/errors.h: 1.140 +const WRITE_ERROR = 7; 1.141 +// const UNEXPECTED_ERROR = 8; // Replaced with errors 38-42 1.142 +const ELEVATION_CANCELED = 9; 1.143 + 1.144 +// Windows service specific errors 1.145 +const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 24; 1.146 +const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 25; 1.147 +const SERVICE_UPDATER_SIGN_ERROR = 26; 1.148 +const SERVICE_UPDATER_COMPARE_ERROR = 27; 1.149 +const SERVICE_UPDATER_IDENTITY_ERROR = 28; 1.150 +const SERVICE_STILL_APPLYING_ON_SUCCESS = 29; 1.151 +const SERVICE_STILL_APPLYING_ON_FAILURE = 30; 1.152 +const SERVICE_UPDATER_NOT_FIXED_DRIVE = 31; 1.153 +const SERVICE_COULD_NOT_LOCK_UPDATER = 32; 1.154 +const SERVICE_INSTALLDIR_ERROR = 33; 1.155 +const SERVICE_COULD_NOT_COPY_UPDATER = 49; 1.156 + 1.157 +const WRITE_ERROR_ACCESS_DENIED = 35; 1.158 +// const WRITE_ERROR_SHARING_VIOLATION = 36; // Replaced with errors 46-48 1.159 +const WRITE_ERROR_CALLBACK_APP = 37; 1.160 +const INVALID_UPDATER_STATUS_CODE = 38; 1.161 +const UNEXPECTED_BZIP_ERROR = 39; 1.162 +const UNEXPECTED_MAR_ERROR = 40; 1.163 +const UNEXPECTED_BSPATCH_ERROR = 41; 1.164 +const UNEXPECTED_FILE_OPERATION_ERROR = 42; 1.165 +const FILESYSTEM_MOUNT_READWRITE_ERROR = 43; 1.166 +const FOTA_GENERAL_ERROR = 44; 1.167 +const FOTA_UNKNOWN_ERROR = 45; 1.168 +const WRITE_ERROR_SHARING_VIOLATION_SIGNALED = 46; 1.169 +const WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID = 47; 1.170 +const WRITE_ERROR_SHARING_VIOLATION_NOPID = 48; 1.171 +const FOTA_FILE_OPERATION_ERROR = 49; 1.172 +const FOTA_RECOVERY_ERROR = 50; 1.173 + 1.174 +const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100; 1.175 +const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101; 1.176 +const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110; 1.177 +const NETWORK_ERROR_OFFLINE = 111; 1.178 +const FILE_ERROR_TOO_BIG = 112; 1.179 + 1.180 +// Error codes should be < 1000. Errors above 1000 represent http status codes 1.181 +const HTTP_ERROR_OFFSET = 1000; 1.182 + 1.183 +const DOWNLOAD_CHUNK_SIZE = 300000; // bytes 1.184 +const DOWNLOAD_BACKGROUND_INTERVAL = 600; // seconds 1.185 +const DOWNLOAD_FOREGROUND_INTERVAL = 0; 1.186 + 1.187 +const UPDATE_WINDOW_NAME = "Update:Wizard"; 1.188 + 1.189 +// The number of consecutive failures when updating using the service before 1.190 +// setting the app.update.service.enabled preference to false. 1.191 +const DEFAULT_SERVICE_MAX_ERRORS = 10; 1.192 + 1.193 +// The number of consecutive socket errors to allow before falling back to 1.194 +// downloading a different MAR file or failing if already downloading the full. 1.195 +const DEFAULT_SOCKET_MAX_ERRORS = 10; 1.196 + 1.197 +// The number of milliseconds to wait before retrying a connection error. 1.198 +const DEFAULT_UPDATE_RETRY_TIMEOUT = 2000; 1.199 + 1.200 +// A background download is in progress (no notification) 1.201 +const PING_BGUC_IS_DOWNLOADING = 0; 1.202 +// An update is staged (no notification) 1.203 +const PING_BGUC_IS_STAGED = 1; 1.204 +// Invalid url for app.update.url default preference (no notification) 1.205 +const PING_BGUC_INVALID_DEFAULT_URL = 2; 1.206 +// Invalid url for app.update.url user preference (no notification) 1.207 +const PING_BGUC_INVALID_CUSTOM_URL = 3; 1.208 +// Invalid url for app.update.url.override user preference (no notification) 1.209 +const PING_BGUC_INVALID_OVERRIDE_URL = 4; 1.210 +// Unable to check for updates per gCanCheckForUpdates and hasUpdateMutex() 1.211 +// (no notification) 1.212 +const PING_BGUC_UNABLE_TO_CHECK = 5; 1.213 +// Already has an active update in progress (no notification) 1.214 +const PING_BGUC_HAS_ACTIVEUPDATE = 6; 1.215 +// Background checks disabled by preference (no notification) 1.216 +const PING_BGUC_PREF_DISABLED = 7; 1.217 +// Background checks disabled for the current session (no notification) 1.218 +const PING_BGUC_DISABLED_FOR_SESSION = 8; 1.219 +// Background checks disabled in Metro (no notification) 1.220 +const PING_BGUC_METRO_DISABLED = 9; 1.221 +// Unable to perform a background check while offline (no notification) 1.222 +const PING_BGUC_OFFLINE = 10; 1.223 +// No update found certificate check failed and threshold reached 1.224 +// (possible mitm attack notification) 1.225 +const PING_BGUC_CERT_ATTR_NO_UPDATE_NOTIFY = 11; 1.226 +// No update found certificate check failed and threshold not reached 1.227 +// (no notification) 1.228 +const PING_BGUC_CERT_ATTR_NO_UPDATE_SILENT = 12; 1.229 +// Update found certificate check failed and threshold reached 1.230 +// (possible mitm attack notification) 1.231 +const PING_BGUC_CERT_ATTR_WITH_UPDATE_NOTIFY = 13; 1.232 +// Update found certificate check failed and threshold not reached 1.233 +// (no notification) 1.234 +const PING_BGUC_CERT_ATTR_WITH_UPDATE_SILENT = 14; 1.235 +// General update check failure and threshold reached 1.236 +// (check failure notification) 1.237 +const PING_BGUC_GENERAL_ERROR_NOTIFY = 15; 1.238 +// General update check failure and threshold not reached 1.239 +// (no notification) 1.240 +const PING_BGUC_GENERAL_ERROR_SILENT = 16; 1.241 +// No update found (no notification) 1.242 +const PING_BGUC_NO_UPDATE_FOUND = 17; 1.243 +// No compatible update found though there were updates (no notification) 1.244 +const PING_BGUC_NO_COMPAT_UPDATE_FOUND = 18; 1.245 +// Update found for a previous version (no notification) 1.246 +const PING_BGUC_UPDATE_PREVIOUS_VERSION = 19; 1.247 +// Update found for a version with the never preference set (no notification) 1.248 +const PING_BGUC_UPDATE_NEVER_PREF = 20; 1.249 +// Update found without a type attribute (no notification) 1.250 +const PING_BGUC_UPDATE_INVALID_TYPE = 21; 1.251 +// The system is no longer supported (system unsupported notification) 1.252 +const PING_BGUC_UNSUPPORTED = 22; 1.253 +// Unable to apply updates (manual install to update notification) 1.254 +const PING_BGUC_UNABLE_TO_APPLY = 23; 1.255 +// Showing prompt due to the update.xml specifying showPrompt 1.256 +// (update notification) 1.257 +const PING_BGUC_SHOWPROMPT_SNIPPET = 24; 1.258 +// Showing prompt due to preference (update notification) 1.259 +const PING_BGUC_SHOWPROMPT_PREF = 25; 1.260 +// Incompatible add-on check disabled by preference (background download) 1.261 +const PING_BGUC_ADDON_PREF_DISABLED = 26; 1.262 +// Incompatible add-on not checked not performed due to same update version and 1.263 +// app version (background download) 1.264 +const PING_BGUC_ADDON_SAME_APP_VER = 27; 1.265 +// No incompatible add-ons found during incompatible check (background download) 1.266 +const PING_BGUC_CHECK_NO_INCOMPAT = 28; 1.267 +// Incompatible add-ons found and all of them have updates (background download) 1.268 +const PING_BGUC_ADDON_UPDATES_FOR_INCOMPAT = 29; 1.269 +// Incompatible add-ons found (update notification) 1.270 +const PING_BGUC_ADDON_HAVE_INCOMPAT = 30; 1.271 + 1.272 +var gLocale = null; 1.273 +var gUpdateMutexHandle = null; 1.274 + 1.275 +#ifdef MOZ_WIDGET_GONK 1.276 +var gSDCardMountLock = null; 1.277 + 1.278 +XPCOMUtils.defineLazyGetter(this, "gExtStorage", function aus_gExtStorage() { 1.279 + return Services.env.get("EXTERNAL_STORAGE"); 1.280 +}); 1.281 +#endif 1.282 + 1.283 +XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel", 1.284 + "resource://gre/modules/UpdateChannel.jsm"); 1.285 + 1.286 +XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() { 1.287 + return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false); 1.288 +}); 1.289 + 1.290 +XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() { 1.291 + return Services.strings.createBundle(URI_UPDATES_PROPERTIES); 1.292 +}); 1.293 + 1.294 +// shared code for suppressing bad cert dialogs 1.295 +XPCOMUtils.defineLazyGetter(this, "gCertUtils", function aus_gCertUtils() { 1.296 + let temp = { }; 1.297 + Cu.import("resource://gre/modules/CertUtils.jsm", temp); 1.298 + return temp; 1.299 +}); 1.300 + 1.301 +XPCOMUtils.defineLazyGetter(this, "gABI", function aus_gABI() { 1.302 + let abi = null; 1.303 + try { 1.304 + abi = Services.appinfo.XPCOMABI; 1.305 + } 1.306 + catch (e) { 1.307 + LOG("gABI - XPCOM ABI unknown: updates are not possible."); 1.308 + } 1.309 +#ifdef XP_MACOSX 1.310 + // Mac universal build should report a different ABI than either macppc 1.311 + // or mactel. 1.312 + let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"]. 1.313 + getService(Ci.nsIMacUtils); 1.314 + 1.315 + if (macutils.isUniversalBinary) 1.316 + abi += "-u-" + macutils.architecturesInBinary; 1.317 +#ifdef MOZ_SHARK 1.318 + // Disambiguate optimised and shark nightlies 1.319 + abi += "-shark" 1.320 +#endif 1.321 +#endif 1.322 + return abi; 1.323 +}); 1.324 + 1.325 +#ifdef MOZ_WIDGET_GONK 1.326 +XPCOMUtils.defineLazyGetter(this, "gProductModel", function aus_gProductModel() { 1.327 + Cu.import("resource://gre/modules/systemlibs.js"); 1.328 + return libcutils.property_get("ro.product.model"); 1.329 +}); 1.330 +#endif 1.331 + 1.332 +XPCOMUtils.defineLazyGetter(this, "gOSVersion", function aus_gOSVersion() { 1.333 + let osVersion; 1.334 + let sysInfo = Cc["@mozilla.org/system-info;1"]. 1.335 + getService(Ci.nsIPropertyBag2); 1.336 + try { 1.337 + osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version"); 1.338 + } 1.339 + catch (e) { 1.340 + LOG("gOSVersion - OS Version unknown: updates are not possible."); 1.341 + } 1.342 + 1.343 + if (osVersion) { 1.344 +#ifdef BUILD_CTYPES 1.345 +#ifdef XP_WIN 1.346 + const BYTE = ctypes.uint8_t; 1.347 + const WORD = ctypes.uint16_t; 1.348 + const DWORD = ctypes.uint32_t; 1.349 + const WCHAR = ctypes.jschar; 1.350 + const BOOL = ctypes.int; 1.351 + 1.352 + // This structure is described at: 1.353 + // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx 1.354 + const SZCSDVERSIONLENGTH = 128; 1.355 + const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW', 1.356 + [ 1.357 + {dwOSVersionInfoSize: DWORD}, 1.358 + {dwMajorVersion: DWORD}, 1.359 + {dwMinorVersion: DWORD}, 1.360 + {dwBuildNumber: DWORD}, 1.361 + {dwPlatformId: DWORD}, 1.362 + {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)}, 1.363 + {wServicePackMajor: WORD}, 1.364 + {wServicePackMinor: WORD}, 1.365 + {wSuiteMask: WORD}, 1.366 + {wProductType: BYTE}, 1.367 + {wReserved: BYTE} 1.368 + ]); 1.369 + 1.370 + // This structure is described at: 1.371 + // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx 1.372 + const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO', 1.373 + [ 1.374 + {wProcessorArchitecture: WORD}, 1.375 + {wReserved: WORD}, 1.376 + {dwPageSize: DWORD}, 1.377 + {lpMinimumApplicationAddress: ctypes.voidptr_t}, 1.378 + {lpMaximumApplicationAddress: ctypes.voidptr_t}, 1.379 + {dwActiveProcessorMask: DWORD.ptr}, 1.380 + {dwNumberOfProcessors: DWORD}, 1.381 + {dwProcessorType: DWORD}, 1.382 + {dwAllocationGranularity: DWORD}, 1.383 + {wProcessorLevel: WORD}, 1.384 + {wProcessorRevision: WORD} 1.385 + ]); 1.386 + 1.387 + let kernel32 = false; 1.388 + try { 1.389 + kernel32 = ctypes.open("Kernel32"); 1.390 + } catch (e) { 1.391 + LOG("gOSVersion - Unable to open kernel32! " + e); 1.392 + osVersion += ".unknown (unknown)"; 1.393 + } 1.394 + 1.395 + if(kernel32) { 1.396 + try { 1.397 + // Get Service pack info 1.398 + try { 1.399 + let GetVersionEx = kernel32.declare("GetVersionExW", 1.400 + ctypes.default_abi, 1.401 + BOOL, 1.402 + OSVERSIONINFOEXW.ptr); 1.403 + let winVer = OSVERSIONINFOEXW(); 1.404 + winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size; 1.405 + 1.406 + if(0 !== GetVersionEx(winVer.address())) { 1.407 + osVersion += "." + winVer.wServicePackMajor 1.408 + + "." + winVer.wServicePackMinor; 1.409 + } else { 1.410 + LOG("gOSVersion - Unknown failure in GetVersionEX (returned 0)"); 1.411 + osVersion += ".unknown"; 1.412 + } 1.413 + } catch (e) { 1.414 + LOG("gOSVersion - error getting service pack information. Exception: " + e); 1.415 + osVersion += ".unknown"; 1.416 + } 1.417 + 1.418 + // Get processor architecture 1.419 + let arch = "unknown"; 1.420 + try { 1.421 + let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo", 1.422 + ctypes.default_abi, 1.423 + ctypes.void_t, 1.424 + SYSTEM_INFO.ptr); 1.425 + let sysInfo = SYSTEM_INFO(); 1.426 + // Default to unknown 1.427 + sysInfo.wProcessorArchitecture = 0xffff; 1.428 + 1.429 + GetNativeSystemInfo(sysInfo.address()); 1.430 + switch(sysInfo.wProcessorArchitecture) { 1.431 + case 9: 1.432 + arch = "x64"; 1.433 + break; 1.434 + case 6: 1.435 + arch = "IA64"; 1.436 + break; 1.437 + case 0: 1.438 + arch = "x86"; 1.439 + break; 1.440 + } 1.441 + } catch (e) { 1.442 + LOG("gOSVersion - error getting processor architecture. Exception: " + e); 1.443 + } finally { 1.444 + osVersion += " (" + arch + ")"; 1.445 + } 1.446 + } finally { 1.447 + kernel32.close(); 1.448 + } 1.449 + } 1.450 +#endif 1.451 +#endif 1.452 + 1.453 + try { 1.454 + osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")"; 1.455 + } 1.456 + catch (e) { 1.457 + // Not all platforms have a secondary widget library, so an error is nothing to worry about. 1.458 + } 1.459 + osVersion = encodeURIComponent(osVersion); 1.460 + } 1.461 + return osVersion; 1.462 +}); 1.463 + 1.464 +/** 1.465 + * Tests to make sure that we can write to a given directory. 1.466 + * 1.467 + * @param updateTestFile a test file in the directory that needs to be tested. 1.468 + * @param createDirectory whether a test directory should be created. 1.469 + * @throws if we don't have right access to the directory. 1.470 + */ 1.471 +function testWriteAccess(updateTestFile, createDirectory) { 1.472 + const NORMAL_FILE_TYPE = Ci.nsILocalFile.NORMAL_FILE_TYPE; 1.473 + const DIRECTORY_TYPE = Ci.nsILocalFile.DIRECTORY_TYPE; 1.474 + if (updateTestFile.exists()) 1.475 + updateTestFile.remove(false); 1.476 + updateTestFile.create(createDirectory ? DIRECTORY_TYPE : NORMAL_FILE_TYPE, 1.477 + createDirectory ? FileUtils.PERMS_DIRECTORY : FileUtils.PERMS_FILE); 1.478 + updateTestFile.remove(false); 1.479 +} 1.480 + 1.481 +#ifdef XP_WIN 1.482 + 1.483 +/** 1.484 + * Closes a Win32 handle 1.485 + * 1.486 + * @param handle The handle to close 1.487 + */ 1.488 +function closeHandle(handle) { 1.489 + var lib = ctypes.open("kernel32.dll"); 1.490 + var CloseHandle = lib.declare("CloseHandle", 1.491 + ctypes.winapi_abi, 1.492 + ctypes.int32_t, /* success */ 1.493 + ctypes.void_t.ptr); /* handle */ 1.494 + CloseHandle(handle); 1.495 + lib.close(); 1.496 +} 1.497 + 1.498 +/** 1.499 + * Creates a mutex. 1.500 + * 1.501 + * @param aName 1.502 + * The name for the mutex. 1.503 + * @param aAllowExisting 1.504 + * If false the function will close the handle and return null. 1.505 + * @return The Win32 handle to the mutex. 1.506 + */ 1.507 +function createMutex(aName, aAllowExisting) { 1.508 + if (aAllowExisting === undefined) { 1.509 + aAllowExisting = true; 1.510 + } 1.511 + 1.512 + const INITIAL_OWN = 1; 1.513 + const ERROR_ALREADY_EXISTS = 0xB7; 1.514 + var lib = ctypes.open("kernel32.dll"); 1.515 + var CreateMutexW = lib.declare("CreateMutexW", 1.516 + ctypes.winapi_abi, 1.517 + ctypes.void_t.ptr, /* return handle */ 1.518 + ctypes.void_t.ptr, /* security attributes */ 1.519 + ctypes.int32_t, /* initial owner */ 1.520 + ctypes.jschar.ptr); /* name */ 1.521 + 1.522 + var handle = CreateMutexW(null, INITIAL_OWN, aName); 1.523 + var alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS; 1.524 + if (handle && !handle.isNull() && !aAllowExisting && alreadyExists) { 1.525 + closeHandle(handle); 1.526 + handle = null; 1.527 + } 1.528 + lib.close(); 1.529 + 1.530 + if (handle && handle.isNull()) 1.531 + handle = null; 1.532 + 1.533 + return handle; 1.534 +} 1.535 + 1.536 +/** 1.537 + * Determines a unique mutex name for the installation 1.538 + * 1.539 + * @param aGlobal true if the function should return a global mutex. A global 1.540 + * mutex is valid across different sessions 1.541 + * @return Global mutex path 1.542 + */ 1.543 +function getPerInstallationMutexName(aGlobal) { 1.544 + if (aGlobal === undefined) { 1.545 + aGobal = true; 1.546 + } 1.547 + let hasher = Cc["@mozilla.org/security/hash;1"]. 1.548 + createInstance(Ci.nsICryptoHash); 1.549 + hasher.init(hasher.SHA1); 1.550 + 1.551 + var exeFile = Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsILocalFile); 1.552 + 1.553 + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. 1.554 + createInstance(Ci.nsIScriptableUnicodeConverter); 1.555 + converter.charset = "UTF-8"; 1.556 + var data = converter.convertToByteArray(exeFile.path.toLowerCase()); 1.557 + 1.558 + hasher.update(data, data.length); 1.559 + return (aGlobal ? "Global\\" : "") + "MozillaUpdateMutex-" + hasher.finish(true); 1.560 +} 1.561 +#endif // XP_WIN 1.562 + 1.563 +/** 1.564 + * Whether or not the current instance has the update mutex. The update mutex 1.565 + * gives protection against 2 applications from the same installation updating: 1.566 + * 1) Running multiple profiles from the same installation path 1.567 + * 2) Running a Metro and Desktop application at the same time from the same 1.568 + * path 1.569 + * 3) 2 applications running in 2 different user sessions from the same path 1.570 + * 1.571 + * @return true if this instance holds the update mutex 1.572 + */ 1.573 +function hasUpdateMutex() { 1.574 +#ifdef XP_WIN 1.575 + if (!gUpdateMutexHandle) { 1.576 + gUpdateMutexHandle = createMutex(getPerInstallationMutexName(true), false); 1.577 + } 1.578 + 1.579 + return !!gUpdateMutexHandle; 1.580 +#else 1.581 + return true; 1.582 +#endif // XP_WIN 1.583 +} 1.584 + 1.585 +XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() { 1.586 + function submitHasPermissionsTelemetryPing(val) { 1.587 + try { 1.588 + let h = Services.telemetry.getHistogramById("UPDATER_HAS_PERMISSIONS"); 1.589 + h.add(+val); 1.590 + } catch(e) { 1.591 + // Don't allow any exception to be propagated. 1.592 + Components.utils.reportError(e); 1.593 + } 1.594 + } 1.595 + 1.596 + let useService = false; 1.597 + if (shouldUseService() && isServiceInstalled()) { 1.598 + // No need to perform directory write checks, the maintenance service will 1.599 + // be able to write to all directories. 1.600 + LOG("gCanApplyUpdates - bypass the write checks because we'll use the service"); 1.601 + useService = true; 1.602 + } 1.603 + 1.604 + if (!useService) { 1.605 + try { 1.606 + var updateTestFile = getUpdateFile([FILE_PERMS_TEST]); 1.607 + LOG("gCanApplyUpdates - testing write access " + updateTestFile.path); 1.608 + testWriteAccess(updateTestFile, false); 1.609 +#ifdef XP_WIN 1.610 + var sysInfo = Cc["@mozilla.org/system-info;1"]. 1.611 + getService(Ci.nsIPropertyBag2); 1.612 + 1.613 + // Example windowsVersion: Windows XP == 5.1 1.614 + var windowsVersion = sysInfo.getProperty("version"); 1.615 + LOG("gCanApplyUpdates - windowsVersion = " + windowsVersion); 1.616 + 1.617 + /** 1.618 + # For Vista, updates can be performed to a location requiring admin 1.619 + # privileges by requesting elevation via the UAC prompt when launching 1.620 + # updater.exe if the appDir is under the Program Files directory 1.621 + # (e.g. C:\Program Files\) and UAC is turned on and we can elevate 1.622 + # (e.g. user has a split token). 1.623 + # 1.624 + # Note: this does note attempt to handle the case where UAC is turned on 1.625 + # and the installation directory is in a restricted location that 1.626 + # requires admin privileges to update other than Program Files. 1.627 + */ 1.628 + var userCanElevate = false; 1.629 + 1.630 + if (parseFloat(windowsVersion) >= 6) { 1.631 + try { 1.632 + var fileLocator = Cc["@mozilla.org/file/directory_service;1"]. 1.633 + getService(Ci.nsIProperties); 1.634 + // KEY_UPDROOT will fail and throw an exception if 1.635 + // appDir is not under the Program Files, so we rely on that 1.636 + var dir = fileLocator.get(KEY_UPDROOT, Ci.nsIFile); 1.637 + // appDir is under Program Files, so check if the user can elevate 1.638 + userCanElevate = Services.appinfo.QueryInterface(Ci.nsIWinAppHelper). 1.639 + userCanElevate; 1.640 + LOG("gCanApplyUpdates - on Vista, userCanElevate: " + userCanElevate); 1.641 + } 1.642 + catch (ex) { 1.643 + // When the installation directory is not under Program Files, 1.644 + // fall through to checking if write access to the 1.645 + // installation directory is available. 1.646 + LOG("gCanApplyUpdates - on Vista, appDir is not under Program Files"); 1.647 + } 1.648 + } 1.649 + 1.650 + /** 1.651 +# On Windows, we no longer store the update under the app dir. 1.652 +# 1.653 +# If we are on Windows (including Vista, if we can't elevate) we need to 1.654 +# to check that we can create and remove files from the actual app 1.655 +# directory (like C:\Program Files\Mozilla Firefox). If we can't 1.656 +# (because this user is not an adminstrator, for example) canUpdate() 1.657 +# should return false. 1.658 +# 1.659 +# For Vista, we perform this check to enable updating the application 1.660 +# when the user has write access to the installation directory under the 1.661 +# following scenarios: 1.662 +# 1) the installation directory is not under Program Files 1.663 +# (e.g. C:\Program Files) 1.664 +# 2) UAC is turned off 1.665 +# 3) UAC is turned on and the user is not an admin 1.666 +# (e.g. the user does not have a split token) 1.667 +# 4) UAC is turned on and the user is already elevated, so they can't be 1.668 +# elevated again 1.669 + */ 1.670 + if (!userCanElevate) { 1.671 + // if we're unable to create the test file this will throw an exception. 1.672 + var appDirTestFile = getAppBaseDir(); 1.673 + appDirTestFile.append(FILE_PERMS_TEST); 1.674 + LOG("gCanApplyUpdates - testing write access " + appDirTestFile.path); 1.675 + if (appDirTestFile.exists()) 1.676 + appDirTestFile.remove(false) 1.677 + appDirTestFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); 1.678 + appDirTestFile.remove(false); 1.679 + } 1.680 +#endif //XP_WIN 1.681 + } 1.682 + catch (e) { 1.683 + LOG("gCanApplyUpdates - unable to apply updates. Exception: " + e); 1.684 + // No write privileges to install directory 1.685 + submitHasPermissionsTelemetryPing(false); 1.686 + return false; 1.687 + } 1.688 + } // if (!useService) 1.689 + 1.690 + LOG("gCanApplyUpdates - able to apply updates"); 1.691 + submitHasPermissionsTelemetryPing(true); 1.692 + return true; 1.693 +}); 1.694 + 1.695 +/** 1.696 + * Whether or not the application can stage an update. 1.697 + * 1.698 + * @return true if updates can be staged. 1.699 + */ 1.700 +function getCanStageUpdates() { 1.701 + // If background updates are disabled, then just bail out! 1.702 + if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGING_ENABLED, false)) { 1.703 + LOG("getCanStageUpdates - staging updates is disabled by preference " + 1.704 + PREF_APP_UPDATE_STAGING_ENABLED); 1.705 + return false; 1.706 + } 1.707 + 1.708 +#ifdef CHECK_CAN_USE_SERVICE 1.709 + if (getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false)) { 1.710 + // No need to perform directory write checks, the maintenance service will 1.711 + // be able to write to all directories. 1.712 + LOG("getCanStageUpdates - able to stage updates because we'll use the service"); 1.713 + return true; 1.714 + } 1.715 +#endif 1.716 + 1.717 + if (!hasUpdateMutex()) { 1.718 + LOG("getCanStageUpdates - unable to apply updates because another " + 1.719 + "instance of the application is already handling updates for this " + 1.720 + "installation."); 1.721 + return false; 1.722 + } 1.723 + 1.724 + /** 1.725 + * Whether or not the application can stage an update for the current session. 1.726 + * These checks are only performed once per session due to using a lazy getter. 1.727 + * 1.728 + * @return true if updates can be staged for this session. 1.729 + */ 1.730 + XPCOMUtils.defineLazyGetter(this, "canStageUpdatesSession", function canStageUpdatesSession() { 1.731 + try { 1.732 + var updateTestFile = getInstallDirRoot(); 1.733 + updateTestFile.append(FILE_PERMS_TEST); 1.734 + LOG("canStageUpdatesSession - testing write access " + 1.735 + updateTestFile.path); 1.736 + testWriteAccess(updateTestFile, true); 1.737 +#ifndef XP_MACOSX 1.738 + // On all platforms except Mac, we need to test the parent directory as 1.739 + // well, as we need to be able to move files in that directory during the 1.740 + // replacing step. 1.741 + updateTestFile = getInstallDirRoot().parent; 1.742 + updateTestFile.append(FILE_PERMS_TEST); 1.743 + LOG("canStageUpdatesSession - testing write access " + 1.744 + updateTestFile.path); 1.745 + updateTestFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE, 1.746 + FileUtils.PERMS_DIRECTORY); 1.747 + updateTestFile.remove(false); 1.748 +#endif 1.749 + } 1.750 + catch (e) { 1.751 + LOG("canStageUpdatesSession - unable to stage updates. Exception: " + 1.752 + e); 1.753 + // No write privileges 1.754 + return false; 1.755 + } 1.756 + 1.757 + LOG("canStageUpdatesSession - able to stage updates"); 1.758 + return true; 1.759 + }); 1.760 + 1.761 + return canStageUpdatesSession; 1.762 +} 1.763 + 1.764 +XPCOMUtils.defineLazyGetter(this, "gMetroUpdatesEnabled", function aus_gMetroUpdatesEnabled() { 1.765 +#ifdef XP_WIN 1.766 +#ifdef MOZ_METRO 1.767 + if (Services.metro && Services.metro.immersive) { 1.768 + let metroUpdate = getPref("getBoolPref", PREF_APP_UPDATE_METRO_ENABLED, true); 1.769 + if (!metroUpdate) { 1.770 + LOG("gMetroUpdatesEnabled - unable to automatically check for metro " + 1.771 + "updates, disabled by pref"); 1.772 + return false; 1.773 + } 1.774 + } 1.775 +#endif 1.776 +#endif 1.777 + 1.778 + return true; 1.779 +}); 1.780 + 1.781 +XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckForUpdates() { 1.782 + // If the administrator has disabled app update and locked the preference so 1.783 + // users can't check for updates. This preference check is ok in this lazy 1.784 + // getter since locked prefs don't change until the application is restarted. 1.785 + var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true); 1.786 + if (!enabled && Services.prefs.prefIsLocked(PREF_APP_UPDATE_ENABLED)) { 1.787 + LOG("gCanCheckForUpdates - unable to automatically check for updates, " + 1.788 + "the preference is disabled and admistratively locked."); 1.789 + return false; 1.790 + } 1.791 + 1.792 + if (!gMetroUpdatesEnabled) { 1.793 + return false; 1.794 + } 1.795 + 1.796 + // If we don't know the binary platform we're updating, we can't update. 1.797 + if (!gABI) { 1.798 + LOG("gCanCheckForUpdates - unable to check for updates, unknown ABI"); 1.799 + return false; 1.800 + } 1.801 + 1.802 + // If we don't know the OS version we're updating, we can't update. 1.803 + if (!gOSVersion) { 1.804 + LOG("gCanCheckForUpdates - unable to check for updates, unknown OS " + 1.805 + "version"); 1.806 + return false; 1.807 + } 1.808 + 1.809 + LOG("gCanCheckForUpdates - able to check for updates"); 1.810 + return true; 1.811 +}); 1.812 + 1.813 +/** 1.814 + * Logs a string to the error console. 1.815 + * @param string 1.816 + * The string to write to the error console. 1.817 + */ 1.818 +function LOG(string) { 1.819 + if (gLogEnabled) { 1.820 + dump("*** AUS:SVC " + string + "\n"); 1.821 + Services.console.logStringMessage("AUS:SVC " + string); 1.822 + } 1.823 +} 1.824 + 1.825 +/** 1.826 +# Gets a preference value, handling the case where there is no default. 1.827 +# @param func 1.828 +# The name of the preference function to call, on nsIPrefBranch 1.829 +# @param preference 1.830 +# The name of the preference 1.831 +# @param defaultValue 1.832 +# The default value to return in the event the preference has 1.833 +# no setting 1.834 +# @return The value of the preference, or undefined if there was no 1.835 +# user or default value. 1.836 + */ 1.837 +function getPref(func, preference, defaultValue) { 1.838 + try { 1.839 + return Services.prefs[func](preference); 1.840 + } 1.841 + catch (e) { 1.842 + } 1.843 + return defaultValue; 1.844 +} 1.845 + 1.846 +/** 1.847 + * Convert a string containing binary values to hex. 1.848 + */ 1.849 +function binaryToHex(input) { 1.850 + var result = ""; 1.851 + for (var i = 0; i < input.length; ++i) { 1.852 + var hex = input.charCodeAt(i).toString(16); 1.853 + if (hex.length == 1) 1.854 + hex = "0" + hex; 1.855 + result += hex; 1.856 + } 1.857 + return result; 1.858 +} 1.859 + 1.860 +/** 1.861 +# Gets the specified directory at the specified hierarchy under the 1.862 +# update root directory and creates it if it doesn't exist. 1.863 +# @param pathArray 1.864 +# An array of path components to locate beneath the directory 1.865 +# specified by |key| 1.866 +# @return nsIFile object for the location specified. 1.867 + */ 1.868 +function getUpdateDirCreate(pathArray) { 1.869 + return FileUtils.getDir(KEY_UPDROOT, pathArray, true); 1.870 +} 1.871 + 1.872 +/** 1.873 +# Gets the specified directory at the specified hierarchy under the 1.874 +# update root directory and without creating it if it doesn't exist. 1.875 +# @param pathArray 1.876 +# An array of path components to locate beneath the directory 1.877 +# specified by |key| 1.878 +# @return nsIFile object for the location specified. 1.879 + */ 1.880 +function getUpdateDirNoCreate(pathArray) { 1.881 + return FileUtils.getDir(KEY_UPDROOT, pathArray, false); 1.882 +} 1.883 + 1.884 +/** 1.885 + * Gets the application base directory. 1.886 + * 1.887 + * @return nsIFile object for the application base directory. 1.888 + */ 1.889 +function getAppBaseDir() { 1.890 + return Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsIFile).parent; 1.891 +} 1.892 + 1.893 +/** 1.894 + * Gets the root of the installation directory which is the application 1.895 + * bundle directory on Mac OS X and the location of the application binary 1.896 + * on all other platforms. 1.897 + * 1.898 + * @return nsIFile object for the directory 1.899 + */ 1.900 +function getInstallDirRoot() { 1.901 + var dir = getAppBaseDir(); 1.902 +#ifdef XP_MACOSX 1.903 + // On Mac, we store the Updated.app directory inside the bundle directory. 1.904 + dir = dir.parent.parent; 1.905 +#endif 1.906 + return dir; 1.907 +} 1.908 + 1.909 +/** 1.910 + * Gets the file at the specified hierarchy under the update root directory. 1.911 + * @param pathArray 1.912 + * An array of path components to locate beneath the directory 1.913 + * specified by |key|. The last item in this array must be the 1.914 + * leaf name of a file. 1.915 + * @return nsIFile object for the file specified. The file is NOT created 1.916 + * if it does not exist, however all required directories along 1.917 + * the way are. 1.918 + */ 1.919 +function getUpdateFile(pathArray) { 1.920 + var file = getUpdateDirCreate(pathArray.slice(0, -1)); 1.921 + file.append(pathArray[pathArray.length - 1]); 1.922 + return file; 1.923 +} 1.924 + 1.925 +/** 1.926 + * Returns human readable status text from the updates.properties bundle 1.927 + * based on an error code 1.928 + * @param code 1.929 + * The error code to look up human readable status text for 1.930 + * @param defaultCode 1.931 + * The default code to look up should human readable status text 1.932 + * not exist for |code| 1.933 + * @return A human readable status text string 1.934 + */ 1.935 +function getStatusTextFromCode(code, defaultCode) { 1.936 + var reason; 1.937 + try { 1.938 + reason = gUpdateBundle.GetStringFromName("check_error-" + code); 1.939 + LOG("getStatusTextFromCode - transfer error: " + reason + ", code: " + 1.940 + code); 1.941 + } 1.942 + catch (e) { 1.943 + // Use the default reason 1.944 + reason = gUpdateBundle.GetStringFromName("check_error-" + defaultCode); 1.945 + LOG("getStatusTextFromCode - transfer error: " + reason + 1.946 + ", default code: " + defaultCode); 1.947 + } 1.948 + return reason; 1.949 +} 1.950 + 1.951 +/** 1.952 + * Get the Active Updates directory 1.953 + * @return The active updates directory, as a nsIFile object 1.954 + */ 1.955 +function getUpdatesDir() { 1.956 + // Right now, we only support downloading one patch at a time, so we always 1.957 + // use the same target directory. 1.958 + return getUpdateDirCreate([DIR_UPDATES, "0"]); 1.959 +} 1.960 + 1.961 +/** 1.962 + * Get the Active Updates directory inside the directory where we apply the 1.963 + * background updates. 1.964 + * @return The active updates directory inside the updated directory, as a 1.965 + * nsIFile object. 1.966 + */ 1.967 +function getUpdatesDirInApplyToDir() { 1.968 + var dir = getAppBaseDir(); 1.969 +#ifdef XP_MACOSX 1.970 + dir = dir.parent.parent; // the bundle directory 1.971 +#endif 1.972 + dir.append(UPDATED_DIR); 1.973 +#ifdef XP_MACOSX 1.974 + dir.append("Contents"); 1.975 + dir.append("MacOS"); 1.976 +#endif 1.977 + dir.append(DIR_UPDATES); 1.978 + if (!dir.exists()) { 1.979 + dir.create(Ci.nsILocalFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); 1.980 + } 1.981 + return dir; 1.982 +} 1.983 + 1.984 +/** 1.985 + * Reads the update state from the update.status file in the specified 1.986 + * directory. 1.987 + * @param dir 1.988 + * The dir to look for an update.status file in 1.989 + * @return The status value of the update. 1.990 + */ 1.991 +function readStatusFile(dir) { 1.992 + var statusFile = dir.clone(); 1.993 + statusFile.append(FILE_UPDATE_STATUS); 1.994 + var status = readStringFromFile(statusFile) || STATE_NONE; 1.995 + LOG("readStatusFile - status: " + status + ", path: " + statusFile.path); 1.996 + return status; 1.997 +} 1.998 + 1.999 +/** 1.1000 + * Writes the current update operation/state to a file in the patch 1.1001 + * directory, indicating to the patching system that operations need 1.1002 + * to be performed. 1.1003 + * @param dir 1.1004 + * The patch directory where the update.status file should be 1.1005 + * written. 1.1006 + * @param state 1.1007 + * The state value to write. 1.1008 + */ 1.1009 +function writeStatusFile(dir, state) { 1.1010 + var statusFile = dir.clone(); 1.1011 + statusFile.append(FILE_UPDATE_STATUS); 1.1012 + writeStringToFile(statusFile, state); 1.1013 +} 1.1014 + 1.1015 +#ifdef MOZ_WIDGET_GONK 1.1016 +/** 1.1017 + * Reads the link file specified in the update.link file in the 1.1018 + * specified directory and returns the nsIFile for the 1.1019 + * corresponding file. 1.1020 + * @param dir 1.1021 + * The dir to look for an update.link file in 1.1022 + * @return A nsIFile for the file path specified in the 1.1023 + * update.link file or null if the update.link file 1.1024 + * doesn't exist. 1.1025 + */ 1.1026 +function getFileFromUpdateLink(dir) { 1.1027 + var linkFile = dir.clone(); 1.1028 + linkFile.append(FILE_UPDATE_LINK); 1.1029 + var link = readStringFromFile(linkFile); 1.1030 + LOG("getFileFromUpdateLink linkFile.path: " + linkFile.path + ", link: " + link); 1.1031 + if (!link) { 1.1032 + return null; 1.1033 + } 1.1034 + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); 1.1035 + file.initWithPath(link); 1.1036 + return file; 1.1037 +} 1.1038 + 1.1039 +/** 1.1040 + * Creates a link file, which allows the actual patch to live in 1.1041 + * a directory different from the update directory. 1.1042 + * @param dir 1.1043 + * The patch directory where the update.link file 1.1044 + * should be written. 1.1045 + * @param patchFile 1.1046 + * The fully qualified filename of the patchfile. 1.1047 + */ 1.1048 +function writeLinkFile(dir, patchFile) { 1.1049 + var linkFile = dir.clone(); 1.1050 + linkFile.append(FILE_UPDATE_LINK); 1.1051 + writeStringToFile(linkFile, patchFile.path); 1.1052 + if (patchFile.path.indexOf(gExtStorage) == 0) { 1.1053 + // The patchfile is being stored on external storage. Try to lock it 1.1054 + // so that it doesn't get shared with the PC while we're downloading 1.1055 + // to it. 1.1056 + acquireSDCardMountLock(); 1.1057 + } 1.1058 +} 1.1059 + 1.1060 +/** 1.1061 + * Acquires a VolumeMountLock for the sdcard volume. 1.1062 + * 1.1063 + * This prevents the SDCard from being shared with the PC while 1.1064 + * we're downloading the update. 1.1065 + */ 1.1066 +function acquireSDCardMountLock() { 1.1067 + let volsvc = Cc["@mozilla.org/telephony/volume-service;1"]. 1.1068 + getService(Ci.nsIVolumeService); 1.1069 + if (volsvc) { 1.1070 + gSDCardMountLock = volsvc.createMountLock("sdcard"); 1.1071 + } 1.1072 +} 1.1073 + 1.1074 +/** 1.1075 + * Determines if the state corresponds to an interrupted update. 1.1076 + * This could either be because the download was interrupted, or 1.1077 + * because staging the update was interrupted. 1.1078 + * 1.1079 + * @return true if the state corresponds to an interrupted 1.1080 + * update. 1.1081 + */ 1.1082 +function isInterruptedUpdate(status) { 1.1083 + return (status == STATE_DOWNLOADING) || 1.1084 + (status == STATE_PENDING) || 1.1085 + (status == STATE_APPLYING); 1.1086 +} 1.1087 +#endif // MOZ_WIDGET_GONK 1.1088 + 1.1089 +/** 1.1090 + * Releases any SDCard mount lock that we might have. 1.1091 + * 1.1092 + * This once again allows the SDCard to be shared with the PC. 1.1093 + * 1.1094 + * This function was placed outside the #ifdef so that we didn't 1.1095 + * need to put #ifdefs around the callers. 1.1096 + */ 1.1097 +function releaseSDCardMountLock() { 1.1098 +#ifdef MOZ_WIDGET_GONK 1.1099 + if (gSDCardMountLock) { 1.1100 + gSDCardMountLock.unlock(); 1.1101 + gSDCardMountLock = null; 1.1102 + } 1.1103 +#endif 1.1104 +} 1.1105 + 1.1106 +/** 1.1107 + * Determines if the service should be used to attempt an update 1.1108 + * or not. For now this is only when PREF_APP_UPDATE_SERVICE_ENABLED 1.1109 + * is true and we have Firefox. 1.1110 + * 1.1111 + * @return true if the service should be used for updates. 1.1112 + */ 1.1113 +function shouldUseService() { 1.1114 +#ifdef MOZ_MAINTENANCE_SERVICE 1.1115 + return getPref("getBoolPref", 1.1116 + PREF_APP_UPDATE_SERVICE_ENABLED, false); 1.1117 +#else 1.1118 + return false; 1.1119 +#endif 1.1120 +} 1.1121 + 1.1122 +/** 1.1123 + * Determines if the service is is installed and enabled or not. 1.1124 + * 1.1125 + * @return true if the service should be used for updates, 1.1126 + * is installed and enabled. 1.1127 + */ 1.1128 +function isServiceInstalled() { 1.1129 +#ifdef XP_WIN 1.1130 + let installed = 0; 1.1131 + try { 1.1132 + let wrk = Cc["@mozilla.org/windows-registry-key;1"]. 1.1133 + createInstance(Ci.nsIWindowsRegKey); 1.1134 + wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE, 1.1135 + "SOFTWARE\\Mozilla\\MaintenanceService", 1.1136 + wrk.ACCESS_READ | wrk.WOW64_64); 1.1137 + installed = wrk.readIntValue("Installed"); 1.1138 + wrk.close(); 1.1139 + } catch(e) { 1.1140 + } 1.1141 + installed = installed == 1; // convert to bool 1.1142 + LOG("isServiceInstalled = " + installed); 1.1143 + return installed; 1.1144 +#else 1.1145 + return false; 1.1146 +#endif 1.1147 +} 1.1148 + 1.1149 +/** 1.1150 +# Writes the update's application version to a file in the patch directory. If 1.1151 +# the update doesn't provide application version information via the 1.1152 +# appVersion attribute the string "null" will be written to the file. 1.1153 +# This value is compared during startup (in nsUpdateDriver.cpp) to determine if 1.1154 +# the update should be applied. Note that this won't provide protection from 1.1155 +# downgrade of the application for the nightly user case where the application 1.1156 +# version doesn't change. 1.1157 +# @param dir 1.1158 +# The patch directory where the update.version file should be 1.1159 +# written. 1.1160 +# @param version 1.1161 +# The version value to write. Will be the string "null" when the 1.1162 +# update doesn't provide the appVersion attribute in the update xml. 1.1163 + */ 1.1164 +function writeVersionFile(dir, version) { 1.1165 + var versionFile = dir.clone(); 1.1166 + versionFile.append(FILE_UPDATE_VERSION); 1.1167 + writeStringToFile(versionFile, version); 1.1168 +} 1.1169 + 1.1170 +/** 1.1171 + * Removes the MozUpdater folders that bgupdates/staged updates creates. 1.1172 + */ 1.1173 +function cleanUpMozUpdaterDirs() { 1.1174 + try { 1.1175 + var tmpDir = Cc["@mozilla.org/file/directory_service;1"]. 1.1176 + getService(Ci.nsIProperties). 1.1177 + get("TmpD", Ci.nsIFile); 1.1178 + 1.1179 + // We used to store MozUpdater-i folders directly inside the temp directory. 1.1180 + // We need to cleanup these directories if we detect that they still exist. 1.1181 + // To check if they still exist, we simply check for MozUpdater-1. 1.1182 + var mozUpdaterDir1 = tmpDir.clone(); 1.1183 + mozUpdaterDir1.append("MozUpdater-1"); 1.1184 + // Only try to delete the left over folders in "$Temp/MozUpdater-i/*" if 1.1185 + // MozUpdater-1 exists. 1.1186 + if (mozUpdaterDir1.exists()) { 1.1187 + LOG("cleanUpMozUpdaterDirs - Cleaning top level MozUpdater-i folders"); 1.1188 + let i = 0; 1.1189 + let dirEntries = tmpDir.directoryEntries; 1.1190 + while (dirEntries.hasMoreElements() && i < 10) { 1.1191 + let file = dirEntries.getNext().QueryInterface(Ci.nsILocalFile); 1.1192 + if (file.leafName.startsWith("MozUpdater-") && file.leafName != "MozUpdater-1") { 1.1193 + file.remove(true); 1.1194 + i++; 1.1195 + } 1.1196 + } 1.1197 + // If you enumerate the whole temp directory and the count of deleted 1.1198 + // items is less than 10, then delete MozUpdate-1. 1.1199 + if (i < 10) { 1.1200 + mozUpdaterDir1.remove(true); 1.1201 + } 1.1202 + } 1.1203 + 1.1204 + // If we reach here, we simply need to clean the MozUpdater folder. In our 1.1205 + // new way of storing these files, the unique subfolders are inside MozUpdater 1.1206 + var mozUpdaterDir = tmpDir.clone(); 1.1207 + mozUpdaterDir.append("MozUpdater"); 1.1208 + if (mozUpdaterDir.exists()) { 1.1209 + LOG("cleanUpMozUpdaterDirs - Cleaning MozUpdater folder"); 1.1210 + mozUpdaterDir.remove(true); 1.1211 + } 1.1212 + } catch (e) { 1.1213 + LOG("cleanUpMozUpdaterDirs - Exception: " + e); 1.1214 + } 1.1215 +} 1.1216 + 1.1217 +/** 1.1218 + * Removes the contents of the Updates Directory 1.1219 + * 1.1220 + * @param aBackgroundUpdate Whether the update has been performed in the 1.1221 + * background. If this is true, we move the update log file to the 1.1222 + * updated directory, so that it survives replacing the directories 1.1223 + * later on. 1.1224 + */ 1.1225 +function cleanUpUpdatesDir(aBackgroundUpdate) { 1.1226 + // Bail out if we don't have appropriate permissions 1.1227 + try { 1.1228 + var updateDir = getUpdatesDir(); 1.1229 + } catch (e) { 1.1230 + return; 1.1231 + } 1.1232 + 1.1233 + // Preserve the last update log file for debugging purposes. 1.1234 + let file = updateDir.clone(); 1.1235 + file.append(FILE_UPDATE_LOG); 1.1236 + if (file.exists()) { 1.1237 + let dir; 1.1238 + if (aBackgroundUpdate && getUpdateDirNoCreate([]).equals(getAppBaseDir())) { 1.1239 + dir = getUpdatesDirInApplyToDir(); 1.1240 + } else { 1.1241 + dir = updateDir.parent; 1.1242 + } 1.1243 + let logFile = dir.clone(); 1.1244 + logFile.append(FILE_LAST_LOG); 1.1245 + if (logFile.exists()) { 1.1246 + try { 1.1247 + logFile.moveTo(dir, FILE_BACKUP_LOG); 1.1248 + } catch (e) { 1.1249 + LOG("cleanUpUpdatesDir - failed to rename file " + logFile.path + 1.1250 + " to " + FILE_BACKUP_LOG); 1.1251 + } 1.1252 + } 1.1253 + 1.1254 + try { 1.1255 + file.moveTo(dir, FILE_LAST_LOG); 1.1256 + } catch (e) { 1.1257 + LOG("cleanUpUpdatesDir - failed to rename file " + file.path + 1.1258 + " to " + FILE_LAST_LOG); 1.1259 + } 1.1260 + } 1.1261 + 1.1262 + if (!aBackgroundUpdate) { 1.1263 + let e = updateDir.directoryEntries; 1.1264 + while (e.hasMoreElements()) { 1.1265 + let f = e.getNext().QueryInterface(Ci.nsIFile); 1.1266 +#ifdef MOZ_WIDGET_GONK 1.1267 + if (f.leafName == FILE_UPDATE_LINK) { 1.1268 + let linkedFile = getFileFromUpdateLink(updateDir); 1.1269 + if (linkedFile && linkedFile.exists()) { 1.1270 + linkedFile.remove(false); 1.1271 + } 1.1272 + } 1.1273 +#endif 1.1274 + 1.1275 + // Now, recursively remove this file. The recursive removal is needed for 1.1276 + // Mac OSX because this directory will contain a copy of updater.app, 1.1277 + // which is itself a directory. 1.1278 + try { 1.1279 + f.remove(true); 1.1280 + } catch (e) { 1.1281 + LOG("cleanUpUpdatesDir - failed to remove file " + f.path); 1.1282 + } 1.1283 + } 1.1284 + } 1.1285 + releaseSDCardMountLock(); 1.1286 +} 1.1287 + 1.1288 +/** 1.1289 + * Clean up updates list and the updates directory. 1.1290 + */ 1.1291 +function cleanupActiveUpdate() { 1.1292 + // Move the update from the Active Update list into the Past Updates list. 1.1293 + var um = Cc["@mozilla.org/updates/update-manager;1"]. 1.1294 + getService(Ci.nsIUpdateManager); 1.1295 + um.activeUpdate = null; 1.1296 + um.saveUpdates(); 1.1297 + 1.1298 + // Now trash the updates directory, since we're done with it 1.1299 + cleanUpUpdatesDir(); 1.1300 +} 1.1301 + 1.1302 +/** 1.1303 + * Gets the locale from the update.locale file for replacing %LOCALE% in the 1.1304 + * update url. The update.locale file can be located in the application 1.1305 + * directory or the GRE directory with preference given to it being located in 1.1306 + * the application directory. 1.1307 + */ 1.1308 +function getLocale() { 1.1309 + if (gLocale) 1.1310 + return gLocale; 1.1311 + 1.1312 + for (let res of ['app', 'gre']) { 1.1313 + var channel = Services.io.newChannel("resource://" + res + "/" + FILE_UPDATE_LOCALE, null, null); 1.1314 + try { 1.1315 + var inputStream = channel.open(); 1.1316 + gLocale = readStringFromInputStream(inputStream); 1.1317 + } catch(e) {} 1.1318 + if (gLocale) 1.1319 + break; 1.1320 + } 1.1321 + 1.1322 + if (!gLocale) 1.1323 + throw Components.Exception(FILE_UPDATE_LOCALE + " file doesn't exist in " + 1.1324 + "either the application or GRE directories", 1.1325 + Cr.NS_ERROR_FILE_NOT_FOUND); 1.1326 + 1.1327 + LOG("getLocale - getting locale from file: " + channel.originalURI.spec + 1.1328 + ", locale: " + gLocale); 1.1329 + return gLocale; 1.1330 +} 1.1331 + 1.1332 +/* Get the distribution pref values, from defaults only */ 1.1333 +function getDistributionPrefValue(aPrefName) { 1.1334 + var prefValue = "default"; 1.1335 + 1.1336 + try { 1.1337 + prefValue = Services.prefs.getDefaultBranch(null).getCharPref(aPrefName); 1.1338 + } catch (e) { 1.1339 + // use default when pref not found 1.1340 + } 1.1341 + 1.1342 + return prefValue; 1.1343 +} 1.1344 + 1.1345 +/** 1.1346 + * An enumeration of items in a JS array. 1.1347 + * @constructor 1.1348 + */ 1.1349 +function ArrayEnumerator(aItems) { 1.1350 + this._index = 0; 1.1351 + if (aItems) { 1.1352 + for (var i = 0; i < aItems.length; ++i) { 1.1353 + if (!aItems[i]) 1.1354 + aItems.splice(i, 1); 1.1355 + } 1.1356 + } 1.1357 + this._contents = aItems; 1.1358 +} 1.1359 + 1.1360 +ArrayEnumerator.prototype = { 1.1361 + _index: 0, 1.1362 + _contents: [], 1.1363 + 1.1364 + hasMoreElements: function ArrayEnumerator_hasMoreElements() { 1.1365 + return this._index < this._contents.length; 1.1366 + }, 1.1367 + 1.1368 + getNext: function ArrayEnumerator_getNext() { 1.1369 + return this._contents[this._index++]; 1.1370 + } 1.1371 +}; 1.1372 + 1.1373 +/** 1.1374 + * Writes a string of text to a file. A newline will be appended to the data 1.1375 + * written to the file. This function only works with ASCII text. 1.1376 + */ 1.1377 +function writeStringToFile(file, text) { 1.1378 + var fos = FileUtils.openSafeFileOutputStream(file) 1.1379 + text += "\n"; 1.1380 + fos.write(text, text.length); 1.1381 + FileUtils.closeSafeFileOutputStream(fos); 1.1382 +} 1.1383 + 1.1384 +function readStringFromInputStream(inputStream) { 1.1385 + var sis = Cc["@mozilla.org/scriptableinputstream;1"]. 1.1386 + createInstance(Ci.nsIScriptableInputStream); 1.1387 + sis.init(inputStream); 1.1388 + var text = sis.read(sis.available()); 1.1389 + sis.close(); 1.1390 + if (text[text.length - 1] == "\n") 1.1391 + text = text.slice(0, -1); 1.1392 + return text; 1.1393 +} 1.1394 + 1.1395 +/** 1.1396 + * Reads a string of text from a file. A trailing newline will be removed 1.1397 + * before the result is returned. This function only works with ASCII text. 1.1398 + */ 1.1399 +function readStringFromFile(file) { 1.1400 + if (!file.exists()) { 1.1401 + LOG("readStringFromFile - file doesn't exist: " + file.path); 1.1402 + return null; 1.1403 + } 1.1404 + var fis = Cc["@mozilla.org/network/file-input-stream;1"]. 1.1405 + createInstance(Ci.nsIFileInputStream); 1.1406 + fis.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); 1.1407 + return readStringFromInputStream(fis); 1.1408 +} 1.1409 + 1.1410 +function handleUpdateFailure(update, errorCode) { 1.1411 + update.errorCode = parseInt(errorCode); 1.1412 + if (update.errorCode == FOTA_GENERAL_ERROR || 1.1413 + update.errorCode == FOTA_FILE_OPERATION_ERROR || 1.1414 + update.errorCode == FOTA_RECOVERY_ERROR || 1.1415 + update.errorCode == FOTA_UNKNOWN_ERROR) { 1.1416 + // In the case of FOTA update errors, don't reset the state to pending. This 1.1417 + // causes the FOTA update path to try again, which is not necessarily what 1.1418 + // we want. 1.1419 + update.statusText = gUpdateBundle.GetStringFromName("statusFailed"); 1.1420 + 1.1421 + Cc["@mozilla.org/updates/update-prompt;1"]. 1.1422 + createInstance(Ci.nsIUpdatePrompt). 1.1423 + showUpdateError(update); 1.1424 + writeStatusFile(getUpdatesDir(), STATE_FAILED + ": " + errorCode); 1.1425 + cleanupActiveUpdate(); 1.1426 + return true; 1.1427 + } 1.1428 + 1.1429 + if (update.errorCode == WRITE_ERROR || 1.1430 + update.errorCode == WRITE_ERROR_ACCESS_DENIED || 1.1431 + update.errorCode == WRITE_ERROR_SHARING_VIOLATION_SIGNALED || 1.1432 + update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID || 1.1433 + update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPID || 1.1434 + update.errorCode == WRITE_ERROR_CALLBACK_APP || 1.1435 + update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR) { 1.1436 + Cc["@mozilla.org/updates/update-prompt;1"]. 1.1437 + createInstance(Ci.nsIUpdatePrompt). 1.1438 + showUpdateError(update); 1.1439 + writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING); 1.1440 + return true; 1.1441 + } 1.1442 + 1.1443 + if (update.errorCode == ELEVATION_CANCELED) { 1.1444 + writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING); 1.1445 + return true; 1.1446 + } 1.1447 + 1.1448 + if (update.errorCode == SERVICE_UPDATER_COULD_NOT_BE_STARTED || 1.1449 + update.errorCode == SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS || 1.1450 + update.errorCode == SERVICE_UPDATER_SIGN_ERROR || 1.1451 + update.errorCode == SERVICE_UPDATER_COMPARE_ERROR || 1.1452 + update.errorCode == SERVICE_UPDATER_IDENTITY_ERROR || 1.1453 + update.errorCode == SERVICE_STILL_APPLYING_ON_SUCCESS || 1.1454 + update.errorCode == SERVICE_STILL_APPLYING_ON_FAILURE || 1.1455 + update.errorCode == SERVICE_UPDATER_NOT_FIXED_DRIVE || 1.1456 + update.errorCode == SERVICE_COULD_NOT_LOCK_UPDATER || 1.1457 + update.errorCode == SERVICE_COULD_NOT_COPY_UPDATER || 1.1458 + update.errorCode == SERVICE_INSTALLDIR_ERROR) { 1.1459 + 1.1460 + var failCount = getPref("getIntPref", 1.1461 + PREF_APP_UPDATE_SERVICE_ERRORS, 0); 1.1462 + var maxFail = getPref("getIntPref", 1.1463 + PREF_APP_UPDATE_SERVICE_MAX_ERRORS, 1.1464 + DEFAULT_SERVICE_MAX_ERRORS); 1.1465 + 1.1466 + // As a safety, when the service reaches maximum failures, it will 1.1467 + // disable itself and fallback to using the normal update mechanism 1.1468 + // without the service. 1.1469 + if (failCount >= maxFail) { 1.1470 + Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false); 1.1471 + Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS); 1.1472 + } else { 1.1473 + failCount++; 1.1474 + Services.prefs.setIntPref(PREF_APP_UPDATE_SERVICE_ERRORS, 1.1475 + failCount); 1.1476 + } 1.1477 + 1.1478 + writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING); 1.1479 + try { 1.1480 + Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE"). 1.1481 + add(update.errorCode); 1.1482 + } 1.1483 + catch (e) { 1.1484 + Cu.reportError(e); 1.1485 + } 1.1486 + return true; 1.1487 + } 1.1488 + 1.1489 + try { 1.1490 + Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE").add(0); 1.1491 + } 1.1492 + catch (e) { 1.1493 + Cu.reportError(e); 1.1494 + } 1.1495 + return false; 1.1496 +} 1.1497 + 1.1498 +/** 1.1499 + * Fall back to downloading a complete update in case an update has failed. 1.1500 + * 1.1501 + * @param update the update object that has failed to apply. 1.1502 + * @param postStaging true if we have just attempted to stage an update. 1.1503 + */ 1.1504 +function handleFallbackToCompleteUpdate(update, postStaging) { 1.1505 + cleanupActiveUpdate(); 1.1506 + 1.1507 + update.statusText = gUpdateBundle.GetStringFromName("patchApplyFailure"); 1.1508 + var oldType = update.selectedPatch ? update.selectedPatch.type 1.1509 + : "complete"; 1.1510 + if (update.selectedPatch && oldType == "partial" && update.patchCount == 2) { 1.1511 + // Partial patch application failed, try downloading the complete 1.1512 + // update in the background instead. 1.1513 + LOG("handleFallbackToCompleteUpdate - install of partial patch " + 1.1514 + "failed, downloading complete patch"); 1.1515 + var status = Cc["@mozilla.org/updates/update-service;1"]. 1.1516 + getService(Ci.nsIApplicationUpdateService). 1.1517 + downloadUpdate(update, !postStaging); 1.1518 + if (status == STATE_NONE) 1.1519 + cleanupActiveUpdate(); 1.1520 + } 1.1521 + else { 1.1522 + LOG("handleFallbackToCompleteUpdate - install of complete or " + 1.1523 + "only one patch offered failed."); 1.1524 + } 1.1525 + update.QueryInterface(Ci.nsIWritablePropertyBag); 1.1526 + update.setProperty("patchingFailed", oldType); 1.1527 +} 1.1528 + 1.1529 +/** 1.1530 + * Update Patch 1.1531 + * @param patch 1.1532 + * A <patch> element to initialize this object with 1.1533 + * @throws if patch has a size of 0 1.1534 + * @constructor 1.1535 + */ 1.1536 +function UpdatePatch(patch) { 1.1537 + this._properties = {}; 1.1538 + for (var i = 0; i < patch.attributes.length; ++i) { 1.1539 + var attr = patch.attributes.item(i); 1.1540 + attr.QueryInterface(Ci.nsIDOMAttr); 1.1541 + switch (attr.name) { 1.1542 + case "selected": 1.1543 + this.selected = attr.value == "true"; 1.1544 + break; 1.1545 + case "size": 1.1546 + if (0 == parseInt(attr.value)) { 1.1547 + LOG("UpdatePatch:init - 0-sized patch!"); 1.1548 + throw Cr.NS_ERROR_ILLEGAL_VALUE; 1.1549 + } 1.1550 + // fall through 1.1551 + default: 1.1552 + this[attr.name] = attr.value; 1.1553 + break; 1.1554 + }; 1.1555 + } 1.1556 +} 1.1557 +UpdatePatch.prototype = { 1.1558 + /** 1.1559 + * See nsIUpdateService.idl 1.1560 + */ 1.1561 + serialize: function UpdatePatch_serialize(updates) { 1.1562 + var patch = updates.createElementNS(URI_UPDATE_NS, "patch"); 1.1563 + patch.setAttribute("type", this.type); 1.1564 + patch.setAttribute("URL", this.URL); 1.1565 + // finalURL is not available until after the download has started 1.1566 + if (this.finalURL) 1.1567 + patch.setAttribute("finalURL", this.finalURL); 1.1568 + patch.setAttribute("hashFunction", this.hashFunction); 1.1569 + patch.setAttribute("hashValue", this.hashValue); 1.1570 + patch.setAttribute("size", this.size); 1.1571 + patch.setAttribute("selected", this.selected); 1.1572 + patch.setAttribute("state", this.state); 1.1573 + 1.1574 + for (var p in this._properties) { 1.1575 + if (this._properties[p].present) 1.1576 + patch.setAttribute(p, this._properties[p].data); 1.1577 + } 1.1578 + 1.1579 + return patch; 1.1580 + }, 1.1581 + 1.1582 + /** 1.1583 + * A hash of custom properties 1.1584 + */ 1.1585 + _properties: null, 1.1586 + 1.1587 + /** 1.1588 + * See nsIWritablePropertyBag.idl 1.1589 + */ 1.1590 + setProperty: function UpdatePatch_setProperty(name, value) { 1.1591 + this._properties[name] = { data: value, present: true }; 1.1592 + }, 1.1593 + 1.1594 + /** 1.1595 + * See nsIWritablePropertyBag.idl 1.1596 + */ 1.1597 + deleteProperty: function UpdatePatch_deleteProperty(name) { 1.1598 + if (name in this._properties) 1.1599 + this._properties[name].present = false; 1.1600 + else 1.1601 + throw Cr.NS_ERROR_FAILURE; 1.1602 + }, 1.1603 + 1.1604 + /** 1.1605 + * See nsIPropertyBag.idl 1.1606 + */ 1.1607 + get enumerator() { 1.1608 + var properties = []; 1.1609 + for (var p in this._properties) 1.1610 + properties.push(this._properties[p].data); 1.1611 + return new ArrayEnumerator(properties); 1.1612 + }, 1.1613 + 1.1614 + /** 1.1615 + * See nsIPropertyBag.idl 1.1616 + */ 1.1617 + getProperty: function UpdatePatch_getProperty(name) { 1.1618 + if (name in this._properties && 1.1619 + this._properties[name].present) 1.1620 + return this._properties[name].data; 1.1621 + throw Cr.NS_ERROR_FAILURE; 1.1622 + }, 1.1623 + 1.1624 + /** 1.1625 + * Returns whether or not the update.status file for this patch exists at the 1.1626 + * appropriate location. 1.1627 + */ 1.1628 + get statusFileExists() { 1.1629 + var statusFile = getUpdatesDir(); 1.1630 + statusFile.append(FILE_UPDATE_STATUS); 1.1631 + return statusFile.exists(); 1.1632 + }, 1.1633 + 1.1634 + /** 1.1635 + * See nsIUpdateService.idl 1.1636 + */ 1.1637 + get state() { 1.1638 + if (this._properties.state) 1.1639 + return this._properties.state; 1.1640 + return STATE_NONE; 1.1641 + }, 1.1642 + set state(val) { 1.1643 + this._properties.state = val; 1.1644 + }, 1.1645 + 1.1646 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePatch, 1.1647 + Ci.nsIPropertyBag, 1.1648 + Ci.nsIWritablePropertyBag]) 1.1649 +}; 1.1650 + 1.1651 +/** 1.1652 + * Update 1.1653 + * Implements nsIUpdate 1.1654 + * @param update 1.1655 + * An <update> element to initialize this object with 1.1656 + * @throws if the update contains no patches 1.1657 + * @constructor 1.1658 + */ 1.1659 +function Update(update) { 1.1660 + this._properties = {}; 1.1661 + this._patches = []; 1.1662 + this.isCompleteUpdate = false; 1.1663 + this.isOSUpdate = false; 1.1664 + this.showPrompt = false; 1.1665 + this.showNeverForVersion = false; 1.1666 + this.unsupported = false; 1.1667 + this.channel = "default"; 1.1668 + this.promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200); 1.1669 + 1.1670 + // Null <update>, assume this is a message container and do no 1.1671 + // further initialization 1.1672 + if (!update) 1.1673 + return; 1.1674 + 1.1675 + const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE; 1.1676 + for (var i = 0; i < update.childNodes.length; ++i) { 1.1677 + var patchElement = update.childNodes.item(i); 1.1678 + if (patchElement.nodeType != ELEMENT_NODE || 1.1679 + patchElement.localName != "patch") 1.1680 + continue; 1.1681 + 1.1682 + patchElement.QueryInterface(Ci.nsIDOMElement); 1.1683 + try { 1.1684 + var patch = new UpdatePatch(patchElement); 1.1685 + } catch (e) { 1.1686 + continue; 1.1687 + } 1.1688 + this._patches.push(patch); 1.1689 + } 1.1690 + 1.1691 + if (update.hasAttribute("unsupported")) 1.1692 + this.unsupported = ("true" == update.getAttribute("unsupported")); 1.1693 + else if (update.hasAttribute("minSupportedOSVersion")) { 1.1694 + let minOSVersion = update.getAttribute("minSupportedOSVersion"); 1.1695 + try { 1.1696 + let osVersion = Services.sysinfo.getProperty("version"); 1.1697 + this.unsupported = (Services.vc.compare(osVersion, minOSVersion) < 0); 1.1698 + } catch (e) {} 1.1699 + } 1.1700 + 1.1701 + if (this._patches.length == 0 && !this.unsupported) 1.1702 + throw Cr.NS_ERROR_ILLEGAL_VALUE; 1.1703 + 1.1704 + // Fallback to the behavior prior to bug 530872 if the update does not have an 1.1705 + // appVersion attribute. 1.1706 + if (!update.hasAttribute("appVersion")) { 1.1707 + if (update.getAttribute("type") == "major") { 1.1708 + if (update.hasAttribute("detailsURL")) { 1.1709 + this.billboardURL = update.getAttribute("detailsURL"); 1.1710 + this.showPrompt = true; 1.1711 + this.showNeverForVersion = true; 1.1712 + } 1.1713 + } 1.1714 + } 1.1715 + 1.1716 + for (var i = 0; i < update.attributes.length; ++i) { 1.1717 + var attr = update.attributes.item(i); 1.1718 + attr.QueryInterface(Ci.nsIDOMAttr); 1.1719 + if (attr.value == "undefined") 1.1720 + continue; 1.1721 + else if (attr.name == "detailsURL") 1.1722 + this._detailsURL = attr.value; 1.1723 + else if (attr.name == "extensionVersion") { 1.1724 + // Prevent extensionVersion from replacing appVersion if appVersion is 1.1725 + // present in the update xml. 1.1726 + if (!this.appVersion) 1.1727 + this.appVersion = attr.value; 1.1728 + } 1.1729 + else if (attr.name == "installDate" && attr.value) 1.1730 + this.installDate = parseInt(attr.value); 1.1731 + else if (attr.name == "isCompleteUpdate") 1.1732 + this.isCompleteUpdate = attr.value == "true"; 1.1733 + else if (attr.name == "isSecurityUpdate") 1.1734 + this.isSecurityUpdate = attr.value == "true"; 1.1735 + else if (attr.name == "isOSUpdate") 1.1736 + this.isOSUpdate = attr.value == "true"; 1.1737 + else if (attr.name == "showNeverForVersion") 1.1738 + this.showNeverForVersion = attr.value == "true"; 1.1739 + else if (attr.name == "showPrompt") 1.1740 + this.showPrompt = attr.value == "true"; 1.1741 + else if (attr.name == "promptWaitTime") 1.1742 + { 1.1743 + if(!isNaN(attr.value)) 1.1744 + this.promptWaitTime = parseInt(attr.value); 1.1745 + } 1.1746 + else if (attr.name == "version") { 1.1747 + // Prevent version from replacing displayVersion if displayVersion is 1.1748 + // present in the update xml. 1.1749 + if (!this.displayVersion) 1.1750 + this.displayVersion = attr.value; 1.1751 + } 1.1752 + else if (attr.name != "unsupported") { 1.1753 + this[attr.name] = attr.value; 1.1754 + 1.1755 + switch (attr.name) { 1.1756 + case "appVersion": 1.1757 + case "billboardURL": 1.1758 + case "buildID": 1.1759 + case "channel": 1.1760 + case "displayVersion": 1.1761 + case "licenseURL": 1.1762 + case "name": 1.1763 + case "platformVersion": 1.1764 + case "previousAppVersion": 1.1765 + case "serviceURL": 1.1766 + case "statusText": 1.1767 + case "type": 1.1768 + break; 1.1769 + default: 1.1770 + // Save custom attributes when serializing to the local xml file but 1.1771 + // don't use this method for the expected attributes which are already 1.1772 + // handled in serialize. 1.1773 + this.setProperty(attr.name, attr.value); 1.1774 + break; 1.1775 + }; 1.1776 + } 1.1777 + } 1.1778 + 1.1779 + // Set the initial value with the current time when it doesn't already have a 1.1780 + // value or the value is already set to 0 (bug 316328). 1.1781 + if (!this.installDate && this.installDate != 0) 1.1782 + this.installDate = (new Date()).getTime(); 1.1783 + 1.1784 + // The Update Name is either the string provided by the <update> element, or 1.1785 + // the string: "<App Name> <Update App Version>" 1.1786 + var name = ""; 1.1787 + if (update.hasAttribute("name")) 1.1788 + name = update.getAttribute("name"); 1.1789 + else { 1.1790 + var brandBundle = Services.strings.createBundle(URI_BRAND_PROPERTIES); 1.1791 + var appName = brandBundle.GetStringFromName("brandShortName"); 1.1792 + name = gUpdateBundle.formatStringFromName("updateName", 1.1793 + [appName, this.displayVersion], 2); 1.1794 + } 1.1795 + this.name = name; 1.1796 +} 1.1797 +Update.prototype = { 1.1798 + /** 1.1799 + * See nsIUpdateService.idl 1.1800 + */ 1.1801 + get patchCount() { 1.1802 + return this._patches.length; 1.1803 + }, 1.1804 + 1.1805 + /** 1.1806 + * See nsIUpdateService.idl 1.1807 + */ 1.1808 + getPatchAt: function Update_getPatchAt(index) { 1.1809 + return this._patches[index]; 1.1810 + }, 1.1811 + 1.1812 + /** 1.1813 + * See nsIUpdateService.idl 1.1814 + * 1.1815 + * We use a copy of the state cached on this object in |_state| only when 1.1816 + * there is no selected patch, i.e. in the case when we could not load 1.1817 + * |.activeUpdate| from the update manager for some reason but still have 1.1818 + * the update.status file to work with. 1.1819 + */ 1.1820 + _state: "", 1.1821 + set state(state) { 1.1822 + if (this.selectedPatch) 1.1823 + this.selectedPatch.state = state; 1.1824 + this._state = state; 1.1825 + return state; 1.1826 + }, 1.1827 + get state() { 1.1828 + if (this.selectedPatch) 1.1829 + return this.selectedPatch.state; 1.1830 + return this._state; 1.1831 + }, 1.1832 + 1.1833 + /** 1.1834 + * See nsIUpdateService.idl 1.1835 + */ 1.1836 + errorCode: 0, 1.1837 + 1.1838 + /** 1.1839 + * See nsIUpdateService.idl 1.1840 + */ 1.1841 + get selectedPatch() { 1.1842 + for (var i = 0; i < this.patchCount; ++i) { 1.1843 + if (this._patches[i].selected) 1.1844 + return this._patches[i]; 1.1845 + } 1.1846 + return null; 1.1847 + }, 1.1848 + 1.1849 + /** 1.1850 + * See nsIUpdateService.idl 1.1851 + */ 1.1852 + get detailsURL() { 1.1853 + if (!this._detailsURL) { 1.1854 + try { 1.1855 + // Try using a default details URL supplied by the distribution 1.1856 + // if the update XML does not supply one. 1.1857 + return Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_DETAILS); 1.1858 + } 1.1859 + catch (e) { 1.1860 + } 1.1861 + } 1.1862 + return this._detailsURL || ""; 1.1863 + }, 1.1864 + 1.1865 + /** 1.1866 + * See nsIUpdateService.idl 1.1867 + */ 1.1868 + serialize: function Update_serialize(updates) { 1.1869 + var update = updates.createElementNS(URI_UPDATE_NS, "update"); 1.1870 + update.setAttribute("appVersion", this.appVersion); 1.1871 + update.setAttribute("buildID", this.buildID); 1.1872 + update.setAttribute("channel", this.channel); 1.1873 + update.setAttribute("displayVersion", this.displayVersion); 1.1874 + // for backwards compatibility in case the user downgrades 1.1875 + update.setAttribute("extensionVersion", this.appVersion); 1.1876 + update.setAttribute("installDate", this.installDate); 1.1877 + update.setAttribute("isCompleteUpdate", this.isCompleteUpdate); 1.1878 + update.setAttribute("isOSUpdate", this.isOSUpdate); 1.1879 + update.setAttribute("name", this.name); 1.1880 + update.setAttribute("serviceURL", this.serviceURL); 1.1881 + update.setAttribute("showNeverForVersion", this.showNeverForVersion); 1.1882 + update.setAttribute("showPrompt", this.showPrompt); 1.1883 + update.setAttribute("promptWaitTime", this.promptWaitTime); 1.1884 + update.setAttribute("type", this.type); 1.1885 + // for backwards compatibility in case the user downgrades 1.1886 + update.setAttribute("version", this.displayVersion); 1.1887 + 1.1888 + // Optional attributes 1.1889 + if (this.billboardURL) 1.1890 + update.setAttribute("billboardURL", this.billboardURL); 1.1891 + if (this.detailsURL) 1.1892 + update.setAttribute("detailsURL", this.detailsURL); 1.1893 + if (this.licenseURL) 1.1894 + update.setAttribute("licenseURL", this.licenseURL); 1.1895 + if (this.platformVersion) 1.1896 + update.setAttribute("platformVersion", this.platformVersion); 1.1897 + if (this.previousAppVersion) 1.1898 + update.setAttribute("previousAppVersion", this.previousAppVersion); 1.1899 + if (this.statusText) 1.1900 + update.setAttribute("statusText", this.statusText); 1.1901 + if (this.unsupported) 1.1902 + update.setAttribute("unsupported", this.unsupported); 1.1903 + updates.documentElement.appendChild(update); 1.1904 + 1.1905 + for (var p in this._properties) { 1.1906 + if (this._properties[p].present) 1.1907 + update.setAttribute(p, this._properties[p].data); 1.1908 + } 1.1909 + 1.1910 + for (var i = 0; i < this.patchCount; ++i) 1.1911 + update.appendChild(this.getPatchAt(i).serialize(updates)); 1.1912 + 1.1913 + return update; 1.1914 + }, 1.1915 + 1.1916 + /** 1.1917 + * A hash of custom properties 1.1918 + */ 1.1919 + _properties: null, 1.1920 + 1.1921 + /** 1.1922 + * See nsIWritablePropertyBag.idl 1.1923 + */ 1.1924 + setProperty: function Update_setProperty(name, value) { 1.1925 + this._properties[name] = { data: value, present: true }; 1.1926 + }, 1.1927 + 1.1928 + /** 1.1929 + * See nsIWritablePropertyBag.idl 1.1930 + */ 1.1931 + deleteProperty: function Update_deleteProperty(name) { 1.1932 + if (name in this._properties) 1.1933 + this._properties[name].present = false; 1.1934 + else 1.1935 + throw Cr.NS_ERROR_FAILURE; 1.1936 + }, 1.1937 + 1.1938 + /** 1.1939 + * See nsIPropertyBag.idl 1.1940 + */ 1.1941 + get enumerator() { 1.1942 + var properties = []; 1.1943 + for (var p in this._properties) 1.1944 + properties.push(this._properties[p].data); 1.1945 + return new ArrayEnumerator(properties); 1.1946 + }, 1.1947 + 1.1948 + /** 1.1949 + * See nsIPropertyBag.idl 1.1950 + */ 1.1951 + getProperty: function Update_getProperty(name) { 1.1952 + if (name in this._properties && this._properties[name].present) 1.1953 + return this._properties[name].data; 1.1954 + throw Cr.NS_ERROR_FAILURE; 1.1955 + }, 1.1956 + 1.1957 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdate, 1.1958 + Ci.nsIPropertyBag, 1.1959 + Ci.nsIWritablePropertyBag]) 1.1960 +}; 1.1961 + 1.1962 +const UpdateServiceFactory = { 1.1963 + _instance: null, 1.1964 + createInstance: function (outer, iid) { 1.1965 + if (outer != null) 1.1966 + throw Cr.NS_ERROR_NO_AGGREGATION; 1.1967 + return this._instance == null ? this._instance = new UpdateService() : 1.1968 + this._instance; 1.1969 + } 1.1970 +}; 1.1971 + 1.1972 +/** 1.1973 + * UpdateService 1.1974 + * A Service for managing the discovery and installation of software updates. 1.1975 + * @constructor 1.1976 + */ 1.1977 +function UpdateService() { 1.1978 + LOG("Creating UpdateService"); 1.1979 + Services.obs.addObserver(this, "xpcom-shutdown", false); 1.1980 + Services.prefs.addObserver(PREF_APP_UPDATE_LOG, this, false); 1.1981 +#ifdef MOZ_WIDGET_GONK 1.1982 + // PowerManagerService::SyncProfile (which is called for Reboot, PowerOff 1.1983 + // and Restart) sends the profile-change-net-teardown event. We can then 1.1984 + // pause the download in a similar manner to xpcom-shutdown. 1.1985 + Services.obs.addObserver(this, "profile-change-net-teardown", false); 1.1986 +#endif 1.1987 +} 1.1988 + 1.1989 +UpdateService.prototype = { 1.1990 + /** 1.1991 + * The downloader we are using to download updates. There is only ever one of 1.1992 + * these. 1.1993 + */ 1.1994 + _downloader: null, 1.1995 + 1.1996 + /** 1.1997 + * Incompatible add-on count. 1.1998 + */ 1.1999 + _incompatAddonsCount: 0, 1.2000 + 1.2001 + /** 1.2002 + * Whether or not the service registered the "online" observer. 1.2003 + */ 1.2004 + _registeredOnlineObserver: false, 1.2005 + 1.2006 + /** 1.2007 + * The current number of consecutive socket errors 1.2008 + */ 1.2009 + _consecutiveSocketErrors: 0, 1.2010 + 1.2011 + /** 1.2012 + * A timer used to retry socket errors 1.2013 + */ 1.2014 + _retryTimer: null, 1.2015 + 1.2016 + /** 1.2017 + * Whether or not a background update check was initiated by the 1.2018 + * application update timer notification. 1.2019 + */ 1.2020 + _isNotify: true, 1.2021 + 1.2022 + /** 1.2023 + * Handle Observer Service notifications 1.2024 + * @param subject 1.2025 + * The subject of the notification 1.2026 + * @param topic 1.2027 + * The notification name 1.2028 + * @param data 1.2029 + * Additional data 1.2030 + */ 1.2031 + observe: function AUS_observe(subject, topic, data) { 1.2032 + switch (topic) { 1.2033 + case "post-update-processing": 1.2034 + // Clean up any extant updates 1.2035 + this._postUpdateProcessing(); 1.2036 + break; 1.2037 + case "network:offline-status-changed": 1.2038 + this._offlineStatusChanged(data); 1.2039 + break; 1.2040 + case "nsPref:changed": 1.2041 + if (data == PREF_APP_UPDATE_LOG) { 1.2042 + gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false); 1.2043 + } 1.2044 + break; 1.2045 +#ifdef MOZ_WIDGET_GONK 1.2046 + case "profile-change-net-teardown": // fall thru 1.2047 +#endif 1.2048 + case "xpcom-shutdown": 1.2049 + Services.obs.removeObserver(this, topic); 1.2050 + Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, this); 1.2051 + 1.2052 + if (this._retryTimer) { 1.2053 + this._retryTimer.cancel(); 1.2054 + } 1.2055 + 1.2056 + this.pauseDownload(); 1.2057 + // Prevent leaking the downloader (bug 454964) 1.2058 + this._downloader = null; 1.2059 + break; 1.2060 + } 1.2061 + }, 1.2062 + 1.2063 + /** 1.2064 + * The following needs to happen during the post-update-processing 1.2065 + * notification from nsUpdateServiceStub.js: 1.2066 + * 1. post update processing 1.2067 + * 2. resume of a download that was in progress during a previous session 1.2068 + * 3. start of a complete update download after the failure to apply a partial 1.2069 + * update 1.2070 + */ 1.2071 + 1.2072 + /** 1.2073 + * Perform post-processing on updates lingering in the updates directory 1.2074 + * from a previous application session - either report install failures (and 1.2075 + * optionally attempt to fetch a different version if appropriate) or 1.2076 + * notify the user of install success. 1.2077 + */ 1.2078 + _postUpdateProcessing: function AUS__postUpdateProcessing() { 1.2079 + // canCheckForUpdates will return false when metro-only updates are disabled 1.2080 + // from within metro. In that case we still want _postUpdateProcessing to 1.2081 + // run. gMetroUpdatesEnabled returns true on non Windows 8 platforms. 1.2082 + // We want _postUpdateProcessing to run so that it will update the history 1.2083 + // XML. Without updating the history XML, the about flyout will continue to 1.2084 + // have the "Restart to Apply Update" button (history xml indicates update 1.2085 + // is applied). 1.2086 + // TODO: I think this whole if-block should be removed since updates can 1.2087 + // always be applied via the about dialog, we should be running post update 1.2088 + // in those cases. 1.2089 + if (!this.canCheckForUpdates && gMetroUpdatesEnabled) { 1.2090 + LOG("UpdateService:_postUpdateProcessing - unable to check for " + 1.2091 + "updates... returning early"); 1.2092 + return; 1.2093 + } 1.2094 + 1.2095 + if (!this.canApplyUpdates) { 1.2096 + LOG("UpdateService:_postUpdateProcessing - unable to apply " + 1.2097 + "updates... returning early"); 1.2098 + // If the update is present in the update directly somehow, 1.2099 + // it would prevent us from notifying the user of futher updates. 1.2100 + cleanupActiveUpdate(); 1.2101 + return; 1.2102 + } 1.2103 + 1.2104 + var status = readStatusFile(getUpdatesDir()); 1.2105 + // STATE_NONE status means that the update.status file is present but a 1.2106 + // background download error occurred. 1.2107 + if (status == STATE_NONE) { 1.2108 + LOG("UpdateService:_postUpdateProcessing - no status, no update"); 1.2109 + cleanupActiveUpdate(); 1.2110 + return; 1.2111 + } 1.2112 + 1.2113 + var um = Cc["@mozilla.org/updates/update-manager;1"]. 1.2114 + getService(Ci.nsIUpdateManager); 1.2115 + 1.2116 +#ifdef MOZ_WIDGET_GONK 1.2117 + // This code is called very early in the boot process, before we've even 1.2118 + // had a chance to setup the UI so we can give feedback to the user. 1.2119 + // 1.2120 + // Since the download may be occuring over a link which has associated 1.2121 + // cost, we want to require user-consent before resuming the download. 1.2122 + // Also, applying an already downloaded update now is undesireable, 1.2123 + // since the phone will look dead while the update is being applied. 1.2124 + // Applying the update can take several minutes. Instead we wait until 1.2125 + // the UI is initialized so it is possible to give feedback to and get 1.2126 + // consent to update from the user. 1.2127 + if (isInterruptedUpdate(status)) { 1.2128 + LOG("UpdateService:_postUpdateProcessing - interrupted update detected - wait for user consent"); 1.2129 + return; 1.2130 + } 1.2131 +#endif 1.2132 + 1.2133 + var update = um.activeUpdate; 1.2134 + 1.2135 + if (status == STATE_DOWNLOADING) { 1.2136 + LOG("UpdateService:_postUpdateProcessing - patch found in downloading " + 1.2137 + "state"); 1.2138 + if (update && update.state != STATE_SUCCEEDED) { 1.2139 + // Resume download 1.2140 + var status = this.downloadUpdate(update, true); 1.2141 + if (status == STATE_NONE) 1.2142 + cleanupActiveUpdate(); 1.2143 + } 1.2144 + return; 1.2145 + } 1.2146 + 1.2147 + if (status == STATE_APPLYING) { 1.2148 + // This indicates that the background updater service is in either of the 1.2149 + // following two states: 1.2150 + // 1. It is in the process of applying an update in the background, and 1.2151 + // we just happen to be racing against that. 1.2152 + // 2. It has failed to apply an update for some reason, and we hit this 1.2153 + // case because the updater process has set the update status to 1.2154 + // applying, but has never finished. 1.2155 + // In order to differentiate between these two states, we look at the 1.2156 + // state field of the update object. If it's "pending", then we know 1.2157 + // that this is the first time we're hitting this case, so we switch 1.2158 + // that state to "applying" and we just wait and hope for the best. 1.2159 + // If it's "applying", we know that we've already been here once, so 1.2160 + // we really want to start from a clean state. 1.2161 + if (update && 1.2162 + (update.state == STATE_PENDING || update.state == STATE_PENDING_SVC)) { 1.2163 + LOG("UpdateService:_postUpdateProcessing - patch found in applying " + 1.2164 + "state for the first time"); 1.2165 + update.state = STATE_APPLYING; 1.2166 + um.saveUpdates(); 1.2167 + } else { // We get here even if we don't have an update object 1.2168 + LOG("UpdateService:_postUpdateProcessing - patch found in applying " + 1.2169 + "state for the second time"); 1.2170 + cleanupActiveUpdate(); 1.2171 + } 1.2172 + return; 1.2173 + } 1.2174 + 1.2175 +#ifdef MOZ_WIDGET_GONK 1.2176 + // The update is only applied but not selected to be installed 1.2177 + if (status == STATE_APPLIED && update && update.isOSUpdate) { 1.2178 + LOG("UpdateService:_postUpdateProcessing - update staged as applied found"); 1.2179 + return; 1.2180 + } 1.2181 + 1.2182 + if (status == STATE_APPLIED_OS && update && update.isOSUpdate) { 1.2183 + // In gonk, we need to check for OS update status after startup, since 1.2184 + // the recovery partition won't write to update.status for us 1.2185 + var recoveryService = Cc["@mozilla.org/recovery-service;1"]. 1.2186 + getService(Ci.nsIRecoveryService); 1.2187 + 1.2188 + var fotaStatus = recoveryService.getFotaUpdateStatus(); 1.2189 + switch (fotaStatus) { 1.2190 + case Ci.nsIRecoveryService.FOTA_UPDATE_SUCCESS: 1.2191 + status = STATE_SUCCEEDED; 1.2192 + break; 1.2193 + case Ci.nsIRecoveryService.FOTA_UPDATE_FAIL: 1.2194 + status = STATE_FAILED + ": " + FOTA_GENERAL_ERROR; 1.2195 + break; 1.2196 + case Ci.nsIRecoveryService.FOTA_UPDATE_UNKNOWN: 1.2197 + default: 1.2198 + status = STATE_FAILED + ": " + FOTA_UNKNOWN_ERROR; 1.2199 + break; 1.2200 + } 1.2201 + } 1.2202 +#endif 1.2203 + 1.2204 + if (!update) { 1.2205 + if (status != STATE_SUCCEEDED) { 1.2206 + LOG("UpdateService:_postUpdateProcessing - previous patch failed " + 1.2207 + "and no patch available"); 1.2208 + cleanupActiveUpdate(); 1.2209 + return; 1.2210 + } 1.2211 + update = new Update(null); 1.2212 + } 1.2213 + 1.2214 + var prompter = Cc["@mozilla.org/updates/update-prompt;1"]. 1.2215 + createInstance(Ci.nsIUpdatePrompt); 1.2216 + 1.2217 + update.state = status; 1.2218 + this._sendStatusCodeTelemetryPing(status); 1.2219 + 1.2220 + if (status == STATE_SUCCEEDED) { 1.2221 + update.statusText = gUpdateBundle.GetStringFromName("installSuccess"); 1.2222 + 1.2223 + // Update the patch's metadata. 1.2224 + um.activeUpdate = update; 1.2225 + Services.prefs.setBoolPref(PREF_APP_UPDATE_POSTUPDATE, true); 1.2226 + prompter.showUpdateInstalled(); 1.2227 + 1.2228 + // Done with this update. Clean it up. 1.2229 + cleanupActiveUpdate(); 1.2230 + } 1.2231 + else { 1.2232 + // If we hit an error, then the error code will be included in the status 1.2233 + // string following a colon and a space. If we had an I/O error, then we 1.2234 + // assume that the patch is not invalid, and we re-stage the patch so that 1.2235 + // it can be attempted again the next time we restart. This will leave a 1.2236 + // space at the beginning of the error code when there is a failure which 1.2237 + // will be removed by using parseInt below. This prevents panic which has 1.2238 + // occurred numerous times previously (see bug 569642 comment #9 for one 1.2239 + // example) when testing releases due to forgetting to include the space. 1.2240 + var ary = status.split(":"); 1.2241 + update.state = ary[0]; 1.2242 + if (update.state == STATE_FAILED && ary[1]) { 1.2243 + if (handleUpdateFailure(update, ary[1])) { 1.2244 + return; 1.2245 + } 1.2246 + } 1.2247 + 1.2248 + // Something went wrong with the patch application process. 1.2249 + handleFallbackToCompleteUpdate(update, false); 1.2250 + 1.2251 + prompter.showUpdateError(update); 1.2252 + } 1.2253 + 1.2254 + // Now trash the MozUpdater folders which staged/bgupdates uses. 1.2255 + cleanUpMozUpdaterDirs(); 1.2256 + }, 1.2257 + 1.2258 + /** 1.2259 + * Submit a telemetry ping with the boolean value of a pref for a histogram 1.2260 + * 1.2261 + * @param pref 1.2262 + * The preference to report 1.2263 + * @param histogram 1.2264 + * The histogram ID to report to 1.2265 + */ 1.2266 + _sendBoolPrefTelemetryPing: function AUS__boolTelemetryPing(pref, histogram) { 1.2267 + try { 1.2268 + // The getPref is already wrapped in a try/catch but we never 1.2269 + // want telemetry pings breaking app update so we just put it 1.2270 + // inside the try to be safe. 1.2271 + let val = getPref("getBoolPref", pref, false); 1.2272 + Services.telemetry.getHistogramById(histogram).add(+val); 1.2273 + } catch(e) { 1.2274 + // Don't allow any exception to be propagated. 1.2275 + Cu.reportError(e); 1.2276 + } 1.2277 + }, 1.2278 + 1.2279 +#ifdef XP_WIN 1.2280 + /** 1.2281 + * Submit a telemetry ping with a boolean value which indicates if the service 1.2282 + * is installed. 1.2283 + * Also submits a telemetry ping with a boolean value which indicates if the 1.2284 + * service was at some point installed, but is now uninstalled. 1.2285 + */ 1.2286 + _sendServiceInstalledTelemetryPing: function AUS__svcInstallTelemetryPing() { 1.2287 + let installed = isServiceInstalled(); // Is the service installed? 1.2288 + let attempted = 0; 1.2289 + try { 1.2290 + let wrk = Cc["@mozilla.org/windows-registry-key;1"]. 1.2291 + createInstance(Ci.nsIWindowsRegKey); 1.2292 + wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE, 1.2293 + "SOFTWARE\\Mozilla\\MaintenanceService", 1.2294 + wrk.ACCESS_READ | wrk.WOW64_64); 1.2295 + // Was the service at some point installed, but is now uninstalled? 1.2296 + attempted = wrk.readIntValue("Attempted"); 1.2297 + wrk.close(); 1.2298 + } catch(e) { 1.2299 + } 1.2300 + try { 1.2301 + let h = Services.telemetry.getHistogramById("UPDATER_SERVICE_INSTALLED"); 1.2302 + h.add(Number(installed)); 1.2303 + } catch(e) { 1.2304 + // Don't allow any exception to be propagated. 1.2305 + Cu.reportError(e); 1.2306 + } 1.2307 + try { 1.2308 + let h = Services.telemetry.getHistogramById("UPDATER_SERVICE_MANUALLY_UNINSTALLED"); 1.2309 + h.add(!installed && attempted ? 1 : 0); 1.2310 + } catch(e) { 1.2311 + // Don't allow any exception to be propagated. 1.2312 + Cu.reportError(e); 1.2313 + } 1.2314 + }, 1.2315 +#endif 1.2316 + 1.2317 + /** 1.2318 + * Submit a telemetry ping with the int value of a pref for a histogram 1.2319 + * 1.2320 + * @param pref 1.2321 + * The preference to report 1.2322 + * @param histogram 1.2323 + * The histogram ID to report to 1.2324 + */ 1.2325 + _sendIntPrefTelemetryPing: function AUS__intTelemetryPing(pref, histogram) { 1.2326 + try { 1.2327 + // The getPref is already wrapped in a try/catch but we never 1.2328 + // want telemetry pings breaking app update so we just put it 1.2329 + // inside the try to be safe. 1.2330 + let val = getPref("getIntPref", pref, 0); 1.2331 + Services.telemetry.getHistogramById(histogram).add(val); 1.2332 + } catch(e) { 1.2333 + // Don't allow any exception to be propagated. 1.2334 + Cu.reportError(e); 1.2335 + } 1.2336 + }, 1.2337 + 1.2338 + 1.2339 + /** 1.2340 + * Submit the results of applying the update via telemetry. 1.2341 + * 1.2342 + * @param status 1.2343 + * The status of the update as read from the update.status file 1.2344 + */ 1.2345 + _sendStatusCodeTelemetryPing: function AUS__statusTelemetryPing(status) { 1.2346 + try { 1.2347 + let parts = status.split(":"); 1.2348 + if ((parts.length == 1 && status != STATE_SUCCEEDED) || 1.2349 + (parts.length > 1 && parts[0] != STATE_FAILED)) { 1.2350 + // Should also report STATE_DOWNLOAD_FAILED 1.2351 + return; 1.2352 + } 1.2353 + let result = 0; // 0 means success 1.2354 + if (parts.length > 1) { 1.2355 + result = parseInt(parts[1]) || INVALID_UPDATER_STATUS_CODE; 1.2356 + } 1.2357 + Services.telemetry.getHistogramById("UPDATER_STATUS_CODES").add(result); 1.2358 + } catch(e) { 1.2359 + // Don't allow any exception to be propagated. 1.2360 + Cu.reportError(e); 1.2361 + } 1.2362 + }, 1.2363 + 1.2364 + /** 1.2365 + * Submit the interval in days since the last notification for this background 1.2366 + * update check. 1.2367 + */ 1.2368 + _sendLastNotifyIntervalPing: function AUS__notifyIntervalPing() { 1.2369 + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LASTUPDATETIME)) { 1.2370 + let idSuffix = this._isNotify ? "NOTIFY" : "EXTERNAL"; 1.2371 + let lastUpdateTimeSeconds = getPref("getIntPref", 1.2372 + PREF_APP_UPDATE_LASTUPDATETIME, 0); 1.2373 + if (lastUpdateTimeSeconds) { 1.2374 + let currentTimeSeconds = Math.round(Date.now() / 1000); 1.2375 + if (lastUpdateTimeSeconds > currentTimeSeconds) { 1.2376 + try { 1.2377 + Services.telemetry. 1.2378 + getHistogramById("UPDATER_INVALID_LASTUPDATETIME_" + idSuffix). 1.2379 + add(1); 1.2380 + } catch(e) { 1.2381 + Cu.reportError(e); 1.2382 + } 1.2383 + } 1.2384 + else { 1.2385 + let intervalDays = (currentTimeSeconds - lastUpdateTimeSeconds) / 1.2386 + (60 * 60 * 24); 1.2387 + try { 1.2388 + Services.telemetry. 1.2389 + getHistogramById("UPDATER_INVALID_LASTUPDATETIME_" + idSuffix). 1.2390 + add(0); 1.2391 + Services.telemetry. 1.2392 + getHistogramById("UPDATER_LAST_NOTIFY_INTERVAL_DAYS_" + idSuffix). 1.2393 + add(intervalDays); 1.2394 + } catch(e) { 1.2395 + Cu.reportError(e); 1.2396 + } 1.2397 + } 1.2398 + } 1.2399 + } 1.2400 + }, 1.2401 + 1.2402 + /** 1.2403 + * Submit the result for the background update check. 1.2404 + * 1.2405 + * @param code 1.2406 + * An integer value as defined by the PING_BGUC_* constants. 1.2407 + */ 1.2408 + _backgroundUpdateCheckCodePing: function AUS__backgroundUpdateCheckCodePing(code) { 1.2409 + try { 1.2410 + let idSuffix = this._isNotify ? "NOTIFY" : "EXTERNAL"; 1.2411 + Services.telemetry. 1.2412 + getHistogramById("UPDATER_BACKGROUND_CHECK_CODE_" + idSuffix).add(code); 1.2413 + } 1.2414 + catch (e) { 1.2415 + Cu.reportError(e); 1.2416 + } 1.2417 + }, 1.2418 + 1.2419 + /** 1.2420 + * Register an observer when the network comes online, so we can short-circuit 1.2421 + * the app.update.interval when there isn't connectivity 1.2422 + */ 1.2423 + _registerOnlineObserver: function AUS__registerOnlineObserver() { 1.2424 + if (this._registeredOnlineObserver) { 1.2425 + LOG("UpdateService:_registerOnlineObserver - observer already registered"); 1.2426 + return; 1.2427 + } 1.2428 + 1.2429 + LOG("UpdateService:_registerOnlineObserver - waiting for the network to " + 1.2430 + "be online, then forcing another check"); 1.2431 + 1.2432 + Services.obs.addObserver(this, "network:offline-status-changed", false); 1.2433 + this._registeredOnlineObserver = true; 1.2434 + }, 1.2435 + 1.2436 + /** 1.2437 + * Called from the network:offline-status-changed observer. 1.2438 + */ 1.2439 + _offlineStatusChanged: function AUS__offlineStatusChanged(status) { 1.2440 + if (status !== "online") { 1.2441 + return; 1.2442 + } 1.2443 + 1.2444 + Services.obs.removeObserver(this, "network:offline-status-changed"); 1.2445 + this._registeredOnlineObserver = false; 1.2446 + 1.2447 + LOG("UpdateService:_offlineStatusChanged - network is online, forcing " + 1.2448 + "another background check"); 1.2449 + 1.2450 + // the background checker is contained in notify 1.2451 + this._attemptResume(); 1.2452 + }, 1.2453 + 1.2454 + onCheckComplete: function AUS_onCheckComplete(request, updates, updateCount) { 1.2455 + this._selectAndInstallUpdate(updates); 1.2456 + }, 1.2457 + 1.2458 + onError: function AUS_onError(request, update) { 1.2459 + LOG("UpdateService:onError - error during background update. error code: " + 1.2460 + update.errorCode + ", status text: " + update.statusText); 1.2461 + 1.2462 + var maxErrors; 1.2463 + var errCount; 1.2464 + if (update.errorCode == NETWORK_ERROR_OFFLINE) { 1.2465 + // Register an online observer to try again 1.2466 + this._registerOnlineObserver(); 1.2467 + this._backgroundUpdateCheckCodePing(PING_BGUC_OFFLINE); 1.2468 + return; 1.2469 + } 1.2470 + 1.2471 + if (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE || 1.2472 + update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE) { 1.2473 + errCount = getPref("getIntPref", PREF_APP_UPDATE_CERT_ERRORS, 0); 1.2474 + errCount++; 1.2475 + Services.prefs.setIntPref(PREF_APP_UPDATE_CERT_ERRORS, errCount); 1.2476 + maxErrors = getPref("getIntPref", PREF_APP_UPDATE_CERT_MAXERRORS, 5); 1.2477 + } 1.2478 + else { 1.2479 + update.errorCode = BACKGROUNDCHECK_MULTIPLE_FAILURES; 1.2480 + errCount = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDERRORS, 0); 1.2481 + errCount++; 1.2482 + Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, errCount); 1.2483 + maxErrors = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS, 1.2484 + 10); 1.2485 + } 1.2486 + 1.2487 + var pingCode; 1.2488 + if (errCount >= maxErrors) { 1.2489 + var prompter = Cc["@mozilla.org/updates/update-prompt;1"]. 1.2490 + createInstance(Ci.nsIUpdatePrompt); 1.2491 + prompter.showUpdateError(update); 1.2492 + 1.2493 + switch (update.errorCode) { 1.2494 + case CERT_ATTR_CHECK_FAILED_NO_UPDATE: 1.2495 + pingCode = PING_BGUC_CERT_ATTR_NO_UPDATE_NOTIFY; 1.2496 + break; 1.2497 + case CERT_ATTR_CHECK_FAILED_HAS_UPDATE: 1.2498 + pingCode = PING_BGUC_CERT_ATTR_WITH_UPDATE_NOTIFY; 1.2499 + break; 1.2500 + default: 1.2501 + pingCode = PING_BGUC_GENERAL_ERROR_NOTIFY; 1.2502 + } 1.2503 + } 1.2504 + else { 1.2505 + switch (update.errorCode) { 1.2506 + case CERT_ATTR_CHECK_FAILED_NO_UPDATE: 1.2507 + pingCode = PING_BGUC_CERT_ATTR_NO_UPDATE_SILENT; 1.2508 + break; 1.2509 + case CERT_ATTR_CHECK_FAILED_HAS_UPDATE: 1.2510 + pingCode = PING_BGUC_CERT_ATTR_WITH_UPDATE_SILENT; 1.2511 + break; 1.2512 + default: 1.2513 + pingCode = PING_BGUC_GENERAL_ERROR_SILENT; 1.2514 + } 1.2515 + } 1.2516 + this._backgroundUpdateCheckCodePing(pingCode); 1.2517 + }, 1.2518 + 1.2519 + /** 1.2520 + * Called when a connection should be resumed 1.2521 + */ 1.2522 + _attemptResume: function AUS_attemptResume() { 1.2523 + LOG("UpdateService:_attemptResume") 1.2524 + // If a download is in progress, then resume it. 1.2525 + if (this._downloader && this._downloader._patch && 1.2526 + this._downloader._patch.state == STATE_DOWNLOADING && 1.2527 + this._downloader._update) { 1.2528 + LOG("UpdateService:_attemptResume - _patch.state: " + 1.2529 + this._downloader._patch.state); 1.2530 + // Make sure downloading is the state for selectPatch to work correctly 1.2531 + writeStatusFile(getUpdatesDir(), STATE_DOWNLOADING); 1.2532 + var status = this.downloadUpdate(this._downloader._update, 1.2533 + this._downloader.background); 1.2534 + LOG("UpdateService:_attemptResume - downloadUpdate status: " + status); 1.2535 + if (status == STATE_NONE) { 1.2536 + cleanupActiveUpdate(); 1.2537 + } 1.2538 + return; 1.2539 + } 1.2540 + 1.2541 + this.backgroundChecker.checkForUpdates(this, false); 1.2542 + }, 1.2543 + 1.2544 + /** 1.2545 + * Notified when a timer fires 1.2546 + * @param timer 1.2547 + * The timer that fired 1.2548 + */ 1.2549 + notify: function AUS_notify(timer) { 1.2550 + // The telemetry below is specific to background notification. 1.2551 + this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_ENABLED, 1.2552 + "UPDATER_UPDATES_ENABLED"); 1.2553 + this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_METRO_ENABLED, 1.2554 + "UPDATER_UPDATES_METRO_ENABLED"); 1.2555 + this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_AUTO, 1.2556 + "UPDATER_UPDATES_AUTOMATIC"); 1.2557 + this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_STAGING_ENABLED, 1.2558 + "UPDATER_STAGE_ENABLED"); 1.2559 + 1.2560 +#ifdef XP_WIN 1.2561 + this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ENABLED, 1.2562 + "UPDATER_SERVICE_ENABLED"); 1.2563 + this._sendIntPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ERRORS, 1.2564 + "UPDATER_SERVICE_ERRORS"); 1.2565 + this._sendServiceInstalledTelemetryPing(); 1.2566 +#endif 1.2567 + 1.2568 + this._checkForBackgroundUpdates(true); 1.2569 + }, 1.2570 + 1.2571 + /** 1.2572 + * See nsIUpdateService.idl 1.2573 + */ 1.2574 + checkForBackgroundUpdates: function AUS_checkForBackgroundUpdates() { 1.2575 + this._checkForBackgroundUpdates(false); 1.2576 + }, 1.2577 + 1.2578 + /** 1.2579 + * Checks for updates in the background. 1.2580 + * @param isNotify 1.2581 + * Whether or not a background update check was initiated by the 1.2582 + * application update timer notification. 1.2583 + */ 1.2584 + _checkForBackgroundUpdates: function AUS__checkForBackgroundUpdates(isNotify) { 1.2585 + this._isNotify = isNotify; 1.2586 + // From this point on, the telemetry reported differentiates between a call 1.2587 + // to notify and a call to checkForBackgroundUpdates so they are reported 1.2588 + // separately. 1.2589 + this._sendLastNotifyIntervalPing(); 1.2590 + 1.2591 + // If a download is in progress or the patch has been staged do nothing. 1.2592 + if (this.isDownloading) { 1.2593 + this._backgroundUpdateCheckCodePing(PING_BGUC_IS_DOWNLOADING); 1.2594 + return; 1.2595 + } 1.2596 + 1.2597 + if (this._downloader && this._downloader.patchIsStaged) { 1.2598 + this._backgroundUpdateCheckCodePing(PING_BGUC_IS_STAGED); 1.2599 + return; 1.2600 + } 1.2601 + 1.2602 + // The following checks will return early without notification in the call 1.2603 + // to checkForUpdates below. To simplify the background update check ping 1.2604 + // their values are checked here. 1.2605 + try { 1.2606 + if (!this.backgroundChecker.getUpdateURL(false)) { 1.2607 + let prefs = Services.prefs; 1.2608 + if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE)) { 1.2609 + if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL)) { 1.2610 + this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_DEFAULT_URL); 1.2611 + } 1.2612 + else { 1.2613 + this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_CUSTOM_URL); 1.2614 + } 1.2615 + } 1.2616 + else { 1.2617 + this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_OVERRIDE_URL); 1.2618 + } 1.2619 + } 1.2620 + else if (!gMetroUpdatesEnabled) { 1.2621 + this._backgroundUpdateCheckCodePing(PING_BGUC_METRO_DISABLED); 1.2622 + } 1.2623 + else if (!getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true)) { 1.2624 + this._backgroundUpdateCheckCodePing(PING_BGUC_PREF_DISABLED); 1.2625 + } 1.2626 + else if (!(gCanCheckForUpdates && hasUpdateMutex())) { 1.2627 + this._backgroundUpdateCheckCodePing(PING_BGUC_UNABLE_TO_CHECK); 1.2628 + } 1.2629 + else if (!this.backgroundChecker._enabled) { 1.2630 + this._backgroundUpdateCheckCodePing(PING_BGUC_DISABLED_FOR_SESSION); 1.2631 + } 1.2632 + } 1.2633 + catch (e) { 1.2634 + Cu.reportError(e); 1.2635 + } 1.2636 + 1.2637 + this.backgroundChecker.checkForUpdates(this, false); 1.2638 + }, 1.2639 + 1.2640 + /** 1.2641 + * Determine the update from the specified updates that should be offered. 1.2642 + * If both valid major and minor updates are available the minor update will 1.2643 + * be offered. 1.2644 + * @param updates 1.2645 + * An array of available nsIUpdate items 1.2646 + * @return The nsIUpdate to offer. 1.2647 + */ 1.2648 + selectUpdate: function AUS_selectUpdate(updates) { 1.2649 + if (updates.length == 0) { 1.2650 + this._backgroundUpdateCheckCodePing(PING_BGUC_NO_UPDATE_FOUND); 1.2651 + return null; 1.2652 + } 1.2653 + 1.2654 + // The ping for unsupported is sent after the call to showPrompt. 1.2655 + if (updates.length == 1 && updates[0].unsupported) 1.2656 + return updates[0]; 1.2657 + 1.2658 + // Choose the newest of the available minor and major updates. 1.2659 + var majorUpdate = null; 1.2660 + var minorUpdate = null; 1.2661 + var vc = Services.vc; 1.2662 + var lastPingCode = PING_BGUC_NO_COMPAT_UPDATE_FOUND; 1.2663 + 1.2664 + updates.forEach(function(aUpdate) { 1.2665 + // Ignore updates for older versions of the application and updates for 1.2666 + // the same version of the application with the same build ID. 1.2667 +#ifdef TOR_BROWSER_UPDATE 1.2668 + var compatVersion = TOR_BROWSER_VERSION; 1.2669 +#else 1.2670 + var compatVersion = Services.appinfo.version; 1.2671 +#endif 1.2672 + var rc = vc.compare(aUpdate.appVersion, compatVersion); 1.2673 + if (rc < 0 || ((rc == 0) && 1.2674 + (aUpdate.buildID == Services.appinfo.appBuildID))) { 1.2675 + LOG("UpdateService:selectUpdate - skipping update because the " + 1.2676 + "update's application version is less than the current " + 1.2677 + "application version"); 1.2678 + lastPingCode = PING_BGUC_UPDATE_PREVIOUS_VERSION; 1.2679 + return; 1.2680 + } 1.2681 + 1.2682 + // Skip the update if the user responded with "never" to this update's 1.2683 + // application version and the update specifies showNeverForVersion 1.2684 + // (see bug 350636). 1.2685 + let neverPrefName = PREF_APP_UPDATE_NEVER_BRANCH + aUpdate.appVersion; 1.2686 + if (aUpdate.showNeverForVersion && 1.2687 + getPref("getBoolPref", neverPrefName, false)) { 1.2688 + LOG("UpdateService:selectUpdate - skipping update because the " + 1.2689 + "preference " + neverPrefName + " is true"); 1.2690 + lastPingCode = PING_BGUC_UPDATE_NEVER_PREF; 1.2691 + return; 1.2692 + } 1.2693 + 1.2694 + switch (aUpdate.type) { 1.2695 + case "major": 1.2696 + if (!majorUpdate) 1.2697 + majorUpdate = aUpdate; 1.2698 + else if (vc.compare(majorUpdate.appVersion, aUpdate.appVersion) <= 0) 1.2699 + majorUpdate = aUpdate; 1.2700 + break; 1.2701 + case "minor": 1.2702 + if (!minorUpdate) 1.2703 + minorUpdate = aUpdate; 1.2704 + else if (vc.compare(minorUpdate.appVersion, aUpdate.appVersion) <= 0) 1.2705 + minorUpdate = aUpdate; 1.2706 + break; 1.2707 + default: 1.2708 + LOG("UpdateService:selectUpdate - skipping unknown update type: " + 1.2709 + aUpdate.type); 1.2710 + lastPingCode = PING_BGUC_UPDATE_INVALID_TYPE; 1.2711 + break; 1.2712 + } 1.2713 + }); 1.2714 + 1.2715 + var update = minorUpdate || majorUpdate; 1.2716 + if (!update) 1.2717 + this._backgroundUpdateCheckCodePing(lastPingCode); 1.2718 + 1.2719 + return update; 1.2720 + }, 1.2721 + 1.2722 + /** 1.2723 + * Reference to the currently selected update for when add-on compatibility 1.2724 + * is checked. 1.2725 + */ 1.2726 + _update: null, 1.2727 + 1.2728 + /** 1.2729 + * Determine which of the specified updates should be installed and begin the 1.2730 + * download/installation process or notify the user about the update. 1.2731 + * @param updates 1.2732 + * An array of available updates 1.2733 + */ 1.2734 + _selectAndInstallUpdate: function AUS__selectAndInstallUpdate(updates) { 1.2735 + // Return early if there's an active update. The user is already aware and 1.2736 + // is downloading or performed some user action to prevent notification. 1.2737 + var um = Cc["@mozilla.org/updates/update-manager;1"]. 1.2738 + getService(Ci.nsIUpdateManager); 1.2739 + if (um.activeUpdate) { 1.2740 +#ifdef MOZ_WIDGET_GONK 1.2741 + // For gonk, the user isn't necessarily aware of the update, so we need 1.2742 + // to show the prompt to make sure. 1.2743 + this._showPrompt(um.activeUpdate); 1.2744 +#endif 1.2745 + this._backgroundUpdateCheckCodePing(PING_BGUC_HAS_ACTIVEUPDATE); 1.2746 + return; 1.2747 + } 1.2748 + 1.2749 + var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true); 1.2750 + if (!updateEnabled) { 1.2751 + this._backgroundUpdateCheckCodePing(PING_BGUC_PREF_DISABLED); 1.2752 + LOG("UpdateService:_selectAndInstallUpdate - not prompting because " + 1.2753 + "update is disabled"); 1.2754 + return; 1.2755 + } 1.2756 + 1.2757 + if (!gMetroUpdatesEnabled) { 1.2758 + this._backgroundUpdateCheckCodePing(PING_BGUC_METRO_DISABLED); 1.2759 + return; 1.2760 + } 1.2761 + 1.2762 + var update = this.selectUpdate(updates, updates.length); 1.2763 + if (!update) { 1.2764 + return; 1.2765 + } 1.2766 + 1.2767 + if (update.unsupported) { 1.2768 + LOG("UpdateService:_selectAndInstallUpdate - update not supported for " + 1.2769 + "this system"); 1.2770 + if (!getPref("getBoolPref", PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, false)) { 1.2771 + LOG("UpdateService:_selectAndInstallUpdate - notifying that the " + 1.2772 + "update is not supported for this system"); 1.2773 + this._showPrompt(update); 1.2774 + } 1.2775 + this._backgroundUpdateCheckCodePing(PING_BGUC_UNSUPPORTED); 1.2776 + return; 1.2777 + } 1.2778 + 1.2779 + if (!(gCanApplyUpdates && hasUpdateMutex())) { 1.2780 + LOG("UpdateService:_selectAndInstallUpdate - the user is unable to " + 1.2781 + "apply updates... prompting"); 1.2782 + this._showPrompt(update); 1.2783 + this._backgroundUpdateCheckCodePing(PING_BGUC_UNABLE_TO_APPLY); 1.2784 + return; 1.2785 + } 1.2786 + 1.2787 + /** 1.2788 +# From this point on there are two possible outcomes: 1.2789 +# 1. download and install the update automatically 1.2790 +# 2. notify the user about the availability of an update 1.2791 +# 1.2792 +# Notes: 1.2793 +# a) if the app.update.auto preference is false then automatic download and 1.2794 +# install is disabled and the user will be notified. 1.2795 +# b) if the update has a showPrompt attribute the user will be notified. 1.2796 +# c) Mode is determined by the value of the app.update.mode preference. 1.2797 +# 1.2798 +# If the update when it is first read has an appVersion attribute the 1.2799 +# following behavior implemented in bug 530872 will occur: 1.2800 +# Mode Incompatible Add-ons Outcome 1.2801 +# 0 N/A Auto Install 1.2802 +# 1 Yes Notify 1.2803 +# 1 No Auto Install 1.2804 +# 1.2805 +# If the update when it is first read does not have an appVersion attribute 1.2806 +# the following deprecated behavior will occur: 1.2807 +# Update Type Mode Incompatible Add-ons Outcome 1.2808 +# Major all N/A Notify 1.2809 +# Minor 0 N/A Auto Install 1.2810 +# Minor 1 Yes Notify 1.2811 +# Minor 1 No Auto Install 1.2812 + */ 1.2813 + if (update.showPrompt) { 1.2814 + LOG("UpdateService:_selectAndInstallUpdate - prompting because the " + 1.2815 + "update snippet specified showPrompt"); 1.2816 + this._showPrompt(update); 1.2817 + if (!Services.metro || !Services.metro.immersive) { 1.2818 + this._backgroundUpdateCheckCodePing(PING_BGUC_SHOWPROMPT_SNIPPET); 1.2819 + return; 1.2820 + } 1.2821 + } 1.2822 + 1.2823 + if (!getPref("getBoolPref", PREF_APP_UPDATE_AUTO, true)) { 1.2824 + LOG("UpdateService:_selectAndInstallUpdate - prompting because silent " + 1.2825 + "install is disabled"); 1.2826 + this._showPrompt(update); 1.2827 + if (!Services.metro || !Services.metro.immersive) { 1.2828 + this._backgroundUpdateCheckCodePing(PING_BGUC_SHOWPROMPT_PREF); 1.2829 + return; 1.2830 + } 1.2831 + } 1.2832 + 1.2833 + if (getPref("getIntPref", PREF_APP_UPDATE_MODE, 1) == 0) { 1.2834 + // Do not prompt regardless of add-on incompatibilities 1.2835 + LOG("UpdateService:_selectAndInstallUpdate - add-on compatibility " + 1.2836 + "check disabled by preference, just download the update"); 1.2837 + var status = this.downloadUpdate(update, true); 1.2838 + if (status == STATE_NONE) 1.2839 + cleanupActiveUpdate(); 1.2840 + this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_PREF_DISABLED); 1.2841 + return; 1.2842 + } 1.2843 + 1.2844 + // Only check add-on compatibility when the version changes. 1.2845 +#ifdef TOR_BROWSER_UPDATE 1.2846 + var compatVersion = TOR_BROWSER_VERSION; 1.2847 +#else 1.2848 + var compatVersion = Services.appinfo.version; 1.2849 +#endif 1.2850 + if (update.appVersion && 1.2851 + Services.vc.compare(update.appVersion, compatVersion) != 0) { 1.2852 + this._update = update; 1.2853 + this._checkAddonCompatibility(); 1.2854 + } 1.2855 + else { 1.2856 + LOG("UpdateService:_selectAndInstallUpdate - add-on compatibility " + 1.2857 + "check not performed due to the update version being the same as " + 1.2858 + "the current application version, just download the update"); 1.2859 + var status = this.downloadUpdate(update, true); 1.2860 + if (status == STATE_NONE) 1.2861 + cleanupActiveUpdate(); 1.2862 + this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_SAME_APP_VER); 1.2863 + } 1.2864 + }, 1.2865 + 1.2866 + _showPrompt: function AUS__showPrompt(update) { 1.2867 + var prompter = Cc["@mozilla.org/updates/update-prompt;1"]. 1.2868 + createInstance(Ci.nsIUpdatePrompt); 1.2869 + prompter.showUpdateAvailable(update); 1.2870 + }, 1.2871 + 1.2872 + _checkAddonCompatibility: function AUS__checkAddonCompatibility() { 1.2873 + try { 1.2874 + var hotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); 1.2875 + } 1.2876 + catch (e) { } 1.2877 + 1.2878 + // Get all the installed add-ons 1.2879 + var self = this; 1.2880 + AddonManager.getAllAddons(function(addons) { 1.2881 +#ifdef TOR_BROWSER_UPDATE 1.2882 + let compatVersion = self._update.platformVersion; 1.2883 +#else 1.2884 + let compatVersion = self._update.appVersion; 1.2885 +#endif 1.2886 + self._incompatibleAddons = []; 1.2887 + addons.forEach(function(addon) { 1.2888 + // Protect against code that overrides the add-ons manager and doesn't 1.2889 + // implement the isCompatibleWith or the findUpdates method. 1.2890 + if (!("isCompatibleWith" in addon) || !("findUpdates" in addon)) { 1.2891 + let errMsg = "Add-on doesn't implement either the isCompatibleWith " + 1.2892 + "or the findUpdates method!"; 1.2893 + if (addon.id) 1.2894 + errMsg += " Add-on ID: " + addon.id; 1.2895 + Cu.reportError(errMsg); 1.2896 + return; 1.2897 + } 1.2898 + 1.2899 + // If an add-on isn't appDisabled and isn't userDisabled then it is 1.2900 + // either active now or the user expects it to be active after the 1.2901 + // restart. If that is the case and the add-on is not installed by the 1.2902 + // application and is not compatible with the new application version 1.2903 + // then the user should be warned that the add-on will become 1.2904 + // incompatible. If an addon's type equals plugin it is skipped since 1.2905 + // checking plugins compatibility information isn't supported and 1.2906 + // getting the scope property of a plugin breaks in some environments 1.2907 + // (see bug 566787). The hotfix add-on is also ignored as it shouldn't 1.2908 + // block the user from upgrading. 1.2909 + try { 1.2910 + if (addon.type != "plugin" && addon.id != hotfixID && 1.2911 + !addon.appDisabled && !addon.userDisabled && 1.2912 + addon.scope != AddonManager.SCOPE_APPLICATION && 1.2913 + addon.isCompatible && 1.2914 + !addon.isCompatibleWith(compatVersion, 1.2915 + self._update.platformVersion)) 1.2916 + self._incompatibleAddons.push(addon); 1.2917 + } 1.2918 + catch (e) { 1.2919 + Cu.reportError(e); 1.2920 + } 1.2921 + }); 1.2922 + 1.2923 + if (self._incompatibleAddons.length > 0) { 1.2924 + /** 1.2925 +# PREF_APP_UPDATE_INCOMPATIBLE_MODE 1.2926 +# Controls the mode in which we check for updates as follows. 1.2927 +# 1.2928 +# PREF_APP_UPDATE_INCOMPATIBLE_MODE != 1 1.2929 +# We check for VersionInfo _and_ NewerVersion updates for the 1.2930 +# incompatible add-ons - i.e. if Foo 1.2 is installed and it is 1.2931 +# incompatible with the update, and we find Foo 2.0 which is but has 1.2932 +# not been installed, then we do NOT prompt because the user can 1.2933 +# download Foo 2.0 when they restart after the update during the add-on 1.2934 +# mismatch checking UI. This is the default, since it suppresses most 1.2935 +# prompt dialogs. 1.2936 +# 1.2937 +# PREF_APP_UPDATE_INCOMPATIBLE_MODE == 1 1.2938 +# We check for VersionInfo updates for the incompatible add-ons - i.e. 1.2939 +# if the situation above with Foo 1.2 and available update to 2.0 1.2940 +# applies, we DO show the prompt since a download operation will be 1.2941 +# required after the update. This is not the default and is supplied 1.2942 +# only as a hidden option for those that want it. 1.2943 + */ 1.2944 + self._updateCheckCount = self._incompatibleAddons.length; 1.2945 + LOG("UpdateService:_checkAddonCompatibility - checking for " + 1.2946 + "incompatible add-ons"); 1.2947 + 1.2948 + self._incompatibleAddons.forEach(function(addon) { 1.2949 + addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, 1.2950 + compatVersion, this._update.platformVersion); 1.2951 + }, self); 1.2952 + } 1.2953 + else { 1.2954 + LOG("UpdateService:_checkAddonCompatibility - no incompatible " + 1.2955 + "add-ons found, just download the update"); 1.2956 + var status = self.downloadUpdate(self._update, true); 1.2957 + if (status == STATE_NONE) 1.2958 + cleanupActiveUpdate(); 1.2959 + self._update = null; 1.2960 + this._backgroundUpdateCheckCodePing(PING_BGUC_CHECK_NO_INCOMPAT); 1.2961 + } 1.2962 + }); 1.2963 + }, 1.2964 + 1.2965 + // AddonUpdateListener 1.2966 + onCompatibilityUpdateAvailable: function(addon) { 1.2967 + // Remove the add-on from the list of add-ons that will become incompatible 1.2968 + // with the new version of the application. 1.2969 + for (var i = 0; i < this._incompatibleAddons.length; ++i) { 1.2970 + if (this._incompatibleAddons[i].id == addon.id) { 1.2971 + LOG("UpdateService:onCompatibilityUpdateAvailable - found update for " + 1.2972 + "add-on ID: " + addon.id); 1.2973 + this._incompatibleAddons.splice(i, 1); 1.2974 + } 1.2975 + } 1.2976 + }, 1.2977 + 1.2978 + onUpdateAvailable: function(addon, install) { 1.2979 + if (getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE, 0) == 1) 1.2980 + return; 1.2981 + 1.2982 + // If the new version of this add-on is blocklisted for the new application 1.2983 + // then it isn't a valid update and the user should still be warned that 1.2984 + // the add-on will become incompatible. 1.2985 + let bs = Cc["@mozilla.org/extensions/blocklist;1"]. 1.2986 + getService(Ci.nsIBlocklistService); 1.2987 +#ifdef TOR_BROWSER_UPDATE 1.2988 + let compatVersion = gUpdates.update.platformVersion; 1.2989 +#else 1.2990 + let compatVersion = gUpdates.update.appVersion; 1.2991 +#endif 1.2992 + if (bs.isAddonBlocklisted(addon, 1.2993 + compatVersion, 1.2994 + gUpdates.update.platformVersion)) 1.2995 + return; 1.2996 + 1.2997 + // Compatibility or new version updates mean the same thing here. 1.2998 + this.onCompatibilityUpdateAvailable(addon); 1.2999 + }, 1.3000 + 1.3001 + onUpdateFinished: function(addon) { 1.3002 + if (--this._updateCheckCount > 0) 1.3003 + return; 1.3004 + 1.3005 + if (this._incompatibleAddons.length > 0 || 1.3006 + !(gCanApplyUpdates && hasUpdateMutex())) { 1.3007 + LOG("UpdateService:onUpdateEnded - prompting because there are " + 1.3008 + "incompatible add-ons"); 1.3009 + this._showPrompt(this._update); 1.3010 + this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_HAVE_INCOMPAT); 1.3011 + } 1.3012 + else { 1.3013 + LOG("UpdateService:_selectAndInstallUpdate - updates for all " + 1.3014 + "incompatible add-ons found, just download the update"); 1.3015 + var status = this.downloadUpdate(this._update, true); 1.3016 + if (status == STATE_NONE) 1.3017 + cleanupActiveUpdate(); 1.3018 + this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_UPDATES_FOR_INCOMPAT); 1.3019 + } 1.3020 + this._update = null; 1.3021 + }, 1.3022 + 1.3023 + /** 1.3024 + * The Checker used for background update checks. 1.3025 + */ 1.3026 + _backgroundChecker: null, 1.3027 + 1.3028 + /** 1.3029 + * See nsIUpdateService.idl 1.3030 + */ 1.3031 + get backgroundChecker() { 1.3032 + if (!this._backgroundChecker) 1.3033 + this._backgroundChecker = new Checker(); 1.3034 + return this._backgroundChecker; 1.3035 + }, 1.3036 + 1.3037 + /** 1.3038 + * See nsIUpdateService.idl 1.3039 + */ 1.3040 + get canCheckForUpdates() { 1.3041 + return gCanCheckForUpdates && hasUpdateMutex(); 1.3042 + }, 1.3043 + 1.3044 + /** 1.3045 + * See nsIUpdateService.idl 1.3046 + */ 1.3047 + get canApplyUpdates() { 1.3048 + return gCanApplyUpdates && hasUpdateMutex(); 1.3049 + }, 1.3050 + 1.3051 + /** 1.3052 + * See nsIUpdateService.idl 1.3053 + */ 1.3054 + get canStageUpdates() { 1.3055 + return getCanStageUpdates(); 1.3056 + }, 1.3057 + 1.3058 + /** 1.3059 + * See nsIUpdateService.idl 1.3060 + */ 1.3061 + get isOtherInstanceHandlingUpdates() { 1.3062 + return !hasUpdateMutex(); 1.3063 + }, 1.3064 + 1.3065 + 1.3066 + /** 1.3067 + * See nsIUpdateService.idl 1.3068 + */ 1.3069 + addDownloadListener: function AUS_addDownloadListener(listener) { 1.3070 + if (!this._downloader) { 1.3071 + LOG("UpdateService:addDownloadListener - no downloader!"); 1.3072 + return; 1.3073 + } 1.3074 + this._downloader.addDownloadListener(listener); 1.3075 + }, 1.3076 + 1.3077 + /** 1.3078 + * See nsIUpdateService.idl 1.3079 + */ 1.3080 + removeDownloadListener: function AUS_removeDownloadListener(listener) { 1.3081 + if (!this._downloader) { 1.3082 + LOG("UpdateService:removeDownloadListener - no downloader!"); 1.3083 + return; 1.3084 + } 1.3085 + this._downloader.removeDownloadListener(listener); 1.3086 + }, 1.3087 + 1.3088 + /** 1.3089 + * See nsIUpdateService.idl 1.3090 + */ 1.3091 + downloadUpdate: function AUS_downloadUpdate(update, background) { 1.3092 + if (!update) 1.3093 + throw Cr.NS_ERROR_NULL_POINTER; 1.3094 + 1.3095 + // Don't download the update if the update's version is less than the 1.3096 + // current application's version or the update's version is the same as the 1.3097 + // application's version and the build ID is the same as the application's 1.3098 + // build ID. 1.3099 +#ifdef TOR_BROWSER_UPDATE 1.3100 + var compatVersion = TOR_BROWSER_VERSION; 1.3101 +#else 1.3102 + var compatVersion = Services.appinfo.version; 1.3103 +#endif 1.3104 + if (update.appVersion && 1.3105 + (Services.vc.compare(update.appVersion, compatVersion) < 0 || 1.3106 + update.buildID && update.buildID == Services.appinfo.appBuildID && 1.3107 + update.appVersion == compatVersion)) { 1.3108 + LOG("UpdateService:downloadUpdate - canceling download of update since " + 1.3109 + "it is for an earlier or same application version and build ID.\n" + 1.3110 +#ifdef TOR_BROWSER_UPDATE 1.3111 + "current Tor Browser version: " + compatVersion + "\n" + 1.3112 + "update Tor Browser version : " + update.appVersion + "\n" + 1.3113 +#else 1.3114 + "current application version: " + compatVersion + "\n" + 1.3115 + "update application version : " + update.appVersion + "\n" + 1.3116 +#endif 1.3117 + "current build ID: " + Services.appinfo.appBuildID + "\n" + 1.3118 + "update build ID : " + update.buildID); 1.3119 + cleanupActiveUpdate(); 1.3120 + return STATE_NONE; 1.3121 + } 1.3122 + 1.3123 + // If a download request is in progress vs. a download ready to resume 1.3124 + if (this.isDownloading) { 1.3125 + if (update.isCompleteUpdate == this._downloader.isCompleteUpdate && 1.3126 + background == this._downloader.background) { 1.3127 + LOG("UpdateService:downloadUpdate - no support for downloading more " + 1.3128 + "than one update at a time"); 1.3129 + return readStatusFile(getUpdatesDir()); 1.3130 + } 1.3131 + this._downloader.cancel(); 1.3132 + } 1.3133 +#ifdef MOZ_WIDGET_GONK 1.3134 + var um = Cc["@mozilla.org/updates/update-manager;1"]. 1.3135 + getService(Ci.nsIUpdateManager); 1.3136 + var activeUpdate = um.activeUpdate; 1.3137 + if (activeUpdate && 1.3138 + (activeUpdate.appVersion != update.appVersion || 1.3139 + activeUpdate.buildID != update.buildID)) { 1.3140 + // We have an activeUpdate (which presumably was interrupted), and are 1.3141 + // about start downloading a new one. Make sure we remove all traces 1.3142 + // of the active one (otherwise we'll start appending the new update.mar 1.3143 + // the the one that's been partially downloaded). 1.3144 + LOG("UpdateService:downloadUpdate - removing stale active update."); 1.3145 + cleanupActiveUpdate(); 1.3146 + } 1.3147 +#endif 1.3148 + // Set the previous application version prior to downloading the update. 1.3149 + update.previousAppVersion = compatVersion; 1.3150 + this._downloader = new Downloader(background, this); 1.3151 + return this._downloader.downloadUpdate(update); 1.3152 + }, 1.3153 + 1.3154 + /** 1.3155 + * See nsIUpdateService.idl 1.3156 + */ 1.3157 + pauseDownload: function AUS_pauseDownload() { 1.3158 + if (this.isDownloading) { 1.3159 + this._downloader.cancel(); 1.3160 + } else if (this._retryTimer) { 1.3161 + // Download status is still consider as 'downloading' during retry. 1.3162 + // We need to cancel both retry and download at this stage. 1.3163 + this._retryTimer.cancel(); 1.3164 + this._retryTimer = null; 1.3165 + this._downloader.cancel(); 1.3166 + } 1.3167 + }, 1.3168 + 1.3169 + /** 1.3170 + * See nsIUpdateService.idl 1.3171 + */ 1.3172 + getUpdatesDirectory: getUpdatesDir, 1.3173 + 1.3174 + /** 1.3175 + * See nsIUpdateService.idl 1.3176 + */ 1.3177 + get isDownloading() { 1.3178 + return this._downloader && this._downloader.isBusy; 1.3179 + }, 1.3180 + 1.3181 + /** 1.3182 + * See nsIUpdateService.idl 1.3183 + */ 1.3184 + applyOsUpdate: function AUS_applyOsUpdate(aUpdate) { 1.3185 + if (!aUpdate.isOSUpdate || aUpdate.state != STATE_APPLIED) { 1.3186 + aUpdate.statusText = "fota-state-error"; 1.3187 + throw Cr.NS_ERROR_FAILURE; 1.3188 + } 1.3189 + 1.3190 + let osApplyToDir; 1.3191 + try { 1.3192 + aUpdate.QueryInterface(Ci.nsIWritablePropertyBag); 1.3193 + osApplyToDir = aUpdate.getProperty("osApplyToDir"); 1.3194 + } catch (e) {} 1.3195 + 1.3196 + if (!osApplyToDir) { 1.3197 + LOG("UpdateService:applyOsUpdate - Error: osApplyToDir is not defined" + 1.3198 + "in the nsIUpdate!"); 1.3199 + handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR); 1.3200 + return; 1.3201 + } 1.3202 + 1.3203 + let updateFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); 1.3204 + updateFile.initWithPath(osApplyToDir + "/update.zip"); 1.3205 + if (!updateFile.exists()) { 1.3206 + LOG("UpdateService:applyOsUpdate - Error: OS update is not found at " + 1.3207 + updateFile.path); 1.3208 + handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR); 1.3209 + return; 1.3210 + } 1.3211 + 1.3212 + writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED_OS); 1.3213 + LOG("UpdateService:applyOsUpdate - Rebooting into recovery to apply " + 1.3214 + "FOTA update: " + updateFile.path); 1.3215 + try { 1.3216 + let recoveryService = Cc["@mozilla.org/recovery-service;1"] 1.3217 + .getService(Ci.nsIRecoveryService); 1.3218 + recoveryService.installFotaUpdate(updateFile.path); 1.3219 + } catch (e) { 1.3220 + LOG("UpdateService:applyOsUpdate - Error: Couldn't reboot into recovery" + 1.3221 + " to apply FOTA update " + updateFile.path); 1.3222 + writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED); 1.3223 + handleUpdateFailure(aUpdate, FOTA_RECOVERY_ERROR); 1.3224 + } 1.3225 + }, 1.3226 + 1.3227 + classID: UPDATESERVICE_CID, 1.3228 + classInfo: XPCOMUtils.generateCI({classID: UPDATESERVICE_CID, 1.3229 + contractID: UPDATESERVICE_CONTRACTID, 1.3230 + interfaces: [Ci.nsIApplicationUpdateService, 1.3231 + Ci.nsITimerCallback, 1.3232 + Ci.nsIObserver], 1.3233 + flags: Ci.nsIClassInfo.SINGLETON}), 1.3234 + 1.3235 + _xpcom_factory: UpdateServiceFactory, 1.3236 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIApplicationUpdateService, 1.3237 + Ci.nsIUpdateCheckListener, 1.3238 + Ci.nsITimerCallback, 1.3239 + Ci.nsIObserver]) 1.3240 +}; 1.3241 + 1.3242 +/** 1.3243 + * A service to manage active and past updates. 1.3244 + * @constructor 1.3245 + */ 1.3246 +function UpdateManager() { 1.3247 + // Ensure the Active Update file is loaded 1.3248 + var updates = this._loadXMLFileIntoArray(getUpdateFile( 1.3249 + [FILE_UPDATE_ACTIVE])); 1.3250 + if (updates.length > 0) { 1.3251 + // Under some edgecases such as Windows system restore the active-update.xml 1.3252 + // will contain a pending update without the status file which will return 1.3253 + // STATE_NONE. To recover from this situation clean the updates dir and 1.3254 + // rewrite the active-update.xml file without the broken update. 1.3255 + if (readStatusFile(getUpdatesDir()) == STATE_NONE) { 1.3256 + cleanUpUpdatesDir(); 1.3257 + this._writeUpdatesToXMLFile([], getUpdateFile([FILE_UPDATE_ACTIVE])); 1.3258 + } 1.3259 + else 1.3260 + this._activeUpdate = updates[0]; 1.3261 + } 1.3262 +} 1.3263 +UpdateManager.prototype = { 1.3264 + /** 1.3265 + * All previously downloaded and installed updates, as an array of nsIUpdate 1.3266 + * objects. 1.3267 + */ 1.3268 + _updates: null, 1.3269 + 1.3270 + /** 1.3271 + * The current actively downloading/installing update, as a nsIUpdate object. 1.3272 + */ 1.3273 + _activeUpdate: null, 1.3274 + 1.3275 + /** 1.3276 + * Handle Observer Service notifications 1.3277 + * @param subject 1.3278 + * The subject of the notification 1.3279 + * @param topic 1.3280 + * The notification name 1.3281 + * @param data 1.3282 + * Additional data 1.3283 + */ 1.3284 + observe: function UM_observe(subject, topic, data) { 1.3285 + // Hack to be able to run and cleanup tests by reloading the update data. 1.3286 + if (topic == "um-reload-update-data") { 1.3287 + this._updates = this._loadXMLFileIntoArray(getUpdateFile( 1.3288 + [FILE_UPDATES_DB])); 1.3289 + this._activeUpdate = null; 1.3290 + var updates = this._loadXMLFileIntoArray(getUpdateFile( 1.3291 + [FILE_UPDATE_ACTIVE])); 1.3292 + if (updates.length > 0) 1.3293 + this._activeUpdate = updates[0]; 1.3294 + } 1.3295 + }, 1.3296 + 1.3297 + /** 1.3298 + * Loads an updates.xml formatted file into an array of nsIUpdate items. 1.3299 + * @param file 1.3300 + * A nsIFile for the updates.xml file 1.3301 + * @return The array of nsIUpdate items held in the file. 1.3302 + */ 1.3303 + _loadXMLFileIntoArray: function UM__loadXMLFileIntoArray(file) { 1.3304 + if (!file.exists()) { 1.3305 + LOG("UpdateManager:_loadXMLFileIntoArray: XML file does not exist"); 1.3306 + return []; 1.3307 + } 1.3308 + 1.3309 + var result = []; 1.3310 + var fileStream = Cc["@mozilla.org/network/file-input-stream;1"]. 1.3311 + createInstance(Ci.nsIFileInputStream); 1.3312 + fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); 1.3313 + try { 1.3314 + var parser = Cc["@mozilla.org/xmlextras/domparser;1"]. 1.3315 + createInstance(Ci.nsIDOMParser); 1.3316 + var doc = parser.parseFromStream(fileStream, "UTF-8", 1.3317 + fileStream.available(), "text/xml"); 1.3318 + 1.3319 + const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE; 1.3320 + var updateCount = doc.documentElement.childNodes.length; 1.3321 + for (var i = 0; i < updateCount; ++i) { 1.3322 + var updateElement = doc.documentElement.childNodes.item(i); 1.3323 + if (updateElement.nodeType != ELEMENT_NODE || 1.3324 + updateElement.localName != "update") 1.3325 + continue; 1.3326 + 1.3327 + updateElement.QueryInterface(Ci.nsIDOMElement); 1.3328 + try { 1.3329 + var update = new Update(updateElement); 1.3330 + } catch (e) { 1.3331 + LOG("UpdateManager:_loadXMLFileIntoArray - invalid update"); 1.3332 + continue; 1.3333 + } 1.3334 + result.push(update); 1.3335 + } 1.3336 + } 1.3337 + catch (e) { 1.3338 + LOG("UpdateManager:_loadXMLFileIntoArray - error constructing update " + 1.3339 + "list. Exception: " + e); 1.3340 + } 1.3341 + fileStream.close(); 1.3342 + return result; 1.3343 + }, 1.3344 + 1.3345 + /** 1.3346 + * Load the update manager, initializing state from state files. 1.3347 + */ 1.3348 + _ensureUpdates: function UM__ensureUpdates() { 1.3349 + if (!this._updates) { 1.3350 + this._updates = this._loadXMLFileIntoArray(getUpdateFile( 1.3351 + [FILE_UPDATES_DB])); 1.3352 + var activeUpdates = this._loadXMLFileIntoArray(getUpdateFile( 1.3353 + [FILE_UPDATE_ACTIVE])); 1.3354 + if (activeUpdates.length > 0) 1.3355 + this._activeUpdate = activeUpdates[0]; 1.3356 + } 1.3357 + }, 1.3358 + 1.3359 + /** 1.3360 + * See nsIUpdateService.idl 1.3361 + */ 1.3362 + getUpdateAt: function UM_getUpdateAt(index) { 1.3363 + this._ensureUpdates(); 1.3364 + return this._updates[index]; 1.3365 + }, 1.3366 + 1.3367 + /** 1.3368 + * See nsIUpdateService.idl 1.3369 + */ 1.3370 + get updateCount() { 1.3371 + this._ensureUpdates(); 1.3372 + return this._updates.length; 1.3373 + }, 1.3374 + 1.3375 + /** 1.3376 + * See nsIUpdateService.idl 1.3377 + */ 1.3378 + get activeUpdate() { 1.3379 + if (this._activeUpdate && 1.3380 + this._activeUpdate.channel != UpdateChannel.get()) { 1.3381 + LOG("UpdateManager:get activeUpdate - channel has changed, " + 1.3382 + "reloading default preferences to workaround bug 802022"); 1.3383 + // Workaround to get distribution preferences loaded (Bug 774618). This 1.3384 + // can be removed after bug 802022 is fixed. 1.3385 + let prefSvc = Services.prefs.QueryInterface(Ci.nsIObserver); 1.3386 + prefSvc.observe(null, "reload-default-prefs", null); 1.3387 + if (this._activeUpdate.channel != UpdateChannel.get()) { 1.3388 + // User switched channels, clear out any old active updates and remove 1.3389 + // partial downloads 1.3390 + this._activeUpdate = null; 1.3391 + this.saveUpdates(); 1.3392 + 1.3393 + // Destroy the updates directory, since we're done with it. 1.3394 + cleanUpUpdatesDir(); 1.3395 + } 1.3396 + } 1.3397 + return this._activeUpdate; 1.3398 + }, 1.3399 + set activeUpdate(activeUpdate) { 1.3400 + this._addUpdate(activeUpdate); 1.3401 + this._activeUpdate = activeUpdate; 1.3402 + if (!activeUpdate) { 1.3403 + // If |activeUpdate| is null, we have updated both lists - the active list 1.3404 + // and the history list, so we want to write both files. 1.3405 + this.saveUpdates(); 1.3406 + } 1.3407 + else 1.3408 + this._writeUpdatesToXMLFile([this._activeUpdate], 1.3409 + getUpdateFile([FILE_UPDATE_ACTIVE])); 1.3410 + return activeUpdate; 1.3411 + }, 1.3412 + 1.3413 + /** 1.3414 + * Add an update to the Updates list. If the item already exists in the list, 1.3415 + * replace the existing value with the new value. 1.3416 + * @param update 1.3417 + * The nsIUpdate object to add. 1.3418 + */ 1.3419 + _addUpdate: function UM__addUpdate(update) { 1.3420 + if (!update) 1.3421 + return; 1.3422 + this._ensureUpdates(); 1.3423 + if (this._updates) { 1.3424 + for (var i = 0; i < this._updates.length; ++i) { 1.3425 + if (this._updates[i] && 1.3426 + this._updates[i].appVersion == update.appVersion && 1.3427 + this._updates[i].buildID == update.buildID) { 1.3428 + // Replace the existing entry with the new value, updating 1.3429 + // all metadata. 1.3430 + this._updates[i] = update; 1.3431 + return; 1.3432 + } 1.3433 + } 1.3434 + } 1.3435 + // Otherwise add it to the front of the list. 1.3436 + this._updates.unshift(update); 1.3437 + }, 1.3438 + 1.3439 + /** 1.3440 + * Serializes an array of updates to an XML file 1.3441 + * @param updates 1.3442 + * An array of nsIUpdate objects 1.3443 + * @param file 1.3444 + * The nsIFile object to serialize to 1.3445 + */ 1.3446 + _writeUpdatesToXMLFile: function UM__writeUpdatesToXMLFile(updates, file) { 1.3447 + var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"]. 1.3448 + createInstance(Ci.nsIFileOutputStream); 1.3449 + var modeFlags = FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | 1.3450 + FileUtils.MODE_TRUNCATE; 1.3451 + if (!file.exists()) 1.3452 + file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); 1.3453 + fos.init(file, modeFlags, FileUtils.PERMS_FILE, 0); 1.3454 + 1.3455 + try { 1.3456 + var parser = Cc["@mozilla.org/xmlextras/domparser;1"]. 1.3457 + createInstance(Ci.nsIDOMParser); 1.3458 + const EMPTY_UPDATES_DOCUMENT = "<?xml version=\"1.0\"?><updates xmlns=\"http://www.mozilla.org/2005/app-update\"></updates>"; 1.3459 + var doc = parser.parseFromString(EMPTY_UPDATES_DOCUMENT, "text/xml"); 1.3460 + 1.3461 + for (var i = 0; i < updates.length; ++i) { 1.3462 + if (updates[i]) 1.3463 + doc.documentElement.appendChild(updates[i].serialize(doc)); 1.3464 + } 1.3465 + 1.3466 + var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"]. 1.3467 + createInstance(Ci.nsIDOMSerializer); 1.3468 + serializer.serializeToStream(doc.documentElement, fos, null); 1.3469 + } 1.3470 + catch (e) { 1.3471 + } 1.3472 + 1.3473 + FileUtils.closeSafeFileOutputStream(fos); 1.3474 + }, 1.3475 + 1.3476 + /** 1.3477 + * See nsIUpdateService.idl 1.3478 + */ 1.3479 + saveUpdates: function UM_saveUpdates() { 1.3480 + this._writeUpdatesToXMLFile([this._activeUpdate], 1.3481 + getUpdateFile([FILE_UPDATE_ACTIVE])); 1.3482 + if (this._activeUpdate) 1.3483 + this._addUpdate(this._activeUpdate); 1.3484 + 1.3485 + this._ensureUpdates(); 1.3486 + // Don't write updates that have a temporary state to the updates.xml file. 1.3487 + if (this._updates) { 1.3488 + let updates = this._updates.slice(); 1.3489 + for (let i = updates.length - 1; i >= 0; --i) { 1.3490 + let state = updates[i].state; 1.3491 + if (state == STATE_NONE || state == STATE_DOWNLOADING || 1.3492 + state == STATE_APPLIED || state == STATE_APPLIED_SVC || 1.3493 + state == STATE_PENDING || state == STATE_PENDING_SVC) { 1.3494 + updates.splice(i, 1); 1.3495 + } 1.3496 + } 1.3497 + 1.3498 + this._writeUpdatesToXMLFile(updates.slice(0, 10), 1.3499 + getUpdateFile([FILE_UPDATES_DB])); 1.3500 + } 1.3501 + }, 1.3502 + 1.3503 + refreshUpdateStatus: function UM_refreshUpdateStatus(update) { 1.3504 + var updateSucceeded = true; 1.3505 + var status = readStatusFile(getUpdatesDir()); 1.3506 + var ary = status.split(":"); 1.3507 + update.state = ary[0]; 1.3508 + if (update.state == STATE_FAILED && ary[1]) { 1.3509 + updateSucceeded = false; 1.3510 + if (!handleUpdateFailure(update, ary[1])) { 1.3511 + handleFallbackToCompleteUpdate(update, true); 1.3512 + } 1.3513 + } 1.3514 + if (update.state == STATE_APPLIED && shouldUseService()) { 1.3515 + writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SVC); 1.3516 + } 1.3517 + var um = Cc["@mozilla.org/updates/update-manager;1"]. 1.3518 + getService(Ci.nsIUpdateManager); 1.3519 + um.saveUpdates(); 1.3520 + 1.3521 + if (update.state != STATE_PENDING && update.state != STATE_PENDING_SVC) { 1.3522 + // Destroy the updates directory, since we're done with it. 1.3523 + // Make sure to not do this when the updater has fallen back to 1.3524 + // non-staged updates. 1.3525 + cleanUpUpdatesDir(updateSucceeded); 1.3526 + } 1.3527 + 1.3528 + // Send an observer notification which the update wizard uses in 1.3529 + // order to update its UI. 1.3530 + LOG("UpdateManager:refreshUpdateStatus - Notifying observers that " + 1.3531 + "the update was staged. state: " + update.state + ", status: " + status); 1.3532 + Services.obs.notifyObservers(null, "update-staged", update.state); 1.3533 + 1.3534 + // Do this after *everything* else, since it will likely cause the app 1.3535 + // to shut down. 1.3536 +#ifdef MOZ_WIDGET_GONK 1.3537 + if (update.state == STATE_APPLIED) { 1.3538 + // Notify the user that an update has been staged and is ready for 1.3539 + // installation (i.e. that they should restart the application). We do 1.3540 + // not notify on failed update attempts. 1.3541 + var prompter = Cc["@mozilla.org/updates/update-prompt;1"]. 1.3542 + createInstance(Ci.nsIUpdatePrompt); 1.3543 + prompter.showUpdateDownloaded(update, true); 1.3544 + } else { 1.3545 + releaseSDCardMountLock(); 1.3546 + } 1.3547 +#else 1.3548 + // Only prompt when the UI isn't already open. 1.3549 + let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null); 1.3550 + if (Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) || 1.3551 + windowType && Services.wm.getMostRecentWindow(windowType)) { 1.3552 + return; 1.3553 + } 1.3554 + 1.3555 + if (update.state == STATE_APPLIED || update.state == STATE_APPLIED_SVC || 1.3556 + update.state == STATE_PENDING || update.state == STATE_PENDING_SVC) { 1.3557 + // Notify the user that an update has been staged and is ready for 1.3558 + // installation (i.e. that they should restart the application). 1.3559 + var prompter = Cc["@mozilla.org/updates/update-prompt;1"]. 1.3560 + createInstance(Ci.nsIUpdatePrompt); 1.3561 + prompter.showUpdateDownloaded(update, true); 1.3562 + } 1.3563 +#endif 1.3564 + }, 1.3565 + 1.3566 + classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"), 1.3567 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager, Ci.nsIObserver]) 1.3568 +}; 1.3569 + 1.3570 +/** 1.3571 + * Checker 1.3572 + * Checks for new Updates 1.3573 + * @constructor 1.3574 + */ 1.3575 +function Checker() { 1.3576 +} 1.3577 +Checker.prototype = { 1.3578 + /** 1.3579 + * The XMLHttpRequest object that performs the connection. 1.3580 + */ 1.3581 + _request : null, 1.3582 + 1.3583 + /** 1.3584 + * The nsIUpdateCheckListener callback 1.3585 + */ 1.3586 + _callback : null, 1.3587 + 1.3588 + /** 1.3589 + * The URL of the update service XML file to connect to that contains details 1.3590 + * about available updates. 1.3591 + */ 1.3592 + getUpdateURL: function UC_getUpdateURL(force) { 1.3593 + this._forced = force; 1.3594 + 1.3595 + // Use the override URL if specified. 1.3596 + var url = getPref("getCharPref", PREF_APP_UPDATE_URL_OVERRIDE, null); 1.3597 + 1.3598 + // Otherwise, construct the update URL from component parts. 1.3599 + if (!url) { 1.3600 + try { 1.3601 + url = Services.prefs.getDefaultBranch(null). 1.3602 + getCharPref(PREF_APP_UPDATE_URL); 1.3603 + } catch (e) { 1.3604 + } 1.3605 + } 1.3606 + 1.3607 + if (!url || url == "") { 1.3608 + LOG("Checker:getUpdateURL - update URL not defined"); 1.3609 + return null; 1.3610 + } 1.3611 + 1.3612 + url = url.replace(/%PRODUCT%/g, Services.appinfo.name); 1.3613 +#ifdef TOR_BROWSER_UPDATE 1.3614 + url = url.replace(/%VERSION%/g, TOR_BROWSER_VERSION); 1.3615 +#else 1.3616 + url = url.replace(/%VERSION%/g, Services.appinfo.version); 1.3617 +#endif 1.3618 + url = url.replace(/%BUILD_ID%/g, Services.appinfo.appBuildID); 1.3619 + url = url.replace(/%BUILD_TARGET%/g, Services.appinfo.OS + "_" + gABI); 1.3620 + url = url.replace(/%OS_VERSION%/g, gOSVersion); 1.3621 + if (/%LOCALE%/.test(url)) 1.3622 + url = url.replace(/%LOCALE%/g, getLocale()); 1.3623 + url = url.replace(/%CHANNEL%/g, UpdateChannel.get()); 1.3624 + url = url.replace(/%PLATFORM_VERSION%/g, Services.appinfo.platformVersion); 1.3625 + url = url.replace(/%DISTRIBUTION%/g, 1.3626 + getDistributionPrefValue(PREF_APP_DISTRIBUTION)); 1.3627 + url = url.replace(/%DISTRIBUTION_VERSION%/g, 1.3628 + getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION)); 1.3629 + url = url.replace(/%CUSTOM%/g, getPref("getCharPref", PREF_APP_UPDATE_CUSTOM, "")); 1.3630 + url = url.replace(/\+/g, "%2B"); 1.3631 + 1.3632 +#ifdef MOZ_WIDGET_GONK 1.3633 + url = url.replace(/%PRODUCT_MODEL%/g, gProductModel); 1.3634 + url = url.replace(/%B2G_VERSION%/g, getPref("getCharPref", PREF_APP_B2G_VERSION, null)); 1.3635 +#endif 1.3636 + 1.3637 + if (force) 1.3638 + url += (url.indexOf("?") != -1 ? "&" : "?") + "force=1"; 1.3639 + 1.3640 + LOG("Checker:getUpdateURL - update URL: " + url); 1.3641 + return url; 1.3642 + }, 1.3643 + 1.3644 + /** 1.3645 + * See nsIUpdateService.idl 1.3646 + */ 1.3647 + checkForUpdates: function UC_checkForUpdates(listener, force) { 1.3648 + LOG("Checker: checkForUpdates, force: " + force); 1.3649 + if (!listener) 1.3650 + throw Cr.NS_ERROR_NULL_POINTER; 1.3651 + 1.3652 + Services.obs.notifyObservers(null, "update-check-start", null); 1.3653 + 1.3654 + var url = this.getUpdateURL(force); 1.3655 + if (!url || (!this.enabled && !force)) 1.3656 + return; 1.3657 + 1.3658 + this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. 1.3659 + createInstance(Ci.nsISupports); 1.3660 + // This is here to let unit test code override XHR 1.3661 + if (this._request.wrappedJSObject) { 1.3662 + this._request = this._request.wrappedJSObject; 1.3663 + } 1.3664 + this._request.open("GET", url, true); 1.3665 + var allowNonBuiltIn = !getPref("getBoolPref", 1.3666 + PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true); 1.3667 + this._request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(allowNonBuiltIn); 1.3668 + // Prevent the request from reading from the cache. 1.3669 + this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; 1.3670 + // Prevent the request from writing to the cache. 1.3671 + this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; 1.3672 + 1.3673 + this._request.overrideMimeType("text/xml"); 1.3674 + // The Cache-Control header is only interpreted by proxies and the 1.3675 + // final destination. It does not help if a resource is already 1.3676 + // cached locally. 1.3677 + this._request.setRequestHeader("Cache-Control", "no-cache"); 1.3678 + // HTTP/1.0 servers might not implement Cache-Control and 1.3679 + // might only implement Pragma: no-cache 1.3680 + this._request.setRequestHeader("Pragma", "no-cache"); 1.3681 + 1.3682 + var self = this; 1.3683 + this._request.addEventListener("error", function(event) { self.onError(event); } ,false); 1.3684 + this._request.addEventListener("load", function(event) { self.onLoad(event); }, false); 1.3685 + 1.3686 + LOG("Checker:checkForUpdates - sending request to: " + url); 1.3687 + this._request.send(null); 1.3688 + 1.3689 + this._callback = listener; 1.3690 + }, 1.3691 + 1.3692 + /** 1.3693 + * Returns an array of nsIUpdate objects discovered by the update check. 1.3694 + * @throws if the XML document element node name is not updates. 1.3695 + */ 1.3696 + get _updates() { 1.3697 + var updatesElement = this._request.responseXML.documentElement; 1.3698 + if (!updatesElement) { 1.3699 + LOG("Checker:_updates get - empty updates document?!"); 1.3700 + return []; 1.3701 + } 1.3702 + 1.3703 + if (updatesElement.nodeName != "updates") { 1.3704 + LOG("Checker:_updates get - unexpected node name!"); 1.3705 + throw new Error("Unexpected node name, expected: updates, got: " + 1.3706 + updatesElement.nodeName); 1.3707 + } 1.3708 + 1.3709 + const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE; 1.3710 + var updates = []; 1.3711 + for (var i = 0; i < updatesElement.childNodes.length; ++i) { 1.3712 + var updateElement = updatesElement.childNodes.item(i); 1.3713 + if (updateElement.nodeType != ELEMENT_NODE || 1.3714 + updateElement.localName != "update") 1.3715 + continue; 1.3716 + 1.3717 + updateElement.QueryInterface(Ci.nsIDOMElement); 1.3718 + try { 1.3719 + var update = new Update(updateElement); 1.3720 + } catch (e) { 1.3721 + LOG("Checker:_updates get - invalid <update/>, ignoring..."); 1.3722 + continue; 1.3723 + } 1.3724 + update.serviceURL = this.getUpdateURL(this._forced); 1.3725 + update.channel = UpdateChannel.get(); 1.3726 + updates.push(update); 1.3727 + } 1.3728 + 1.3729 + return updates; 1.3730 + }, 1.3731 + 1.3732 + /** 1.3733 + * Returns the status code for the XMLHttpRequest 1.3734 + */ 1.3735 + _getChannelStatus: function UC__getChannelStatus(request) { 1.3736 + var status = 0; 1.3737 + try { 1.3738 + status = request.status; 1.3739 + } 1.3740 + catch (e) { 1.3741 + } 1.3742 + 1.3743 + if (status == 0) 1.3744 + status = request.channel.QueryInterface(Ci.nsIRequest).status; 1.3745 + return status; 1.3746 + }, 1.3747 + 1.3748 + _isHttpStatusCode: function UC__isHttpStatusCode(status) { 1.3749 + return status >= 100 && status <= 599; 1.3750 + }, 1.3751 + 1.3752 + /** 1.3753 + * The XMLHttpRequest succeeded and the document was loaded. 1.3754 + * @param event 1.3755 + * The nsIDOMEvent for the load 1.3756 + */ 1.3757 + onLoad: function UC_onLoad(event) { 1.3758 + LOG("Checker:onLoad - request completed downloading document"); 1.3759 + 1.3760 + var prefs = Services.prefs; 1.3761 + var certs = null; 1.3762 + if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE) && 1.3763 + getPref("getBoolPref", PREF_APP_UPDATE_CERT_CHECKATTRS, true)) { 1.3764 + certs = gCertUtils.readCertPrefs(PREF_APP_UPDATE_CERTS_BRANCH); 1.3765 + } 1.3766 + 1.3767 + try { 1.3768 + // Analyze the resulting DOM and determine the set of updates. 1.3769 + var updates = this._updates; 1.3770 + LOG("Checker:onLoad - number of updates available: " + updates.length); 1.3771 + var allowNonBuiltIn = !getPref("getBoolPref", 1.3772 + PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true); 1.3773 + gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs); 1.3774 + 1.3775 + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CERT_ERRORS)) 1.3776 + Services.prefs.clearUserPref(PREF_APP_UPDATE_CERT_ERRORS); 1.3777 + 1.3778 + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) 1.3779 + Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS); 1.3780 + 1.3781 + // Tell the callback about the updates 1.3782 + this._callback.onCheckComplete(event.target, updates, updates.length); 1.3783 + } 1.3784 + catch (e) { 1.3785 + LOG("Checker:onLoad - there was a problem checking for updates. " + 1.3786 + "Exception: " + e); 1.3787 + var request = event.target; 1.3788 + var status = this._getChannelStatus(request); 1.3789 + LOG("Checker:onLoad - request.status: " + status); 1.3790 + var update = new Update(null); 1.3791 + update.errorCode = status; 1.3792 + update.statusText = getStatusTextFromCode(status, 404); 1.3793 + 1.3794 + if (this._isHttpStatusCode(status)) { 1.3795 + update.errorCode = HTTP_ERROR_OFFSET + status; 1.3796 + } 1.3797 + if (e.result == Cr.NS_ERROR_ILLEGAL_VALUE) { 1.3798 + update.errorCode = updates[0] ? CERT_ATTR_CHECK_FAILED_HAS_UPDATE 1.3799 + : CERT_ATTR_CHECK_FAILED_NO_UPDATE; 1.3800 + } 1.3801 + this._callback.onError(request, update); 1.3802 + } 1.3803 + 1.3804 + this._callback = null; 1.3805 + this._request = null; 1.3806 + }, 1.3807 + 1.3808 + /** 1.3809 + * There was an error of some kind during the XMLHttpRequest 1.3810 + * @param event 1.3811 + * The nsIDOMEvent for the error 1.3812 + */ 1.3813 + onError: function UC_onError(event) { 1.3814 + var request = event.target; 1.3815 + var status = this._getChannelStatus(request); 1.3816 + LOG("Checker:onError - request.status: " + status); 1.3817 + 1.3818 + // If we can't find an error string specific to this status code, 1.3819 + // just use the 200 message from above, which means everything 1.3820 + // "looks" fine but there was probably an XML error or a bogus file. 1.3821 + var update = new Update(null); 1.3822 + update.errorCode = status; 1.3823 + update.statusText = getStatusTextFromCode(status, 200); 1.3824 + 1.3825 + if (status == Cr.NS_ERROR_OFFLINE) { 1.3826 + // We use a separate constant here because nsIUpdate.errorCode is signed 1.3827 + update.errorCode = NETWORK_ERROR_OFFLINE; 1.3828 + } else if (this._isHttpStatusCode(status)) { 1.3829 + update.errorCode = HTTP_ERROR_OFFSET + status; 1.3830 + } 1.3831 + 1.3832 + this._callback.onError(request, update); 1.3833 + 1.3834 + this._request = null; 1.3835 + }, 1.3836 + 1.3837 + /** 1.3838 + * Whether or not we are allowed to do update checking. 1.3839 + */ 1.3840 + _enabled: true, 1.3841 + get enabled() { 1.3842 + if (!gMetroUpdatesEnabled) { 1.3843 + return false; 1.3844 + } 1.3845 + 1.3846 + return getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) && 1.3847 + gCanCheckForUpdates && hasUpdateMutex() && this._enabled; 1.3848 + }, 1.3849 + 1.3850 + /** 1.3851 + * See nsIUpdateService.idl 1.3852 + */ 1.3853 + stopChecking: function UC_stopChecking(duration) { 1.3854 + // Always stop the current check 1.3855 + if (this._request) 1.3856 + this._request.abort(); 1.3857 + 1.3858 + switch (duration) { 1.3859 + case Ci.nsIUpdateChecker.CURRENT_SESSION: 1.3860 + this._enabled = false; 1.3861 + break; 1.3862 + case Ci.nsIUpdateChecker.ANY_CHECKS: 1.3863 + this._enabled = false; 1.3864 + Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled); 1.3865 + break; 1.3866 + } 1.3867 + 1.3868 + this._callback = null; 1.3869 + }, 1.3870 + 1.3871 + classID: Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"), 1.3872 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateChecker]) 1.3873 +}; 1.3874 + 1.3875 +/** 1.3876 + * Manages the download of updates 1.3877 + * @param background 1.3878 + * Whether or not this downloader is operating in background 1.3879 + * update mode. 1.3880 + * @param updateService 1.3881 + * The update service that created this downloader. 1.3882 + * @constructor 1.3883 + */ 1.3884 +function Downloader(background, updateService) { 1.3885 + LOG("Creating Downloader"); 1.3886 + this.background = background; 1.3887 + this.updateService = updateService; 1.3888 +} 1.3889 +Downloader.prototype = { 1.3890 + /** 1.3891 + * The nsIUpdatePatch that we are downloading 1.3892 + */ 1.3893 + _patch: null, 1.3894 + 1.3895 + /** 1.3896 + * The nsIUpdate that we are downloading 1.3897 + */ 1.3898 + _update: null, 1.3899 + 1.3900 + /** 1.3901 + * The nsIIncrementalDownload object handling the download 1.3902 + */ 1.3903 + _request: null, 1.3904 + 1.3905 + /** 1.3906 + * Whether or not the update being downloaded is a complete replacement of 1.3907 + * the user's existing installation or a patch representing the difference 1.3908 + * between the new version and the previous version. 1.3909 + */ 1.3910 + isCompleteUpdate: null, 1.3911 + 1.3912 + /** 1.3913 + * Cancels the active download. 1.3914 + */ 1.3915 + cancel: function Downloader_cancel(cancelError) { 1.3916 + LOG("Downloader: cancel"); 1.3917 + if (cancelError === undefined) { 1.3918 + cancelError = Cr.NS_BINDING_ABORTED; 1.3919 + } 1.3920 + if (this._request && this._request instanceof Ci.nsIRequest) { 1.3921 + this._request.cancel(cancelError); 1.3922 + } 1.3923 + releaseSDCardMountLock(); 1.3924 + }, 1.3925 + 1.3926 + /** 1.3927 + * Whether or not a patch has been downloaded and staged for installation. 1.3928 + */ 1.3929 + get patchIsStaged() { 1.3930 + var readState = readStatusFile(getUpdatesDir()); 1.3931 + // Note that if we decide to download and apply new updates after another 1.3932 + // update has been successfully applied in the background, we need to stop 1.3933 + // checking for the APPLIED state here. 1.3934 + return readState == STATE_PENDING || readState == STATE_PENDING_SVC || 1.3935 + readState == STATE_APPLIED || readState == STATE_APPLIED_SVC; 1.3936 + }, 1.3937 + 1.3938 + /** 1.3939 + * Verify the downloaded file. We assume that the download is complete at 1.3940 + * this point. 1.3941 + */ 1.3942 + _verifyDownload: function Downloader__verifyDownload() { 1.3943 + LOG("Downloader:_verifyDownload called"); 1.3944 + if (!this._request) 1.3945 + return false; 1.3946 + 1.3947 + var destination = this._request.destination; 1.3948 + 1.3949 + // Ensure that the file size matches the expected file size. 1.3950 + if (destination.fileSize != this._patch.size) { 1.3951 + LOG("Downloader:_verifyDownload downloaded size != expected size."); 1.3952 + return false; 1.3953 + } 1.3954 + 1.3955 + LOG("Downloader:_verifyDownload downloaded size == expected size."); 1.3956 + var fileStream = Cc["@mozilla.org/network/file-input-stream;1"]. 1.3957 + createInstance(Ci.nsIFileInputStream); 1.3958 + fileStream.init(destination, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); 1.3959 + 1.3960 + try { 1.3961 + var hash = Cc["@mozilla.org/security/hash;1"]. 1.3962 + createInstance(Ci.nsICryptoHash); 1.3963 + var hashFunction = Ci.nsICryptoHash[this._patch.hashFunction.toUpperCase()]; 1.3964 + if (hashFunction == undefined) 1.3965 + throw Cr.NS_ERROR_UNEXPECTED; 1.3966 + hash.init(hashFunction); 1.3967 + hash.updateFromStream(fileStream, -1); 1.3968 + // NOTE: For now, we assume that the format of _patch.hashValue is hex 1.3969 + // encoded binary (such as what is typically output by programs like 1.3970 + // sha1sum). In the future, this may change to base64 depending on how 1.3971 + // we choose to compute these hashes. 1.3972 + digest = binaryToHex(hash.finish(false)); 1.3973 + } catch (e) { 1.3974 + LOG("Downloader:_verifyDownload - failed to compute hash of the " + 1.3975 + "downloaded update archive"); 1.3976 + digest = ""; 1.3977 + } 1.3978 + 1.3979 + fileStream.close(); 1.3980 + 1.3981 + if (digest == this._patch.hashValue.toLowerCase()) { 1.3982 + LOG("Downloader:_verifyDownload hashes match."); 1.3983 + return true; 1.3984 + } 1.3985 + 1.3986 + LOG("Downloader:_verifyDownload hashes do not match. "); 1.3987 + return false; 1.3988 + }, 1.3989 + 1.3990 + /** 1.3991 + * Select the patch to use given the current state of updateDir and the given 1.3992 + * set of update patches. 1.3993 + * @param update 1.3994 + * A nsIUpdate object to select a patch from 1.3995 + * @param updateDir 1.3996 + * A nsIFile representing the update directory 1.3997 + * @return A nsIUpdatePatch object to download 1.3998 + */ 1.3999 + _selectPatch: function Downloader__selectPatch(update, updateDir) { 1.4000 + // Given an update to download, we will always try to download the patch 1.4001 + // for a partial update over the patch for a full update. 1.4002 + 1.4003 + /** 1.4004 + * Return the first UpdatePatch with the given type. 1.4005 + * @param type 1.4006 + * The type of the patch ("complete" or "partial") 1.4007 + * @return A nsIUpdatePatch object matching the type specified 1.4008 + */ 1.4009 + function getPatchOfType(type) { 1.4010 + for (var i = 0; i < update.patchCount; ++i) { 1.4011 + var patch = update.getPatchAt(i); 1.4012 + if (patch && patch.type == type) 1.4013 + return patch; 1.4014 + } 1.4015 + return null; 1.4016 + } 1.4017 + 1.4018 + // Look to see if any of the patches in the Update object has been 1.4019 + // pre-selected for download, otherwise we must figure out which one 1.4020 + // to select ourselves. 1.4021 + var selectedPatch = update.selectedPatch; 1.4022 + 1.4023 + var state = readStatusFile(updateDir); 1.4024 + 1.4025 + // If this is a patch that we know about, then select it. If it is a patch 1.4026 + // that we do not know about, then remove it and use our default logic. 1.4027 + var useComplete = false; 1.4028 + if (selectedPatch) { 1.4029 + LOG("Downloader:_selectPatch - found existing patch with state: " + 1.4030 + state); 1.4031 + switch (state) { 1.4032 + case STATE_DOWNLOADING: 1.4033 + LOG("Downloader:_selectPatch - resuming download"); 1.4034 + return selectedPatch; 1.4035 +#ifdef MOZ_WIDGET_GONK 1.4036 + case STATE_PENDING: 1.4037 + case STATE_APPLYING: 1.4038 + LOG("Downloader:_selectPatch - resuming interrupted apply"); 1.4039 + return selectedPatch; 1.4040 + case STATE_APPLIED: 1.4041 + LOG("Downloader:_selectPatch - already downloaded and staged"); 1.4042 + return null; 1.4043 +#else 1.4044 + case STATE_PENDING_SVC: 1.4045 + case STATE_PENDING: 1.4046 + LOG("Downloader:_selectPatch - already downloaded and staged"); 1.4047 + return null; 1.4048 +#endif 1.4049 + default: 1.4050 + // Something went wrong when we tried to apply the previous patch. 1.4051 + // Try the complete patch next time. 1.4052 + if (update && selectedPatch.type == "partial") { 1.4053 + useComplete = true; 1.4054 + } else { 1.4055 + // This is a pretty fatal error. Just bail. 1.4056 + LOG("Downloader:_selectPatch - failed to apply complete patch!"); 1.4057 + writeStatusFile(updateDir, STATE_NONE); 1.4058 + writeVersionFile(getUpdatesDir(), null); 1.4059 + return null; 1.4060 + } 1.4061 + } 1.4062 + 1.4063 + selectedPatch = null; 1.4064 + } 1.4065 + 1.4066 + // If we were not able to discover an update from a previous download, we 1.4067 + // select the best patch from the given set. 1.4068 + var partialPatch = getPatchOfType("partial"); 1.4069 + if (!useComplete) 1.4070 + selectedPatch = partialPatch; 1.4071 + if (!selectedPatch) { 1.4072 + if (partialPatch) 1.4073 + partialPatch.selected = false; 1.4074 + selectedPatch = getPatchOfType("complete"); 1.4075 + } 1.4076 + 1.4077 + // Restore the updateDir since we may have deleted it. 1.4078 + updateDir = getUpdatesDir(); 1.4079 + 1.4080 + // if update only contains a partial patch, selectedPatch == null here if 1.4081 + // the partial patch has been attempted and fails and we're trying to get a 1.4082 + // complete patch 1.4083 + if (selectedPatch) 1.4084 + selectedPatch.selected = true; 1.4085 + 1.4086 + update.isCompleteUpdate = useComplete; 1.4087 + 1.4088 + // Reset the Active Update object on the Update Manager and flush the 1.4089 + // Active Update DB. 1.4090 + var um = Cc["@mozilla.org/updates/update-manager;1"]. 1.4091 + getService(Ci.nsIUpdateManager); 1.4092 + um.activeUpdate = update; 1.4093 + 1.4094 + return selectedPatch; 1.4095 + }, 1.4096 + 1.4097 + /** 1.4098 + * Whether or not we are currently downloading something. 1.4099 + */ 1.4100 + get isBusy() { 1.4101 + return this._request != null; 1.4102 + }, 1.4103 + 1.4104 + /** 1.4105 + * Get the nsIFile to use for downloading the active update's selected patch 1.4106 + */ 1.4107 + _getUpdateArchiveFile: function Downloader__getUpdateArchiveFile() { 1.4108 + var updateArchive; 1.4109 +#ifdef USE_UPDATE_ARCHIVE_DIR 1.4110 + try { 1.4111 + updateArchive = FileUtils.getDir(KEY_UPDATE_ARCHIVE_DIR, [], true); 1.4112 + } catch (e) { 1.4113 + return null; 1.4114 + } 1.4115 +#else 1.4116 + updateArchive = getUpdatesDir().clone(); 1.4117 +#endif 1.4118 + 1.4119 + updateArchive.append(FILE_UPDATE_ARCHIVE); 1.4120 + return updateArchive; 1.4121 + }, 1.4122 + 1.4123 + /** 1.4124 + * Download and stage the given update. 1.4125 + * @param update 1.4126 + * A nsIUpdate object to download a patch for. Cannot be null. 1.4127 + */ 1.4128 + downloadUpdate: function Downloader_downloadUpdate(update) { 1.4129 + LOG("UpdateService:_downloadUpdate"); 1.4130 + if (!update) 1.4131 + throw Cr.NS_ERROR_NULL_POINTER; 1.4132 + 1.4133 + var updateDir = getUpdatesDir(); 1.4134 + 1.4135 + this._update = update; 1.4136 + 1.4137 + // This function may return null, which indicates that there are no patches 1.4138 + // to download. 1.4139 + this._patch = this._selectPatch(update, updateDir); 1.4140 + if (!this._patch) { 1.4141 + LOG("Downloader:downloadUpdate - no patch to download"); 1.4142 + return readStatusFile(updateDir); 1.4143 + } 1.4144 + this.isCompleteUpdate = this._patch.type == "complete"; 1.4145 + 1.4146 + var patchFile = null; 1.4147 + 1.4148 +#ifdef MOZ_WIDGET_GONK 1.4149 + let status = readStatusFile(updateDir); 1.4150 + if (isInterruptedUpdate(status)) { 1.4151 + LOG("Downloader:downloadUpdate - interruptted update"); 1.4152 + // The update was interrupted. Try to locate the existing patch file. 1.4153 + // For an interrupted download, this allows a resume rather than a 1.4154 + // re-download. 1.4155 + patchFile = getFileFromUpdateLink(updateDir); 1.4156 + if (!patchFile) { 1.4157 + // No link file. We'll just assume that the update.mar is in the 1.4158 + // update directory. 1.4159 + patchFile = updateDir.clone(); 1.4160 + patchFile.append(FILE_UPDATE_ARCHIVE); 1.4161 + } 1.4162 + if (patchFile.exists()) { 1.4163 + LOG("Downloader:downloadUpdate - resuming with patchFile " + patchFile.path); 1.4164 + if (patchFile.fileSize == this._patch.size) { 1.4165 + LOG("Downloader:downloadUpdate - patchFile appears to be fully downloaded"); 1.4166 + // Bump the status along so that we don't try to redownload again. 1.4167 + status = STATE_PENDING; 1.4168 + } 1.4169 + } else { 1.4170 + LOG("Downloader:downloadUpdate - patchFile " + patchFile.path + 1.4171 + " doesn't exist - performing full download"); 1.4172 + // The patchfile doesn't exist, we might as well treat this like 1.4173 + // a new download. 1.4174 + patchFile = null; 1.4175 + } 1.4176 + if (patchFile && (status != STATE_DOWNLOADING)) { 1.4177 + // It looks like the patch was downloaded, but got interrupted while it 1.4178 + // was being verified or applied. So we'll fake the downloading portion. 1.4179 + 1.4180 + writeStatusFile(updateDir, STATE_PENDING); 1.4181 + 1.4182 + // Since the code expects the onStopRequest callback to happen 1.4183 + // asynchronously (And you have to call AUS_addDownloadListener 1.4184 + // after calling AUS_downloadUpdate) we need to defer this. 1.4185 + 1.4186 + this._downloadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.4187 + this._downloadTimer.initWithCallback(function() { 1.4188 + this._downloadTimer = null; 1.4189 + // Send a fake onStopRequest. Filling in the destination allows 1.4190 + // _verifyDownload to work, and then the update will be applied. 1.4191 + this._request = {destination: patchFile}; 1.4192 + this.onStopRequest(this._request, null, Cr.NS_OK); 1.4193 + }.bind(this), 0, Ci.nsITimer.TYPE_ONE_SHOT); 1.4194 + 1.4195 + // Returning STATE_DOWNLOADING makes UpdatePrompt think we're 1.4196 + // downloading. The onStopRequest that we spoofed above will make it 1.4197 + // look like the download finished. 1.4198 + return STATE_DOWNLOADING; 1.4199 + } 1.4200 + } 1.4201 +#endif 1.4202 + if (!patchFile) { 1.4203 + // Find a place to put the patchfile that we're going to download. 1.4204 + patchFile = this._getUpdateArchiveFile(); 1.4205 + } 1.4206 + if (!patchFile) { 1.4207 + return STATE_NONE; 1.4208 + } 1.4209 + 1.4210 +#ifdef MOZ_WIDGET_GONK 1.4211 + if (patchFile.path.indexOf(updateDir.path) != 0) { 1.4212 + // The patchFile is in a directory which is different from the 1.4213 + // updateDir, create a link file. 1.4214 + writeLinkFile(updateDir, patchFile); 1.4215 + 1.4216 + if (!isInterruptedUpdate(status) && patchFile.exists()) { 1.4217 + // Remove stale patchFile 1.4218 + patchFile.remove(false); 1.4219 + } 1.4220 + } 1.4221 +#endif 1.4222 + 1.4223 + var uri = Services.io.newURI(this._patch.URL, null, null); 1.4224 + 1.4225 + this._request = Cc["@mozilla.org/network/incremental-download;1"]. 1.4226 + createInstance(Ci.nsIIncrementalDownload); 1.4227 + 1.4228 + LOG("Downloader:downloadUpdate - downloading from " + uri.spec + " to " + 1.4229 + patchFile.path); 1.4230 + var interval = this.background ? getPref("getIntPref", 1.4231 + PREF_APP_UPDATE_BACKGROUND_INTERVAL, 1.4232 + DOWNLOAD_BACKGROUND_INTERVAL) 1.4233 + : DOWNLOAD_FOREGROUND_INTERVAL; 1.4234 + this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval); 1.4235 + this._request.start(this, null); 1.4236 + 1.4237 + writeStatusFile(updateDir, STATE_DOWNLOADING); 1.4238 + this._patch.QueryInterface(Ci.nsIWritablePropertyBag); 1.4239 + this._patch.state = STATE_DOWNLOADING; 1.4240 + var um = Cc["@mozilla.org/updates/update-manager;1"]. 1.4241 + getService(Ci.nsIUpdateManager); 1.4242 + um.saveUpdates(); 1.4243 + return STATE_DOWNLOADING; 1.4244 + }, 1.4245 + 1.4246 + /** 1.4247 + * An array of download listeners to notify when we receive 1.4248 + * nsIRequestObserver or nsIProgressEventSink method calls. 1.4249 + */ 1.4250 + _listeners: [], 1.4251 + 1.4252 + /** 1.4253 + * Adds a listener to the download process 1.4254 + * @param listener 1.4255 + * A download listener, implementing nsIRequestObserver and 1.4256 + * nsIProgressEventSink 1.4257 + */ 1.4258 + addDownloadListener: function Downloader_addDownloadListener(listener) { 1.4259 + for (var i = 0; i < this._listeners.length; ++i) { 1.4260 + if (this._listeners[i] == listener) 1.4261 + return; 1.4262 + } 1.4263 + this._listeners.push(listener); 1.4264 + }, 1.4265 + 1.4266 + /** 1.4267 + * Removes a download listener 1.4268 + * @param listener 1.4269 + * The listener to remove. 1.4270 + */ 1.4271 + removeDownloadListener: function Downloader_removeDownloadListener(listener) { 1.4272 + for (var i = 0; i < this._listeners.length; ++i) { 1.4273 + if (this._listeners[i] == listener) { 1.4274 + this._listeners.splice(i, 1); 1.4275 + return; 1.4276 + } 1.4277 + } 1.4278 + }, 1.4279 + 1.4280 + /** 1.4281 + * When the async request begins 1.4282 + * @param request 1.4283 + * The nsIRequest object for the transfer 1.4284 + * @param context 1.4285 + * Additional data 1.4286 + */ 1.4287 + onStartRequest: function Downloader_onStartRequest(request, context) { 1.4288 + if (request instanceof Ci.nsIIncrementalDownload) 1.4289 + LOG("Downloader:onStartRequest - original URI spec: " + request.URI.spec + 1.4290 + ", final URI spec: " + request.finalURI.spec); 1.4291 + // Always set finalURL in onStartRequest since it can change. 1.4292 + this._patch.finalURL = request.finalURI.spec; 1.4293 + var um = Cc["@mozilla.org/updates/update-manager;1"]. 1.4294 + getService(Ci.nsIUpdateManager); 1.4295 + um.saveUpdates(); 1.4296 + 1.4297 + var listeners = this._listeners.concat(); 1.4298 + var listenerCount = listeners.length; 1.4299 + for (var i = 0; i < listenerCount; ++i) 1.4300 + listeners[i].onStartRequest(request, context); 1.4301 + }, 1.4302 + 1.4303 + /** 1.4304 + * When new data has been downloaded 1.4305 + * @param request 1.4306 + * The nsIRequest object for the transfer 1.4307 + * @param context 1.4308 + * Additional data 1.4309 + * @param progress 1.4310 + * The current number of bytes transferred 1.4311 + * @param maxProgress 1.4312 + * The total number of bytes that must be transferred 1.4313 + */ 1.4314 + onProgress: function Downloader_onProgress(request, context, progress, 1.4315 + maxProgress) { 1.4316 + LOG("Downloader:onProgress - progress: " + progress + "/" + maxProgress); 1.4317 + 1.4318 + if (progress > this._patch.size) { 1.4319 + LOG("Downloader:onProgress - progress: " + progress + 1.4320 + " is higher than patch size: " + this._patch.size); 1.4321 + // It's important that we use a different code than 1.4322 + // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference 1.4323 + // between a hash error and a wrong download error. 1.4324 + this.cancel(Cr.NS_ERROR_UNEXPECTED); 1.4325 + return; 1.4326 + } 1.4327 + 1.4328 + if (maxProgress != this._patch.size) { 1.4329 + LOG("Downloader:onProgress - maxProgress: " + maxProgress + 1.4330 + " is not equal to expectd patch size: " + this._patch.size); 1.4331 + // It's important that we use a different code than 1.4332 + // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference 1.4333 + // between a hash error and a wrong download error. 1.4334 + this.cancel(Cr.NS_ERROR_UNEXPECTED); 1.4335 + return; 1.4336 + } 1.4337 + 1.4338 + var listeners = this._listeners.concat(); 1.4339 + var listenerCount = listeners.length; 1.4340 + for (var i = 0; i < listenerCount; ++i) { 1.4341 + var listener = listeners[i]; 1.4342 + if (listener instanceof Ci.nsIProgressEventSink) 1.4343 + listener.onProgress(request, context, progress, maxProgress); 1.4344 + } 1.4345 + this.updateService._consecutiveSocketErrors = 0; 1.4346 + }, 1.4347 + 1.4348 + /** 1.4349 + * When we have new status text 1.4350 + * @param request 1.4351 + * The nsIRequest object for the transfer 1.4352 + * @param context 1.4353 + * Additional data 1.4354 + * @param status 1.4355 + * A status code 1.4356 + * @param statusText 1.4357 + * Human readable version of |status| 1.4358 + */ 1.4359 + onStatus: function Downloader_onStatus(request, context, status, statusText) { 1.4360 + LOG("Downloader:onStatus - status: " + status + ", statusText: " + 1.4361 + statusText); 1.4362 + 1.4363 + var listeners = this._listeners.concat(); 1.4364 + var listenerCount = listeners.length; 1.4365 + for (var i = 0; i < listenerCount; ++i) { 1.4366 + var listener = listeners[i]; 1.4367 + if (listener instanceof Ci.nsIProgressEventSink) 1.4368 + listener.onStatus(request, context, status, statusText); 1.4369 + } 1.4370 + }, 1.4371 + 1.4372 + /** 1.4373 + * When data transfer ceases 1.4374 + * @param request 1.4375 + * The nsIRequest object for the transfer 1.4376 + * @param context 1.4377 + * Additional data 1.4378 + * @param status 1.4379 + * Status code containing the reason for the cessation. 1.4380 + */ 1.4381 + onStopRequest: function Downloader_onStopRequest(request, context, status) { 1.4382 + if (request instanceof Ci.nsIIncrementalDownload) 1.4383 + LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec + 1.4384 + ", final URI spec: " + request.finalURI.spec + ", status: " + status); 1.4385 + 1.4386 + // XXX ehsan shouldShowPrompt should always be false here. 1.4387 + // But what happens when there is already a UI showing? 1.4388 + var state = this._patch.state; 1.4389 + var shouldShowPrompt = false; 1.4390 + var shouldRegisterOnlineObserver = false; 1.4391 + var shouldRetrySoon = false; 1.4392 + var deleteActiveUpdate = false; 1.4393 + var retryTimeout = getPref("getIntPref", PREF_APP_UPDATE_RETRY_TIMEOUT, 1.4394 + DEFAULT_UPDATE_RETRY_TIMEOUT); 1.4395 + var maxFail = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_ERRORS, 1.4396 + DEFAULT_SOCKET_MAX_ERRORS); 1.4397 + LOG("Downloader:onStopRequest - status: " + status + ", " + 1.4398 + "current fail: " + this.updateService._consecutiveSocketErrors + ", " + 1.4399 + "max fail: " + maxFail + ", " + "retryTimeout: " + retryTimeout); 1.4400 + if (Components.isSuccessCode(status)) { 1.4401 + if (this._verifyDownload()) { 1.4402 + state = shouldUseService() ? STATE_PENDING_SVC : STATE_PENDING; 1.4403 + if (this.background) { 1.4404 + shouldShowPrompt = !getCanStageUpdates(); 1.4405 + } 1.4406 + 1.4407 + // Tell the updater.exe we're ready to apply. 1.4408 + writeStatusFile(getUpdatesDir(), state); 1.4409 + writeVersionFile(getUpdatesDir(), this._update.appVersion); 1.4410 + this._update.installDate = (new Date()).getTime(); 1.4411 + this._update.statusText = gUpdateBundle.GetStringFromName("installPending"); 1.4412 + } 1.4413 + else { 1.4414 + LOG("Downloader:onStopRequest - download verification failed"); 1.4415 + state = STATE_DOWNLOAD_FAILED; 1.4416 + status = Cr.NS_ERROR_CORRUPTED_CONTENT; 1.4417 + 1.4418 + // Yes, this code is a string. 1.4419 + const vfCode = "verification_failed"; 1.4420 + var message = getStatusTextFromCode(vfCode, vfCode); 1.4421 + this._update.statusText = message; 1.4422 + 1.4423 + if (this._update.isCompleteUpdate || this._update.patchCount != 2) 1.4424 + deleteActiveUpdate = true; 1.4425 + 1.4426 + // Destroy the updates directory, since we're done with it. 1.4427 + cleanUpUpdatesDir(); 1.4428 + } 1.4429 + } else if (status == Cr.NS_ERROR_OFFLINE) { 1.4430 + // Register an online observer to try again. 1.4431 + // The online observer will continue the incremental download by 1.4432 + // calling downloadUpdate on the active update which continues 1.4433 + // downloading the file from where it was. 1.4434 + LOG("Downloader:onStopRequest - offline, register online observer: true"); 1.4435 + shouldRegisterOnlineObserver = true; 1.4436 + deleteActiveUpdate = false; 1.4437 + // Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED, and 1.4438 + // NS_ERROR_NET_RESET can be returned when disconnecting the internet while 1.4439 + // a download of a MAR is in progress. There may be others but I have not 1.4440 + // encountered them during testing. 1.4441 + } else if ((status == Cr.NS_ERROR_NET_TIMEOUT || 1.4442 + status == Cr.NS_ERROR_CONNECTION_REFUSED || 1.4443 + status == Cr.NS_ERROR_NET_RESET) && 1.4444 + this.updateService._consecutiveSocketErrors < maxFail) { 1.4445 + LOG("Downloader:onStopRequest - socket error, shouldRetrySoon: true"); 1.4446 + shouldRetrySoon = true; 1.4447 + deleteActiveUpdate = false; 1.4448 + } else if (status != Cr.NS_BINDING_ABORTED && 1.4449 + status != Cr.NS_ERROR_ABORT && 1.4450 + status != Cr.NS_ERROR_DOCUMENT_NOT_CACHED) { 1.4451 + LOG("Downloader:onStopRequest - non-verification failure"); 1.4452 + // Some sort of other failure, log this in the |statusText| property 1.4453 + state = STATE_DOWNLOAD_FAILED; 1.4454 + 1.4455 + // XXXben - if |request| (The Incremental Download) provided a means 1.4456 + // for accessing the http channel we could do more here. 1.4457 + 1.4458 + this._update.statusText = getStatusTextFromCode(status, 1.4459 + Cr.NS_BINDING_FAILED); 1.4460 + 1.4461 +#ifdef MOZ_WIDGET_GONK 1.4462 + // bug891009: On FirefoxOS, manaully retry OTA download will reuse 1.4463 + // the Update object. We need to remove selected patch so that download 1.4464 + // can be triggered again successfully. 1.4465 + this._update.selectedPatch.selected = false; 1.4466 +#endif 1.4467 + 1.4468 + // Destroy the updates directory, since we're done with it. 1.4469 + cleanUpUpdatesDir(); 1.4470 + 1.4471 + deleteActiveUpdate = true; 1.4472 + } 1.4473 + LOG("Downloader:onStopRequest - setting state to: " + state); 1.4474 + this._patch.state = state; 1.4475 + var um = Cc["@mozilla.org/updates/update-manager;1"]. 1.4476 + getService(Ci.nsIUpdateManager); 1.4477 + if (deleteActiveUpdate) { 1.4478 + this._update.installDate = (new Date()).getTime(); 1.4479 + um.activeUpdate = null; 1.4480 + } 1.4481 + else { 1.4482 + if (um.activeUpdate) 1.4483 + um.activeUpdate.state = state; 1.4484 + } 1.4485 + um.saveUpdates(); 1.4486 + 1.4487 + // Only notify listeners about the stopped state if we 1.4488 + // aren't handling an internal retry. 1.4489 + if (!shouldRetrySoon && !shouldRegisterOnlineObserver) { 1.4490 + var listeners = this._listeners.concat(); 1.4491 + var listenerCount = listeners.length; 1.4492 + for (var i = 0; i < listenerCount; ++i) { 1.4493 + listeners[i].onStopRequest(request, context, status); 1.4494 + } 1.4495 + } 1.4496 + 1.4497 + this._request = null; 1.4498 + 1.4499 + if (state == STATE_DOWNLOAD_FAILED) { 1.4500 + var allFailed = true; 1.4501 + // Check if there is a complete update patch that can be downloaded. 1.4502 + if (!this._update.isCompleteUpdate && this._update.patchCount == 2) { 1.4503 + LOG("Downloader:onStopRequest - verification of patch failed, " + 1.4504 + "downloading complete update patch"); 1.4505 + this._update.isCompleteUpdate = true; 1.4506 + let updateStatus = this.downloadUpdate(this._update); 1.4507 + 1.4508 + if (updateStatus == STATE_NONE) { 1.4509 + cleanupActiveUpdate(); 1.4510 + } else { 1.4511 + allFailed = false; 1.4512 + } 1.4513 + } 1.4514 + 1.4515 + if (allFailed) { 1.4516 + LOG("Downloader:onStopRequest - all update patch downloads failed"); 1.4517 + // If the update UI is not open (e.g. the user closed the window while 1.4518 + // downloading) and if at any point this was a foreground download 1.4519 + // notify the user about the error. If the update was a background 1.4520 + // update there is no notification since the user won't be expecting it. 1.4521 + if (!Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME)) { 1.4522 + try { 1.4523 + this._update.QueryInterface(Ci.nsIWritablePropertyBag); 1.4524 + var fgdl = this._update.getProperty("foregroundDownload"); 1.4525 + } 1.4526 + catch (e) { 1.4527 + } 1.4528 + 1.4529 + if (fgdl == "true") { 1.4530 + var prompter = Cc["@mozilla.org/updates/update-prompt;1"]. 1.4531 + createInstance(Ci.nsIUpdatePrompt); 1.4532 + prompter.showUpdateError(this._update); 1.4533 + } 1.4534 + } 1.4535 + 1.4536 +#ifdef MOZ_WIDGET_GONK 1.4537 + // We always forward errors in B2G, since Gaia controls the update UI 1.4538 + var prompter = Cc["@mozilla.org/updates/update-prompt;1"]. 1.4539 + createInstance(Ci.nsIUpdatePrompt); 1.4540 + prompter.showUpdateError(this._update); 1.4541 +#endif 1.4542 + 1.4543 + // Prevent leaking the update object (bug 454964). 1.4544 + this._update = null; 1.4545 + } 1.4546 + // A complete download has been initiated or the failure was handled. 1.4547 + return; 1.4548 + } 1.4549 + 1.4550 + if (state == STATE_PENDING || state == STATE_PENDING_SVC) { 1.4551 + if (getCanStageUpdates()) { 1.4552 + LOG("Downloader:onStopRequest - attempting to stage update: " + 1.4553 + this._update.name); 1.4554 + 1.4555 + // Initiate the update in the background 1.4556 + try { 1.4557 + Cc["@mozilla.org/updates/update-processor;1"]. 1.4558 + createInstance(Ci.nsIUpdateProcessor). 1.4559 + processUpdate(this._update); 1.4560 + } catch (e) { 1.4561 + // Fail gracefully in case the application does not support the update 1.4562 + // processor service. 1.4563 + LOG("Downloader:onStopRequest - failed to stage update. Exception: " + 1.4564 + e); 1.4565 + if (this.background) { 1.4566 + shouldShowPrompt = true; 1.4567 + } 1.4568 + } 1.4569 + } 1.4570 + } 1.4571 + 1.4572 + // Do this after *everything* else, since it will likely cause the app 1.4573 + // to shut down. 1.4574 + if (shouldShowPrompt) { 1.4575 + // Notify the user that an update has been downloaded and is ready for 1.4576 + // installation (i.e. that they should restart the application). We do 1.4577 + // not notify on failed update attempts. 1.4578 + var prompter = Cc["@mozilla.org/updates/update-prompt;1"]. 1.4579 + createInstance(Ci.nsIUpdatePrompt); 1.4580 + prompter.showUpdateDownloaded(this._update, true); 1.4581 + } 1.4582 + 1.4583 + if (shouldRegisterOnlineObserver) { 1.4584 + LOG("Downloader:onStopRequest - Registering online observer"); 1.4585 + this.updateService._registerOnlineObserver(); 1.4586 + } else if (shouldRetrySoon) { 1.4587 + LOG("Downloader:onStopRequest - Retrying soon"); 1.4588 + this.updateService._consecutiveSocketErrors++; 1.4589 + if (this.updateService._retryTimer) { 1.4590 + this.updateService._retryTimer.cancel(); 1.4591 + } 1.4592 + this.updateService._retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.4593 + this.updateService._retryTimer.initWithCallback(function() { 1.4594 + this._attemptResume(); 1.4595 + }.bind(this.updateService), retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT); 1.4596 + } else { 1.4597 + // Prevent leaking the update object (bug 454964) 1.4598 + this._update = null; 1.4599 + } 1.4600 + }, 1.4601 + 1.4602 + /** 1.4603 + * See nsIInterfaceRequestor.idl 1.4604 + */ 1.4605 + getInterface: function Downloader_getInterface(iid) { 1.4606 + // The network request may require proxy authentication, so provide the 1.4607 + // default nsIAuthPrompt if requested. 1.4608 + if (iid.equals(Ci.nsIAuthPrompt)) { 1.4609 + var prompt = Cc["@mozilla.org/network/default-auth-prompt;1"]. 1.4610 + createInstance(); 1.4611 + return prompt.QueryInterface(iid); 1.4612 + } 1.4613 + throw Cr.NS_NOINTERFACE; 1.4614 + }, 1.4615 + 1.4616 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver, 1.4617 + Ci.nsIProgressEventSink, 1.4618 + Ci.nsIInterfaceRequestor]) 1.4619 +}; 1.4620 + 1.4621 +/** 1.4622 + * UpdatePrompt 1.4623 + * An object which can prompt the user with information about updates, request 1.4624 + * action, etc. Embedding clients can override this component with one that 1.4625 + * invokes a native front end. 1.4626 + * @constructor 1.4627 + */ 1.4628 +function UpdatePrompt() { 1.4629 +} 1.4630 +UpdatePrompt.prototype = { 1.4631 + /** 1.4632 + * See nsIUpdateService.idl 1.4633 + */ 1.4634 + checkForUpdates: function UP_checkForUpdates() { 1.4635 + if (this._getAltUpdateWindow()) 1.4636 + return; 1.4637 + 1.4638 + this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME, 1.4639 + null, null); 1.4640 + }, 1.4641 + 1.4642 + /** 1.4643 + * See nsIUpdateService.idl 1.4644 + */ 1.4645 + showUpdateAvailable: function UP_showUpdateAvailable(update) { 1.4646 + if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) || 1.4647 + this._getUpdateWindow() || this._getAltUpdateWindow()) 1.4648 + return; 1.4649 + 1.4650 + var stringsPrefix = "updateAvailable_" + update.type + "."; 1.4651 + var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title", 1.4652 + [update.name], 1); 1.4653 + var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text"); 1.4654 + var imageUrl = ""; 1.4655 + this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null, 1.4656 + UPDATE_WINDOW_NAME, "updatesavailable", update, 1.4657 + title, text, imageUrl); 1.4658 + }, 1.4659 + 1.4660 + /** 1.4661 + * See nsIUpdateService.idl 1.4662 + */ 1.4663 + showUpdateDownloaded: function UP_showUpdateDownloaded(update, background) { 1.4664 + if (this._getAltUpdateWindow()) 1.4665 + return; 1.4666 + 1.4667 + if (background) { 1.4668 + if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false)) 1.4669 + return; 1.4670 + 1.4671 + var stringsPrefix = "updateDownloaded_" + update.type + "."; 1.4672 + var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title", 1.4673 + [update.name], 1); 1.4674 + var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text"); 1.4675 + var imageUrl = ""; 1.4676 + this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null, 1.4677 + UPDATE_WINDOW_NAME, "finishedBackground", update, 1.4678 + title, text, imageUrl); 1.4679 + } else { 1.4680 + this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, 1.4681 + UPDATE_WINDOW_NAME, "finishedBackground", update); 1.4682 + } 1.4683 + }, 1.4684 + 1.4685 + /** 1.4686 + * See nsIUpdateService.idl 1.4687 + */ 1.4688 + showUpdateInstalled: function UP_showUpdateInstalled() { 1.4689 + if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) || 1.4690 + !getPref("getBoolPref", PREF_APP_UPDATE_SHOW_INSTALLED_UI, false) || 1.4691 + this._getUpdateWindow()) 1.4692 + return; 1.4693 + 1.4694 + var page = "installed"; 1.4695 + var win = this._getUpdateWindow(); 1.4696 + if (win) { 1.4697 + if (page && "setCurrentPage" in win) 1.4698 + win.setCurrentPage(page); 1.4699 + win.focus(); 1.4700 + } 1.4701 + else { 1.4702 + var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no"; 1.4703 + var arg = Cc["@mozilla.org/supports-string;1"]. 1.4704 + createInstance(Ci.nsISupportsString); 1.4705 + arg.data = page; 1.4706 + Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, null, openFeatures, arg); 1.4707 + } 1.4708 + }, 1.4709 + 1.4710 + /** 1.4711 + * See nsIUpdateService.idl 1.4712 + */ 1.4713 + showUpdateError: function UP_showUpdateError(update) { 1.4714 + if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) || 1.4715 + this._getAltUpdateWindow()) 1.4716 + return; 1.4717 + 1.4718 + // In some cases, we want to just show a simple alert dialog: 1.4719 + if (update.state == STATE_FAILED && 1.4720 + (update.errorCode == WRITE_ERROR || 1.4721 + update.errorCode == WRITE_ERROR_ACCESS_DENIED || 1.4722 + update.errorCode == WRITE_ERROR_SHARING_VIOLATION_SIGNALED || 1.4723 + update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID || 1.4724 + update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPID || 1.4725 + update.errorCode == WRITE_ERROR_CALLBACK_APP || 1.4726 + update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR || 1.4727 + update.errorCode == FOTA_GENERAL_ERROR || 1.4728 + update.errorCode == FOTA_FILE_OPERATION_ERROR || 1.4729 + update.errorCode == FOTA_RECOVERY_ERROR || 1.4730 + update.errorCode == FOTA_UNKNOWN_ERROR)) { 1.4731 + var title = gUpdateBundle.GetStringFromName("updaterIOErrorTitle"); 1.4732 + var text = gUpdateBundle.formatStringFromName("updaterIOErrorMsg", 1.4733 + [Services.appinfo.name, 1.4734 + Services.appinfo.name], 2); 1.4735 + Services.ww.getNewPrompter(null).alert(title, text); 1.4736 + return; 1.4737 + } 1.4738 + 1.4739 + if (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE || 1.4740 + update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE || 1.4741 + update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) { 1.4742 + this._showUIWhenIdle(null, URI_UPDATE_PROMPT_DIALOG, null, 1.4743 + UPDATE_WINDOW_NAME, null, update); 1.4744 + return; 1.4745 + } 1.4746 + 1.4747 + this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME, 1.4748 + "errors", update); 1.4749 + }, 1.4750 + 1.4751 + /** 1.4752 + * See nsIUpdateService.idl 1.4753 + */ 1.4754 + showUpdateHistory: function UP_showUpdateHistory(parent) { 1.4755 + this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal,dialog=yes", 1.4756 + "Update:History", null, null); 1.4757 + }, 1.4758 + 1.4759 + /** 1.4760 + * Returns the update window if present. 1.4761 + */ 1.4762 + _getUpdateWindow: function UP__getUpdateWindow() { 1.4763 + return Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME); 1.4764 + }, 1.4765 + 1.4766 + /** 1.4767 + * Returns an alternative update window if present. When a window with this 1.4768 + * windowtype is open the application update service won't open the normal 1.4769 + * application update user interface window. 1.4770 + */ 1.4771 + _getAltUpdateWindow: function UP__getAltUpdateWindow() { 1.4772 + let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null); 1.4773 + if (!windowType) 1.4774 + return null; 1.4775 + return Services.wm.getMostRecentWindow(windowType); 1.4776 + }, 1.4777 + 1.4778 + /** 1.4779 + * Initiate a less obtrusive UI, starting with a non-modal notification alert 1.4780 + * @param parent 1.4781 + * A parent window, can be null 1.4782 + * @param uri 1.4783 + * The URI string of the dialog to show 1.4784 + * @param name 1.4785 + * The Window Name of the dialog to show, in case it is already open 1.4786 + * and can merely be focused 1.4787 + * @param page 1.4788 + * The page of the wizard to be displayed, if one is already open. 1.4789 + * @param update 1.4790 + * An update to pass to the UI in the window arguments. 1.4791 + * Can be null 1.4792 + * @param title 1.4793 + * The title for the notification alert. 1.4794 + * @param text 1.4795 + * The contents of the notification alert. 1.4796 + * @param imageUrl 1.4797 + * A URL identifying the image to put in the notification alert. 1.4798 + */ 1.4799 + _showUnobtrusiveUI: function UP__showUnobUI(parent, uri, features, name, page, 1.4800 + update, title, text, imageUrl) { 1.4801 + var observer = { 1.4802 + updatePrompt: this, 1.4803 + service: null, 1.4804 + timer: null, 1.4805 + notify: function () { 1.4806 + // the user hasn't restarted yet => prompt when idle 1.4807 + this.service.removeObserver(this, "quit-application"); 1.4808 + // If the update window is already open skip showing the UI 1.4809 + if (this.updatePrompt._getUpdateWindow()) 1.4810 + return; 1.4811 + this.updatePrompt._showUIWhenIdle(parent, uri, features, name, page, update); 1.4812 + }, 1.4813 + observe: function (aSubject, aTopic, aData) { 1.4814 + switch (aTopic) { 1.4815 + case "alertclickcallback": 1.4816 + this.updatePrompt._showUI(parent, uri, features, name, page, update); 1.4817 + // fall thru 1.4818 + case "quit-application": 1.4819 + if (this.timer) 1.4820 + this.timer.cancel(); 1.4821 + this.service.removeObserver(this, "quit-application"); 1.4822 + break; 1.4823 + } 1.4824 + } 1.4825 + }; 1.4826 + 1.4827 + // bug 534090 - show the UI for update available notifications when the 1.4828 + // the system has been idle for at least IDLE_TIME without displaying an 1.4829 + // alert notification. 1.4830 + if (page == "updatesavailable") { 1.4831 + var idleService = Cc["@mozilla.org/widget/idleservice;1"]. 1.4832 + getService(Ci.nsIIdleService); 1.4833 + 1.4834 + const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60); 1.4835 + if (idleService.idleTime / 1000 >= IDLE_TIME) { 1.4836 + this._showUI(parent, uri, features, name, page, update); 1.4837 + return; 1.4838 + } 1.4839 + } 1.4840 + 1.4841 + try { 1.4842 + var notifier = Cc["@mozilla.org/alerts-service;1"]. 1.4843 + getService(Ci.nsIAlertsService); 1.4844 + notifier.showAlertNotification(imageUrl, title, text, true, "", observer); 1.4845 + } 1.4846 + catch (e) { 1.4847 + // Failed to retrieve alerts service, platform unsupported 1.4848 + this._showUIWhenIdle(parent, uri, features, name, page, update); 1.4849 + return; 1.4850 + } 1.4851 + 1.4852 + observer.service = Services.obs; 1.4853 + observer.service.addObserver(observer, "quit-application", false); 1.4854 + 1.4855 + // bug 534090 - show the UI when idle for update available notifications. 1.4856 + if (page == "updatesavailable") { 1.4857 + this._showUIWhenIdle(parent, uri, features, name, page, update); 1.4858 + return; 1.4859 + } 1.4860 + 1.4861 + // Give the user x seconds to react before prompting as defined by 1.4862 + // promptWaitTime 1.4863 + observer.timer = Cc["@mozilla.org/timer;1"]. 1.4864 + createInstance(Ci.nsITimer); 1.4865 + observer.timer.initWithCallback(observer, update.promptWaitTime * 1000, 1.4866 + observer.timer.TYPE_ONE_SHOT); 1.4867 + }, 1.4868 + 1.4869 + /** 1.4870 + * Show the UI when the user was idle 1.4871 + * @param parent 1.4872 + * A parent window, can be null 1.4873 + * @param uri 1.4874 + * The URI string of the dialog to show 1.4875 + * @param name 1.4876 + * The Window Name of the dialog to show, in case it is already open 1.4877 + * and can merely be focused 1.4878 + * @param page 1.4879 + * The page of the wizard to be displayed, if one is already open. 1.4880 + * @param update 1.4881 + * An update to pass to the UI in the window arguments. 1.4882 + * Can be null 1.4883 + */ 1.4884 + _showUIWhenIdle: function UP__showUIWhenIdle(parent, uri, features, name, 1.4885 + page, update) { 1.4886 + var idleService = Cc["@mozilla.org/widget/idleservice;1"]. 1.4887 + getService(Ci.nsIIdleService); 1.4888 + 1.4889 + const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60); 1.4890 + if (idleService.idleTime / 1000 >= IDLE_TIME) { 1.4891 + this._showUI(parent, uri, features, name, page, update); 1.4892 + } else { 1.4893 + var observer = { 1.4894 + updatePrompt: this, 1.4895 + observe: function (aSubject, aTopic, aData) { 1.4896 + switch (aTopic) { 1.4897 + case "idle": 1.4898 + // If the update window is already open skip showing the UI 1.4899 + if (!this.updatePrompt._getUpdateWindow()) 1.4900 + this.updatePrompt._showUI(parent, uri, features, name, page, update); 1.4901 + // fall thru 1.4902 + case "quit-application": 1.4903 + idleService.removeIdleObserver(this, IDLE_TIME); 1.4904 + Services.obs.removeObserver(this, "quit-application"); 1.4905 + break; 1.4906 + } 1.4907 + } 1.4908 + }; 1.4909 + idleService.addIdleObserver(observer, IDLE_TIME); 1.4910 + Services.obs.addObserver(observer, "quit-application", false); 1.4911 + } 1.4912 + }, 1.4913 + 1.4914 + /** 1.4915 + * Show the Update Checking UI 1.4916 + * @param parent 1.4917 + * A parent window, can be null 1.4918 + * @param uri 1.4919 + * The URI string of the dialog to show 1.4920 + * @param name 1.4921 + * The Window Name of the dialog to show, in case it is already open 1.4922 + * and can merely be focused 1.4923 + * @param page 1.4924 + * The page of the wizard to be displayed, if one is already open. 1.4925 + * @param update 1.4926 + * An update to pass to the UI in the window arguments. 1.4927 + * Can be null 1.4928 + */ 1.4929 + _showUI: function UP__showUI(parent, uri, features, name, page, update) { 1.4930 + var ary = null; 1.4931 + if (update) { 1.4932 + ary = Cc["@mozilla.org/supports-array;1"]. 1.4933 + createInstance(Ci.nsISupportsArray); 1.4934 + ary.AppendElement(update); 1.4935 + } 1.4936 + 1.4937 + var win = this._getUpdateWindow(); 1.4938 + if (win) { 1.4939 + if (page && "setCurrentPage" in win) 1.4940 + win.setCurrentPage(page); 1.4941 + win.focus(); 1.4942 + } 1.4943 + else { 1.4944 + var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no"; 1.4945 + if (features) 1.4946 + openFeatures += "," + features; 1.4947 + Services.ww.openWindow(parent, uri, "", openFeatures, ary); 1.4948 + } 1.4949 + }, 1.4950 + 1.4951 + classDescription: "Update Prompt", 1.4952 + contractID: "@mozilla.org/updates/update-prompt;1", 1.4953 + classID: Components.ID("{27ABA825-35B5-4018-9FDD-F99250A0E722}"), 1.4954 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt]) 1.4955 +}; 1.4956 + 1.4957 +var components = [UpdateService, Checker, UpdatePrompt, UpdateManager]; 1.4958 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components); 1.4959 + 1.4960 +#if 0 1.4961 +/** 1.4962 + * Logs a message and stack trace to the console. 1.4963 + * @param string 1.4964 + * The string to write to the console. 1.4965 + */ 1.4966 +function STACK(string) { 1.4967 + dump("*** " + string + "\n"); 1.4968 + stackTrace(arguments.callee.caller.arguments, -1); 1.4969 +} 1.4970 + 1.4971 +function stackTraceFunctionFormat(aFunctionName) { 1.4972 + var classDelimiter = aFunctionName.indexOf("_"); 1.4973 + var className = aFunctionName.substr(0, classDelimiter); 1.4974 + if (!className) 1.4975 + className = "<global>"; 1.4976 + var functionName = aFunctionName.substr(classDelimiter + 1, aFunctionName.length); 1.4977 + if (!functionName) 1.4978 + functionName = "<anonymous>"; 1.4979 + return className + "::" + functionName; 1.4980 +} 1.4981 + 1.4982 +function stackTraceArgumentsFormat(aArguments) { 1.4983 + arglist = ""; 1.4984 + for (var i = 0; i < aArguments.length; i++) { 1.4985 + arglist += aArguments[i]; 1.4986 + if (i < aArguments.length - 1) 1.4987 + arglist += ", "; 1.4988 + } 1.4989 + return arglist; 1.4990 +} 1.4991 + 1.4992 +function stackTrace(aArguments, aMaxCount) { 1.4993 + dump("=[STACKTRACE]=====================================================\n"); 1.4994 + dump("*** at: " + stackTraceFunctionFormat(aArguments.callee.name) + "(" + 1.4995 + stackTraceArgumentsFormat(aArguments) + ")\n"); 1.4996 + var temp = aArguments.callee.caller; 1.4997 + var count = 0; 1.4998 + while (temp) { 1.4999 + dump("*** " + stackTraceFunctionFormat(temp.name) + "(" + 1.5000 + stackTraceArgumentsFormat(temp.arguments) + ")\n"); 1.5001 + 1.5002 + temp = temp.arguments.callee.caller; 1.5003 + if (aMaxCount > 0 && ++count == aMaxCount) 1.5004 + break; 1.5005 + } 1.5006 + dump("==================================================================\n"); 1.5007 +} 1.5008 + 1.5009 +function dumpFile(file) { 1.5010 + dump("*** file = " + file.path + ", exists = " + file.exists() + "\n"); 1.5011 +} 1.5012 +#endif