3 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
5 # This Source Code Form is subject to the terms of the Mozilla Public
6 # License, v. 2.0. If a copy of the MPL was not distributed with this
7 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
11 Components.utils.import("resource://gre/modules/FileUtils.jsm");
12 Components.utils.import("resource://gre/modules/AddonManager.jsm");
13 Components.utils.import("resource://gre/modules/Services.jsm");
14 Components.utils.import("resource://gre/modules/ctypes.jsm");
15 #ifdef MOZ_SERVICES_HEALTHREPORT
16 Components.utils.import("resource://gre/modules/UpdaterHealthProvider.jsm");
19 const Cc = Components.classes;
20 const Ci = Components.interfaces;
21 const Cr = Components.results;
22 const Cu = Components.utils;
24 const UPDATESERVICE_CID = Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}");
25 const UPDATESERVICE_CONTRACTID = "@mozilla.org/updates/update-service;1";
27 const PREF_APP_UPDATE_ALTWINDOWTYPE = "app.update.altwindowtype";
28 const PREF_APP_UPDATE_AUTO = "app.update.auto";
29 const PREF_APP_UPDATE_BACKGROUND_INTERVAL = "app.update.download.backgroundInterval";
30 const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
31 const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
32 const PREF_APP_UPDATE_CERTS_BRANCH = "app.update.certs.";
33 const PREF_APP_UPDATE_CERT_CHECKATTRS = "app.update.cert.checkAttributes";
34 const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors";
35 const PREF_APP_UPDATE_CERT_MAXERRORS = "app.update.cert.maxErrors";
36 const PREF_APP_UPDATE_CERT_REQUIREBUILTIN = "app.update.cert.requireBuiltIn";
37 const PREF_APP_UPDATE_CUSTOM = "app.update.custom";
38 const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
39 const PREF_APP_UPDATE_METRO_ENABLED = "app.update.metro.enabled";
40 const PREF_APP_UPDATE_IDLETIME = "app.update.idletime";
41 const PREF_APP_UPDATE_INCOMPATIBLE_MODE = "app.update.incompatible.mode";
42 const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
43 const PREF_APP_UPDATE_LASTUPDATETIME = "app.update.lastUpdateTime.background-update-timer";
44 const PREF_APP_UPDATE_LOG = "app.update.log";
45 const PREF_APP_UPDATE_MODE = "app.update.mode";
46 const PREF_APP_UPDATE_NEVER_BRANCH = "app.update.never.";
47 const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
48 const PREF_APP_UPDATE_POSTUPDATE = "app.update.postupdate";
49 const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
50 const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI";
51 const PREF_APP_UPDATE_SILENT = "app.update.silent";
52 const PREF_APP_UPDATE_STAGING_ENABLED = "app.update.staging.enabled";
53 const PREF_APP_UPDATE_URL = "app.update.url";
54 const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
55 const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override";
56 const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled";
57 const PREF_APP_UPDATE_SERVICE_ERRORS = "app.update.service.errors";
58 const PREF_APP_UPDATE_SERVICE_MAX_ERRORS = "app.update.service.maxErrors";
59 const PREF_APP_UPDATE_SOCKET_ERRORS = "app.update.socket.maxErrors";
60 const PREF_APP_UPDATE_RETRY_TIMEOUT = "app.update.socket.retryTimeout";
62 const PREF_APP_DISTRIBUTION = "distribution.id";
63 const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
65 const PREF_APP_B2G_VERSION = "b2g.version";
67 const PREF_EM_HOTFIX_ID = "extensions.hotfix.id";
69 const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
70 const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
71 const URI_BRAND_PROPERTIES = "chrome://branding/locale/brand.properties";
72 const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
73 const URI_UPDATE_NS = "http://www.mozilla.org/2005/app-update";
75 const CATEGORY_UPDATE_TIMER = "update-timer";
77 const KEY_GRED = "GreD";
78 const KEY_UPDROOT = "UpdRootD";
79 const KEY_EXECUTABLE = "XREExeF";
81 #ifdef MOZ_WIDGET_GONK
82 #define USE_UPDATE_ARCHIVE_DIR
85 #ifdef USE_UPDATE_ARCHIVE_DIR
86 const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD"
90 #define CHECK_CAN_USE_SERVICE
91 #elifdef MOZ_WIDGET_GONK
92 // In Gonk, the updater will remount the /system partition to move staged files
93 // into place, so we skip the test here to keep things isolated.
94 #define CHECK_CAN_USE_SERVICE
97 const DIR_UPDATES = "updates";
99 const UPDATED_DIR = "Updated.app";
101 const UPDATED_DIR = "updated";
103 const FILE_UPDATE_STATUS = "update.status";
104 const FILE_UPDATE_VERSION = "update.version";
105 #ifdef MOZ_WIDGET_ANDROID
106 const FILE_UPDATE_ARCHIVE = "update.apk";
108 const FILE_UPDATE_ARCHIVE = "update.mar";
110 const FILE_UPDATE_LINK = "update.link";
111 const FILE_UPDATE_LOG = "update.log";
112 const FILE_UPDATES_DB = "updates.xml";
113 const FILE_UPDATE_ACTIVE = "active-update.xml";
114 const FILE_PERMS_TEST = "update.test";
115 const FILE_LAST_LOG = "last-update.log";
116 const FILE_BACKUP_LOG = "backup-update.log";
117 const FILE_UPDATE_LOCALE = "update.locale";
119 const STATE_NONE = "null";
120 const STATE_DOWNLOADING = "downloading";
121 const STATE_PENDING = "pending";
122 const STATE_PENDING_SVC = "pending-service";
123 const STATE_APPLYING = "applying";
124 const STATE_APPLIED = "applied";
125 const STATE_APPLIED_OS = "applied-os";
126 const STATE_APPLIED_SVC = "applied-service";
127 const STATE_SUCCEEDED = "succeeded";
128 const STATE_DOWNLOAD_FAILED = "download-failed";
129 const STATE_FAILED = "failed";
131 // From updater/errors.h:
132 const WRITE_ERROR = 7;
133 // const UNEXPECTED_ERROR = 8; // Replaced with errors 38-42
134 const ELEVATION_CANCELED = 9;
136 // Windows service specific errors
137 const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 24;
138 const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 25;
139 const SERVICE_UPDATER_SIGN_ERROR = 26;
140 const SERVICE_UPDATER_COMPARE_ERROR = 27;
141 const SERVICE_UPDATER_IDENTITY_ERROR = 28;
142 const SERVICE_STILL_APPLYING_ON_SUCCESS = 29;
143 const SERVICE_STILL_APPLYING_ON_FAILURE = 30;
144 const SERVICE_UPDATER_NOT_FIXED_DRIVE = 31;
145 const SERVICE_COULD_NOT_LOCK_UPDATER = 32;
146 const SERVICE_INSTALLDIR_ERROR = 33;
147 const SERVICE_COULD_NOT_COPY_UPDATER = 49;
149 const WRITE_ERROR_ACCESS_DENIED = 35;
150 // const WRITE_ERROR_SHARING_VIOLATION = 36; // Replaced with errors 46-48
151 const WRITE_ERROR_CALLBACK_APP = 37;
152 const INVALID_UPDATER_STATUS_CODE = 38;
153 const UNEXPECTED_BZIP_ERROR = 39;
154 const UNEXPECTED_MAR_ERROR = 40;
155 const UNEXPECTED_BSPATCH_ERROR = 41;
156 const UNEXPECTED_FILE_OPERATION_ERROR = 42;
157 const FILESYSTEM_MOUNT_READWRITE_ERROR = 43;
158 const FOTA_GENERAL_ERROR = 44;
159 const FOTA_UNKNOWN_ERROR = 45;
160 const WRITE_ERROR_SHARING_VIOLATION_SIGNALED = 46;
161 const WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID = 47;
162 const WRITE_ERROR_SHARING_VIOLATION_NOPID = 48;
163 const FOTA_FILE_OPERATION_ERROR = 49;
164 const FOTA_RECOVERY_ERROR = 50;
166 const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100;
167 const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
168 const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
169 const NETWORK_ERROR_OFFLINE = 111;
170 const FILE_ERROR_TOO_BIG = 112;
172 // Error codes should be < 1000. Errors above 1000 represent http status codes
173 const HTTP_ERROR_OFFSET = 1000;
175 const DOWNLOAD_CHUNK_SIZE = 300000; // bytes
176 const DOWNLOAD_BACKGROUND_INTERVAL = 600; // seconds
177 const DOWNLOAD_FOREGROUND_INTERVAL = 0;
179 const UPDATE_WINDOW_NAME = "Update:Wizard";
181 // The number of consecutive failures when updating using the service before
182 // setting the app.update.service.enabled preference to false.
183 const DEFAULT_SERVICE_MAX_ERRORS = 10;
185 // The number of consecutive socket errors to allow before falling back to
186 // downloading a different MAR file or failing if already downloading the full.
187 const DEFAULT_SOCKET_MAX_ERRORS = 10;
189 // The number of milliseconds to wait before retrying a connection error.
190 const DEFAULT_UPDATE_RETRY_TIMEOUT = 2000;
192 // A background download is in progress (no notification)
193 const PING_BGUC_IS_DOWNLOADING = 0;
194 // An update is staged (no notification)
195 const PING_BGUC_IS_STAGED = 1;
196 // Invalid url for app.update.url default preference (no notification)
197 const PING_BGUC_INVALID_DEFAULT_URL = 2;
198 // Invalid url for app.update.url user preference (no notification)
199 const PING_BGUC_INVALID_CUSTOM_URL = 3;
200 // Invalid url for app.update.url.override user preference (no notification)
201 const PING_BGUC_INVALID_OVERRIDE_URL = 4;
202 // Unable to check for updates per gCanCheckForUpdates and hasUpdateMutex()
204 const PING_BGUC_UNABLE_TO_CHECK = 5;
205 // Already has an active update in progress (no notification)
206 const PING_BGUC_HAS_ACTIVEUPDATE = 6;
207 // Background checks disabled by preference (no notification)
208 const PING_BGUC_PREF_DISABLED = 7;
209 // Background checks disabled for the current session (no notification)
210 const PING_BGUC_DISABLED_FOR_SESSION = 8;
211 // Background checks disabled in Metro (no notification)
212 const PING_BGUC_METRO_DISABLED = 9;
213 // Unable to perform a background check while offline (no notification)
214 const PING_BGUC_OFFLINE = 10;
215 // No update found certificate check failed and threshold reached
216 // (possible mitm attack notification)
217 const PING_BGUC_CERT_ATTR_NO_UPDATE_NOTIFY = 11;
218 // No update found certificate check failed and threshold not reached
220 const PING_BGUC_CERT_ATTR_NO_UPDATE_SILENT = 12;
221 // Update found certificate check failed and threshold reached
222 // (possible mitm attack notification)
223 const PING_BGUC_CERT_ATTR_WITH_UPDATE_NOTIFY = 13;
224 // Update found certificate check failed and threshold not reached
226 const PING_BGUC_CERT_ATTR_WITH_UPDATE_SILENT = 14;
227 // General update check failure and threshold reached
228 // (check failure notification)
229 const PING_BGUC_GENERAL_ERROR_NOTIFY = 15;
230 // General update check failure and threshold not reached
232 const PING_BGUC_GENERAL_ERROR_SILENT = 16;
233 // No update found (no notification)
234 const PING_BGUC_NO_UPDATE_FOUND = 17;
235 // No compatible update found though there were updates (no notification)
236 const PING_BGUC_NO_COMPAT_UPDATE_FOUND = 18;
237 // Update found for a previous version (no notification)
238 const PING_BGUC_UPDATE_PREVIOUS_VERSION = 19;
239 // Update found for a version with the never preference set (no notification)
240 const PING_BGUC_UPDATE_NEVER_PREF = 20;
241 // Update found without a type attribute (no notification)
242 const PING_BGUC_UPDATE_INVALID_TYPE = 21;
243 // The system is no longer supported (system unsupported notification)
244 const PING_BGUC_UNSUPPORTED = 22;
245 // Unable to apply updates (manual install to update notification)
246 const PING_BGUC_UNABLE_TO_APPLY = 23;
247 // Showing prompt due to the update.xml specifying showPrompt
248 // (update notification)
249 const PING_BGUC_SHOWPROMPT_SNIPPET = 24;
250 // Showing prompt due to preference (update notification)
251 const PING_BGUC_SHOWPROMPT_PREF = 25;
252 // Incompatible add-on check disabled by preference (background download)
253 const PING_BGUC_ADDON_PREF_DISABLED = 26;
254 // Incompatible add-on not checked not performed due to same update version and
255 // app version (background download)
256 const PING_BGUC_ADDON_SAME_APP_VER = 27;
257 // No incompatible add-ons found during incompatible check (background download)
258 const PING_BGUC_CHECK_NO_INCOMPAT = 28;
259 // Incompatible add-ons found and all of them have updates (background download)
260 const PING_BGUC_ADDON_UPDATES_FOR_INCOMPAT = 29;
261 // Incompatible add-ons found (update notification)
262 const PING_BGUC_ADDON_HAVE_INCOMPAT = 30;
264 // Health report field names
265 const UpdaterHealthReportFields = {
266 CHECK_START: "updateCheckStart",
267 CHECK_SUCCESS: "updateCheckSuccess",
268 CHECK_FAILED: "updateCheckFailed",
269 COMPLETE_START: "completeUpdateStart",
270 PARTIAL_START: "partialUpdateStart",
271 COMPLETE_SUCCESS: "completeUpdateSuccess",
272 PARTIAL_SUCCESS: "partialUpdateSuccess",
273 FAILED: "updateFailed"
277 var gUpdateMutexHandle = null;
279 #ifdef MOZ_WIDGET_GONK
280 var gSDCardMountLock = null;
282 XPCOMUtils.defineLazyGetter(this, "gExtStorage", function aus_gExtStorage() {
283 return Services.env.get("EXTERNAL_STORAGE");
287 XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
288 "resource://gre/modules/UpdateChannel.jsm");
290 XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
291 return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
294 XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() {
295 return Services.strings.createBundle(URI_UPDATES_PROPERTIES);
298 // shared code for suppressing bad cert dialogs
299 XPCOMUtils.defineLazyGetter(this, "gCertUtils", function aus_gCertUtils() {
301 Cu.import("resource://gre/modules/CertUtils.jsm", temp);
305 XPCOMUtils.defineLazyGetter(this, "gABI", function aus_gABI() {
308 abi = Services.appinfo.XPCOMABI;
311 LOG("gABI - XPCOM ABI unknown: updates are not possible.");
314 // Mac universal build should report a different ABI than either macppc
316 let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
317 getService(Ci.nsIMacUtils);
319 if (macutils.isUniversalBinary)
320 abi += "-u-" + macutils.architecturesInBinary;
322 // Disambiguate optimised and shark nightlies
329 #ifdef MOZ_WIDGET_GONK
330 XPCOMUtils.defineLazyGetter(this, "gProductModel", function aus_gProductModel() {
331 Cu.import("resource://gre/modules/systemlibs.js");
332 return libcutils.property_get("ro.product.model");
334 XPCOMUtils.defineLazyGetter(this, "gProductDevice", function aus_gProductDevice() {
335 Cu.import("resource://gre/modules/systemlibs.js");
336 return libcutils.property_get("ro.product.device");
340 XPCOMUtils.defineLazyGetter(this, "gOSVersion", function aus_gOSVersion() {
342 let sysInfo = Cc["@mozilla.org/system-info;1"].
343 getService(Ci.nsIPropertyBag2);
345 osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
348 LOG("gOSVersion - OS Version unknown: updates are not possible.");
353 const BYTE = ctypes.uint8_t;
354 const WORD = ctypes.uint16_t;
355 const DWORD = ctypes.uint32_t;
356 const WCHAR = ctypes.jschar;
357 const BOOL = ctypes.int;
359 // This structure is described at:
360 // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
361 const SZCSDVERSIONLENGTH = 128;
362 const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
364 {dwOSVersionInfoSize: DWORD},
365 {dwMajorVersion: DWORD},
366 {dwMinorVersion: DWORD},
367 {dwBuildNumber: DWORD},
368 {dwPlatformId: DWORD},
369 {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
370 {wServicePackMajor: WORD},
371 {wServicePackMinor: WORD},
373 {wProductType: BYTE},
377 // This structure is described at:
378 // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
379 const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
381 {wProcessorArchitecture: WORD},
384 {lpMinimumApplicationAddress: ctypes.voidptr_t},
385 {lpMaximumApplicationAddress: ctypes.voidptr_t},
386 {dwActiveProcessorMask: DWORD.ptr},
387 {dwNumberOfProcessors: DWORD},
388 {dwProcessorType: DWORD},
389 {dwAllocationGranularity: DWORD},
390 {wProcessorLevel: WORD},
391 {wProcessorRevision: WORD}
394 let kernel32 = false;
396 kernel32 = ctypes.open("Kernel32");
398 LOG("gOSVersion - Unable to open kernel32! " + e);
399 osVersion += ".unknown (unknown)";
404 // Get Service pack info
406 let GetVersionEx = kernel32.declare("GetVersionExW",
409 OSVERSIONINFOEXW.ptr);
410 let winVer = OSVERSIONINFOEXW();
411 winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
413 if(0 !== GetVersionEx(winVer.address())) {
414 osVersion += "." + winVer.wServicePackMajor
415 + "." + winVer.wServicePackMinor;
417 LOG("gOSVersion - Unknown failure in GetVersionEX (returned 0)");
418 osVersion += ".unknown";
421 LOG("gOSVersion - error getting service pack information. Exception: " + e);
422 osVersion += ".unknown";
425 // Get processor architecture
426 let arch = "unknown";
428 let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
432 let sysInfo = SYSTEM_INFO();
433 // Default to unknown
434 sysInfo.wProcessorArchitecture = 0xffff;
436 GetNativeSystemInfo(sysInfo.address());
437 switch(sysInfo.wProcessorArchitecture) {
449 LOG("gOSVersion - error getting processor architecture. Exception: " + e);
451 osVersion += " (" + arch + ")";
460 osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
463 // Not all platforms have a secondary widget library, so an error is nothing to worry about.
465 osVersion = encodeURIComponent(osVersion);
471 * Tests to make sure that we can write to a given directory.
473 * @param updateTestFile a test file in the directory that needs to be tested.
474 * @param createDirectory whether a test directory should be created.
475 * @throws if we don't have right access to the directory.
477 function testWriteAccess(updateTestFile, createDirectory) {
478 const NORMAL_FILE_TYPE = Ci.nsILocalFile.NORMAL_FILE_TYPE;
479 const DIRECTORY_TYPE = Ci.nsILocalFile.DIRECTORY_TYPE;
480 if (updateTestFile.exists())
481 updateTestFile.remove(false);
482 updateTestFile.create(createDirectory ? DIRECTORY_TYPE : NORMAL_FILE_TYPE,
483 createDirectory ? FileUtils.PERMS_DIRECTORY : FileUtils.PERMS_FILE);
484 updateTestFile.remove(false);
490 * Closes a Win32 handle
492 * @param handle The handle to close
494 function closeHandle(handle) {
495 var lib = ctypes.open("kernel32.dll");
496 var CloseHandle = lib.declare("CloseHandle",
498 ctypes.int32_t, /* success */
499 ctypes.void_t.ptr); /* handle */
508 * The name for the mutex.
509 * @param aAllowExisting
510 * If false the function will close the handle and return null.
511 * @return The Win32 handle to the mutex.
513 function createMutex(aName, aAllowExisting) {
514 if (aAllowExisting === undefined) {
515 aAllowExisting = true;
518 const INITIAL_OWN = 1;
519 const ERROR_ALREADY_EXISTS = 0xB7;
520 var lib = ctypes.open("kernel32.dll");
521 var CreateMutexW = lib.declare("CreateMutexW",
523 ctypes.void_t.ptr, /* return handle */
524 ctypes.void_t.ptr, /* security attributes */
525 ctypes.int32_t, /* initial owner */
526 ctypes.jschar.ptr); /* name */
528 var handle = CreateMutexW(null, INITIAL_OWN, aName);
529 var alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS;
530 if (handle && !handle.isNull() && !aAllowExisting && alreadyExists) {
536 if (handle && handle.isNull())
543 * Determines a unique mutex name for the installation
545 * @param aGlobal true if the function should return a global mutex. A global
546 * mutex is valid across different sessions
547 * @return Global mutex path
549 function getPerInstallationMutexName(aGlobal) {
550 if (aGlobal === undefined) {
553 let hasher = Cc["@mozilla.org/security/hash;1"].
554 createInstance(Ci.nsICryptoHash);
555 hasher.init(hasher.SHA1);
557 var exeFile = Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsILocalFile);
559 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
560 createInstance(Ci.nsIScriptableUnicodeConverter);
561 converter.charset = "UTF-8";
562 var data = converter.convertToByteArray(exeFile.path.toLowerCase());
564 hasher.update(data, data.length);
565 return (aGlobal ? "Global\\" : "") + "MozillaUpdateMutex-" + hasher.finish(true);
570 * Whether or not the current instance has the update mutex. The update mutex
571 * gives protection against 2 applications from the same installation updating:
572 * 1) Running multiple profiles from the same installation path
573 * 2) Running a Metro and Desktop application at the same time from the same
575 * 3) 2 applications running in 2 different user sessions from the same path
577 * @return true if this instance holds the update mutex
579 function hasUpdateMutex() {
581 if (!gUpdateMutexHandle) {
582 gUpdateMutexHandle = createMutex(getPerInstallationMutexName(true), false);
585 return !!gUpdateMutexHandle;
591 XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() {
592 function submitHasPermissionsTelemetryPing(val) {
594 let h = Services.telemetry.getHistogramById("UPDATER_HAS_PERMISSIONS");
597 // Don't allow any exception to be propagated.
598 Components.utils.reportError(e);
602 let useService = false;
603 if (shouldUseService() && isServiceInstalled()) {
604 // No need to perform directory write checks, the maintenance service will
605 // be able to write to all directories.
606 LOG("gCanApplyUpdates - bypass the write checks because we'll use the service");
612 var updateTestFile = getUpdateFile([FILE_PERMS_TEST]);
613 LOG("gCanApplyUpdates - testing write access " + updateTestFile.path);
614 testWriteAccess(updateTestFile, false);
616 // Check that the application bundle can be written to.
617 var appDirTestFile = getAppBaseDir();
618 appDirTestFile.append(FILE_PERMS_TEST);
619 LOG("gCanApplyUpdates - testing write access " + appDirTestFile.path);
620 if (appDirTestFile.exists()) {
621 appDirTestFile.remove(false)
623 appDirTestFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
624 appDirTestFile.remove(false);
626 var sysInfo = Cc["@mozilla.org/system-info;1"].
627 getService(Ci.nsIPropertyBag2);
629 // Example windowsVersion: Windows XP == 5.1
630 var windowsVersion = sysInfo.getProperty("version");
631 LOG("gCanApplyUpdates - windowsVersion = " + windowsVersion);
634 # For Vista, updates can be performed to a location requiring admin
635 # privileges by requesting elevation via the UAC prompt when launching
636 # updater.exe if the appDir is under the Program Files directory
637 # (e.g. C:\Program Files\) and UAC is turned on and we can elevate
638 # (e.g. user has a split token).
640 # Note: this does note attempt to handle the case where UAC is turned on
641 # and the installation directory is in a restricted location that
642 # requires admin privileges to update other than Program Files.
644 var userCanElevate = false;
646 if (parseFloat(windowsVersion) >= 6) {
648 var fileLocator = Cc["@mozilla.org/file/directory_service;1"].
649 getService(Ci.nsIProperties);
650 // KEY_UPDROOT will fail and throw an exception if
651 // appDir is not under the Program Files, so we rely on that
652 var dir = fileLocator.get(KEY_UPDROOT, Ci.nsIFile);
653 // appDir is under Program Files, so check if the user can elevate
654 userCanElevate = Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).
656 LOG("gCanApplyUpdates - on Vista, userCanElevate: " + userCanElevate);
659 // When the installation directory is not under Program Files,
660 // fall through to checking if write access to the
661 // installation directory is available.
662 LOG("gCanApplyUpdates - on Vista, appDir is not under Program Files");
667 # On Windows, we no longer store the update under the app dir.
669 # If we are on Windows (including Vista, if we can't elevate) we need to
670 # to check that we can create and remove files from the actual app
671 # directory (like C:\Program Files\Mozilla Firefox). If we can't
672 # (because this user is not an adminstrator, for example) canUpdate()
673 # should return false.
675 # For Vista, we perform this check to enable updating the application
676 # when the user has write access to the installation directory under the
677 # following scenarios:
678 # 1) the installation directory is not under Program Files
679 # (e.g. C:\Program Files)
680 # 2) UAC is turned off
681 # 3) UAC is turned on and the user is not an admin
682 # (e.g. the user does not have a split token)
683 # 4) UAC is turned on and the user is already elevated, so they can't be
686 if (!userCanElevate) {
687 // if we're unable to create the test file this will throw an exception.
688 var appDirTestFile = getAppBaseDir();
689 appDirTestFile.append(FILE_PERMS_TEST);
690 LOG("gCanApplyUpdates - testing write access " + appDirTestFile.path);
691 if (appDirTestFile.exists())
692 appDirTestFile.remove(false)
693 appDirTestFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
694 appDirTestFile.remove(false);
699 LOG("gCanApplyUpdates - unable to apply updates. Exception: " + e);
700 // No write privileges to install directory
701 submitHasPermissionsTelemetryPing(false);
704 } // if (!useService)
706 LOG("gCanApplyUpdates - able to apply updates");
707 submitHasPermissionsTelemetryPing(true);
712 * Whether or not the application can stage an update.
714 * @return true if updates can be staged.
716 function getCanStageUpdates() {
717 // If background updates are disabled, then just bail out!
718 if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGING_ENABLED, false)) {
719 LOG("getCanStageUpdates - staging updates is disabled by preference " +
720 PREF_APP_UPDATE_STAGING_ENABLED);
724 #ifdef CHECK_CAN_USE_SERVICE
725 if (getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false)) {
726 // No need to perform directory write checks, the maintenance service will
727 // be able to write to all directories.
728 LOG("getCanStageUpdates - able to stage updates because we'll use the service");
733 if (!hasUpdateMutex()) {
734 LOG("getCanStageUpdates - unable to apply updates because another " +
735 "instance of the application is already handling updates for this " +
741 * Whether or not the application can stage an update for the current session.
742 * These checks are only performed once per session due to using a lazy getter.
744 * @return true if updates can be staged for this session.
746 XPCOMUtils.defineLazyGetter(this, "canStageUpdatesSession", function canStageUpdatesSession() {
748 var updateTestFile = getInstallDirRoot();
749 updateTestFile.append(FILE_PERMS_TEST);
750 LOG("canStageUpdatesSession - testing write access " +
751 updateTestFile.path);
752 testWriteAccess(updateTestFile, true);
754 // On all platforms except Mac, we need to test the parent directory as
755 // well, as we need to be able to move files in that directory during the
757 updateTestFile = getInstallDirRoot().parent;
758 updateTestFile.append(FILE_PERMS_TEST);
759 LOG("canStageUpdatesSession - testing write access " +
760 updateTestFile.path);
761 updateTestFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE,
762 FileUtils.PERMS_DIRECTORY);
763 updateTestFile.remove(false);
767 LOG("canStageUpdatesSession - unable to stage updates. Exception: " +
769 // No write privileges
773 LOG("canStageUpdatesSession - able to stage updates");
777 return canStageUpdatesSession;
780 XPCOMUtils.defineLazyGetter(this, "gMetroUpdatesEnabled", function aus_gMetroUpdatesEnabled() {
783 if (Services.metro && Services.metro.immersive) {
784 let metroUpdate = getPref("getBoolPref", PREF_APP_UPDATE_METRO_ENABLED, true);
786 LOG("gMetroUpdatesEnabled - unable to automatically check for metro " +
787 "updates, disabled by pref");
797 XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckForUpdates() {
798 // If the administrator has disabled app update and locked the preference so
799 // users can't check for updates. This preference check is ok in this lazy
800 // getter since locked prefs don't change until the application is restarted.
801 var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
802 if (!enabled && Services.prefs.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
803 LOG("gCanCheckForUpdates - unable to automatically check for updates, " +
804 "the preference is disabled and admistratively locked.");
808 if (!gMetroUpdatesEnabled) {
812 // If we don't know the binary platform we're updating, we can't update.
814 LOG("gCanCheckForUpdates - unable to check for updates, unknown ABI");
818 // If we don't know the OS version we're updating, we can't update.
820 LOG("gCanCheckForUpdates - unable to check for updates, unknown OS " +
825 LOG("gCanCheckForUpdates - able to check for updates");
830 * Logs a string to the error console.
832 * The string to write to the error console.
834 function LOG(string) {
836 dump("*** AUS:SVC " + string + "\n");
837 Services.console.logStringMessage("AUS:SVC " + string);
842 # Gets a preference value, handling the case where there is no default.
844 # The name of the preference function to call, on nsIPrefBranch
846 # The name of the preference
847 # @param defaultValue
848 # The default value to return in the event the preference has
850 # @return The value of the preference, or undefined if there was no
851 # user or default value.
853 function getPref(func, preference, defaultValue) {
855 return Services.prefs[func](preference);
863 * Convert a string containing binary values to hex.
865 function binaryToHex(input) {
867 for (var i = 0; i < input.length; ++i) {
868 var hex = input.charCodeAt(i).toString(16);
877 # Gets the specified directory at the specified hierarchy under the
878 # update root directory and creates it if it doesn't exist.
880 # An array of path components to locate beneath the directory
882 # @return nsIFile object for the location specified.
884 function getUpdateDirCreate(pathArray) {
885 return FileUtils.getDir(KEY_UPDROOT, pathArray, true);
889 # Gets the specified directory at the specified hierarchy under the
890 # update root directory and without creating it if it doesn't exist.
892 # An array of path components to locate beneath the directory
894 # @return nsIFile object for the location specified.
896 function getUpdateDirNoCreate(pathArray) {
897 return FileUtils.getDir(KEY_UPDROOT, pathArray, false);
901 * Gets the application base directory.
903 * @return nsIFile object for the application base directory.
905 function getAppBaseDir() {
906 return Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsIFile).parent;
910 * Gets the root of the installation directory which is the application
911 * bundle directory on Mac OS X and the location of the application binary
912 * on all other platforms.
914 * @return nsIFile object for the directory
916 function getInstallDirRoot() {
917 var dir = getAppBaseDir();
919 // On Mac, we store the Updated.app directory inside the bundle directory.
920 dir = dir.parent.parent;
926 * Gets the file at the specified hierarchy under the update root directory.
928 * An array of path components to locate beneath the directory
929 * specified by |key|. The last item in this array must be the
930 * leaf name of a file.
931 * @return nsIFile object for the file specified. The file is NOT created
932 * if it does not exist, however all required directories along
935 function getUpdateFile(pathArray) {
936 var file = getUpdateDirCreate(pathArray.slice(0, -1));
937 file.append(pathArray[pathArray.length - 1]);
942 * Returns human readable status text from the updates.properties bundle
943 * based on an error code
945 * The error code to look up human readable status text for
947 * The default code to look up should human readable status text
948 * not exist for |code|
949 * @return A human readable status text string
951 function getStatusTextFromCode(code, defaultCode) {
954 reason = gUpdateBundle.GetStringFromName("check_error-" + code);
955 LOG("getStatusTextFromCode - transfer error: " + reason + ", code: " +
959 // Use the default reason
960 reason = gUpdateBundle.GetStringFromName("check_error-" + defaultCode);
961 LOG("getStatusTextFromCode - transfer error: " + reason +
962 ", default code: " + defaultCode);
968 * Record count in the health report.
970 * The field name to record
972 * Status code for errors, 0 otherwise
974 function recordInHealthReport(field, status) {
975 #ifdef MOZ_SERVICES_HEALTHREPORT
977 LOG("recordInHealthReport - " + field + " - " + status);
979 let reporter = Cc["@mozilla.org/datareporting/service;1"]
980 .getService().wrappedJSObject.healthReporter;
983 reporter.onInit().then(function recordUpdateInHealthReport() {
985 reporter.getProvider("org.mozilla.update").recordUpdate(field, status);
991 // If getting the heath reporter service fails, don't fail updating.
993 LOG("recordInHealthReport - could not initialize health reporter");
999 * Get the Active Updates directory
1000 * @return The active updates directory, as a nsIFile object
1002 function getUpdatesDir() {
1003 // Right now, we only support downloading one patch at a time, so we always
1004 // use the same target directory.
1005 return getUpdateDirCreate([DIR_UPDATES, "0"]);
1009 * Get the Active Updates directory inside the directory where we apply the
1011 * @return The active updates directory inside the updated directory, as a
1014 function getUpdatesDirInApplyToDir() {
1015 var dir = getAppBaseDir();
1017 dir = dir.parent.parent; // the bundle directory
1019 dir.append(UPDATED_DIR);
1021 dir.append("Contents");
1022 dir.append("MacOS");
1024 dir.append(DIR_UPDATES);
1025 if (!dir.exists()) {
1026 dir.create(Ci.nsILocalFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
1032 * Reads the update state from the update.status file in the specified
1035 * The dir to look for an update.status file in
1036 * @return The status value of the update.
1038 function readStatusFile(dir) {
1039 var statusFile = dir.clone();
1040 statusFile.append(FILE_UPDATE_STATUS);
1041 var status = readStringFromFile(statusFile) || STATE_NONE;
1042 LOG("readStatusFile - status: " + status + ", path: " + statusFile.path);
1047 * Writes the current update operation/state to a file in the patch
1048 * directory, indicating to the patching system that operations need
1051 * The patch directory where the update.status file should be
1054 * The state value to write.
1056 function writeStatusFile(dir, state) {
1057 var statusFile = dir.clone();
1058 statusFile.append(FILE_UPDATE_STATUS);
1059 writeStringToFile(statusFile, state);
1062 #ifdef MOZ_WIDGET_GONK
1064 * Reads the link file specified in the update.link file in the
1065 * specified directory and returns the nsIFile for the
1066 * corresponding file.
1068 * The dir to look for an update.link file in
1069 * @return A nsIFile for the file path specified in the
1070 * update.link file or null if the update.link file
1073 function getFileFromUpdateLink(dir) {
1074 var linkFile = dir.clone();
1075 linkFile.append(FILE_UPDATE_LINK);
1076 var link = readStringFromFile(linkFile);
1077 LOG("getFileFromUpdateLink linkFile.path: " + linkFile.path + ", link: " + link);
1081 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
1082 file.initWithPath(link);
1087 * Creates a link file, which allows the actual patch to live in
1088 * a directory different from the update directory.
1090 * The patch directory where the update.link file
1091 * should be written.
1093 * The fully qualified filename of the patchfile.
1095 function writeLinkFile(dir, patchFile) {
1096 var linkFile = dir.clone();
1097 linkFile.append(FILE_UPDATE_LINK);
1098 writeStringToFile(linkFile, patchFile.path);
1099 if (patchFile.path.indexOf(gExtStorage) == 0) {
1100 // The patchfile is being stored on external storage. Try to lock it
1101 // so that it doesn't get shared with the PC while we're downloading
1103 acquireSDCardMountLock();
1108 * Acquires a VolumeMountLock for the sdcard volume.
1110 * This prevents the SDCard from being shared with the PC while
1111 * we're downloading the update.
1113 function acquireSDCardMountLock() {
1114 let volsvc = Cc["@mozilla.org/telephony/volume-service;1"].
1115 getService(Ci.nsIVolumeService);
1117 gSDCardMountLock = volsvc.createMountLock("sdcard");
1122 * Determines if the state corresponds to an interrupted update.
1123 * This could either be because the download was interrupted, or
1124 * because staging the update was interrupted.
1126 * @return true if the state corresponds to an interrupted
1129 function isInterruptedUpdate(status) {
1130 return (status == STATE_DOWNLOADING) ||
1131 (status == STATE_PENDING) ||
1132 (status == STATE_APPLYING);
1134 #endif // MOZ_WIDGET_GONK
1137 * Releases any SDCard mount lock that we might have.
1139 * This once again allows the SDCard to be shared with the PC.
1141 * This function was placed outside the #ifdef so that we didn't
1142 * need to put #ifdefs around the callers.
1144 function releaseSDCardMountLock() {
1145 #ifdef MOZ_WIDGET_GONK
1146 if (gSDCardMountLock) {
1147 gSDCardMountLock.unlock();
1148 gSDCardMountLock = null;
1154 * Determines if the service should be used to attempt an update
1155 * or not. For now this is only when PREF_APP_UPDATE_SERVICE_ENABLED
1156 * is true and we have Firefox.
1158 * @return true if the service should be used for updates.
1160 function shouldUseService() {
1161 #ifdef MOZ_MAINTENANCE_SERVICE
1162 return getPref("getBoolPref",
1163 PREF_APP_UPDATE_SERVICE_ENABLED, false);
1170 * Determines if the service is is installed and enabled or not.
1172 * @return true if the service should be used for updates,
1173 * is installed and enabled.
1175 function isServiceInstalled() {
1179 let wrk = Cc["@mozilla.org/windows-registry-key;1"].
1180 createInstance(Ci.nsIWindowsRegKey);
1181 wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
1182 "SOFTWARE\\Mozilla\\MaintenanceService",
1183 wrk.ACCESS_READ | wrk.WOW64_64);
1184 installed = wrk.readIntValue("Installed");
1188 installed = installed == 1; // convert to bool
1189 LOG("isServiceInstalled = " + installed);
1197 # Writes the update's application version to a file in the patch directory. If
1198 # the update doesn't provide application version information via the
1199 # appVersion attribute the string "null" will be written to the file.
1200 # This value is compared during startup (in nsUpdateDriver.cpp) to determine if
1201 # the update should be applied. Note that this won't provide protection from
1202 # downgrade of the application for the nightly user case where the application
1203 # version doesn't change.
1205 # The patch directory where the update.version file should be
1208 # The version value to write. Will be the string "null" when the
1209 # update doesn't provide the appVersion attribute in the update xml.
1211 function writeVersionFile(dir, version) {
1212 var versionFile = dir.clone();
1213 versionFile.append(FILE_UPDATE_VERSION);
1214 writeStringToFile(versionFile, version);
1218 * Removes the MozUpdater folders that bgupdates/staged updates creates.
1220 function cleanUpMozUpdaterDirs() {
1222 var tmpDir = Cc["@mozilla.org/file/directory_service;1"].
1223 getService(Ci.nsIProperties).
1224 get("TmpD", Ci.nsIFile);
1226 // We used to store MozUpdater-i folders directly inside the temp directory.
1227 // We need to cleanup these directories if we detect that they still exist.
1228 // To check if they still exist, we simply check for MozUpdater-1.
1229 var mozUpdaterDir1 = tmpDir.clone();
1230 mozUpdaterDir1.append("MozUpdater-1");
1231 // Only try to delete the left over folders in "$Temp/MozUpdater-i/*" if
1232 // MozUpdater-1 exists.
1233 if (mozUpdaterDir1.exists()) {
1234 LOG("cleanUpMozUpdaterDirs - Cleaning top level MozUpdater-i folders");
1236 let dirEntries = tmpDir.directoryEntries;
1237 while (dirEntries.hasMoreElements() && i < 10) {
1238 let file = dirEntries.getNext().QueryInterface(Ci.nsILocalFile);
1239 if (file.leafName.startsWith("MozUpdater-") && file.leafName != "MozUpdater-1") {
1244 // If you enumerate the whole temp directory and the count of deleted
1245 // items is less than 10, then delete MozUpdate-1.
1247 mozUpdaterDir1.remove(true);
1251 // If we reach here, we simply need to clean the MozUpdater folder. In our
1252 // new way of storing these files, the unique subfolders are inside MozUpdater
1253 var mozUpdaterDir = tmpDir.clone();
1254 mozUpdaterDir.append("MozUpdater");
1255 if (mozUpdaterDir.exists()) {
1256 LOG("cleanUpMozUpdaterDirs - Cleaning MozUpdater folder");
1257 mozUpdaterDir.remove(true);
1260 LOG("cleanUpMozUpdaterDirs - Exception: " + e);
1265 * Removes the contents of the Updates Directory
1267 * @param aBackgroundUpdate Whether the update has been performed in the
1268 * background. If this is true, we move the update log file to the
1269 * updated directory, so that it survives replacing the directories
1272 function cleanUpUpdatesDir(aBackgroundUpdate) {
1273 // Bail out if we don't have appropriate permissions
1275 var updateDir = getUpdatesDir();
1280 // Preserve the last update log file for debugging purposes.
1281 let file = updateDir.clone();
1282 file.append(FILE_UPDATE_LOG);
1283 if (file.exists()) {
1285 if (aBackgroundUpdate && getUpdateDirNoCreate([]).equals(getAppBaseDir())) {
1286 dir = getUpdatesDirInApplyToDir();
1288 dir = updateDir.parent;
1290 let logFile = dir.clone();
1291 logFile.append(FILE_LAST_LOG);
1292 if (logFile.exists()) {
1294 logFile.moveTo(dir, FILE_BACKUP_LOG);
1296 LOG("cleanUpUpdatesDir - failed to rename file " + logFile.path +
1297 " to " + FILE_BACKUP_LOG);
1302 file.moveTo(dir, FILE_LAST_LOG);
1304 LOG("cleanUpUpdatesDir - failed to rename file " + file.path +
1305 " to " + FILE_LAST_LOG);
1309 if (!aBackgroundUpdate) {
1310 let e = updateDir.directoryEntries;
1311 while (e.hasMoreElements()) {
1312 let f = e.getNext().QueryInterface(Ci.nsIFile);
1313 #ifdef MOZ_WIDGET_GONK
1314 if (f.leafName == FILE_UPDATE_LINK) {
1315 let linkedFile = getFileFromUpdateLink(updateDir);
1316 if (linkedFile && linkedFile.exists()) {
1317 linkedFile.remove(false);
1322 // Now, recursively remove this file. The recursive removal is needed for
1323 // Mac OSX because this directory will contain a copy of updater.app,
1324 // which is itself a directory.
1328 LOG("cleanUpUpdatesDir - failed to remove file " + f.path);
1332 releaseSDCardMountLock();
1336 * Clean up updates list and the updates directory.
1338 function cleanupActiveUpdate() {
1339 // Move the update from the Active Update list into the Past Updates list.
1340 var um = Cc["@mozilla.org/updates/update-manager;1"].
1341 getService(Ci.nsIUpdateManager);
1342 um.activeUpdate = null;
1345 // Now trash the updates directory, since we're done with it
1346 cleanUpUpdatesDir();
1350 * Gets the locale from the update.locale file for replacing %LOCALE% in the
1351 * update url. The update.locale file can be located in the application
1352 * directory or the GRE directory with preference given to it being located in
1353 * the application directory.
1355 function getLocale() {
1359 for (let res of ['app', 'gre']) {
1360 var channel = Services.io.newChannel("resource://" + res + "/" + FILE_UPDATE_LOCALE, null, null);
1362 var inputStream = channel.open();
1363 gLocale = readStringFromInputStream(inputStream);
1370 throw Components.Exception(FILE_UPDATE_LOCALE + " file doesn't exist in " +
1371 "either the application or GRE directories",
1372 Cr.NS_ERROR_FILE_NOT_FOUND);
1374 LOG("getLocale - getting locale from file: " + channel.originalURI.spec +
1375 ", locale: " + gLocale);
1379 /* Get the distribution pref values, from defaults only */
1380 function getDistributionPrefValue(aPrefName) {
1381 var prefValue = "default";
1384 prefValue = Services.prefs.getDefaultBranch(null).getCharPref(aPrefName);
1386 // use default when pref not found
1393 * An enumeration of items in a JS array.
1396 function ArrayEnumerator(aItems) {
1399 for (var i = 0; i < aItems.length; ++i) {
1401 aItems.splice(i, 1);
1404 this._contents = aItems;
1407 ArrayEnumerator.prototype = {
1411 hasMoreElements: function ArrayEnumerator_hasMoreElements() {
1412 return this._index < this._contents.length;
1415 getNext: function ArrayEnumerator_getNext() {
1416 return this._contents[this._index++];
1421 * Writes a string of text to a file. A newline will be appended to the data
1422 * written to the file. This function only works with ASCII text.
1424 function writeStringToFile(file, text) {
1425 var fos = FileUtils.openSafeFileOutputStream(file)
1427 fos.write(text, text.length);
1428 FileUtils.closeSafeFileOutputStream(fos);
1431 function readStringFromInputStream(inputStream) {
1432 var sis = Cc["@mozilla.org/scriptableinputstream;1"].
1433 createInstance(Ci.nsIScriptableInputStream);
1434 sis.init(inputStream);
1435 var text = sis.read(sis.available());
1437 if (text[text.length - 1] == "\n")
1438 text = text.slice(0, -1);
1443 * Reads a string of text from a file. A trailing newline will be removed
1444 * before the result is returned. This function only works with ASCII text.
1446 function readStringFromFile(file) {
1447 if (!file.exists()) {
1448 LOG("readStringFromFile - file doesn't exist: " + file.path);
1451 var fis = Cc["@mozilla.org/network/file-input-stream;1"].
1452 createInstance(Ci.nsIFileInputStream);
1453 fis.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
1454 return readStringFromInputStream(fis);
1457 function handleUpdateFailure(update, errorCode) {
1458 update.errorCode = parseInt(errorCode);
1459 if (update.errorCode == FOTA_GENERAL_ERROR ||
1460 update.errorCode == FOTA_FILE_OPERATION_ERROR ||
1461 update.errorCode == FOTA_RECOVERY_ERROR ||
1462 update.errorCode == FOTA_UNKNOWN_ERROR) {
1463 // In the case of FOTA update errors, don't reset the state to pending. This
1464 // causes the FOTA update path to try again, which is not necessarily what
1466 update.statusText = gUpdateBundle.GetStringFromName("statusFailed");
1468 Cc["@mozilla.org/updates/update-prompt;1"].
1469 createInstance(Ci.nsIUpdatePrompt).
1470 showUpdateError(update);
1471 writeStatusFile(getUpdatesDir(), STATE_FAILED + ": " + errorCode);
1472 cleanupActiveUpdate();
1476 if (update.errorCode == WRITE_ERROR ||
1477 update.errorCode == WRITE_ERROR_ACCESS_DENIED ||
1478 update.errorCode == WRITE_ERROR_SHARING_VIOLATION_SIGNALED ||
1479 update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID ||
1480 update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPID ||
1481 update.errorCode == WRITE_ERROR_CALLBACK_APP ||
1482 update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR) {
1483 Cc["@mozilla.org/updates/update-prompt;1"].
1484 createInstance(Ci.nsIUpdatePrompt).
1485 showUpdateError(update);
1486 writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
1490 if (update.errorCode == ELEVATION_CANCELED) {
1491 writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
1495 if (update.errorCode == SERVICE_UPDATER_COULD_NOT_BE_STARTED ||
1496 update.errorCode == SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS ||
1497 update.errorCode == SERVICE_UPDATER_SIGN_ERROR ||
1498 update.errorCode == SERVICE_UPDATER_COMPARE_ERROR ||
1499 update.errorCode == SERVICE_UPDATER_IDENTITY_ERROR ||
1500 update.errorCode == SERVICE_STILL_APPLYING_ON_SUCCESS ||
1501 update.errorCode == SERVICE_STILL_APPLYING_ON_FAILURE ||
1502 update.errorCode == SERVICE_UPDATER_NOT_FIXED_DRIVE ||
1503 update.errorCode == SERVICE_COULD_NOT_LOCK_UPDATER ||
1504 update.errorCode == SERVICE_COULD_NOT_COPY_UPDATER ||
1505 update.errorCode == SERVICE_INSTALLDIR_ERROR) {
1507 var failCount = getPref("getIntPref",
1508 PREF_APP_UPDATE_SERVICE_ERRORS, 0);
1509 var maxFail = getPref("getIntPref",
1510 PREF_APP_UPDATE_SERVICE_MAX_ERRORS,
1511 DEFAULT_SERVICE_MAX_ERRORS);
1513 // As a safety, when the service reaches maximum failures, it will
1514 // disable itself and fallback to using the normal update mechanism
1515 // without the service.
1516 if (failCount >= maxFail) {
1517 Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false);
1518 Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS);
1521 Services.prefs.setIntPref(PREF_APP_UPDATE_SERVICE_ERRORS,
1525 writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
1527 Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE").
1528 add(update.errorCode);
1537 Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE").add(0);
1546 * Fall back to downloading a complete update in case an update has failed.
1548 * @param update the update object that has failed to apply.
1549 * @param postStaging true if we have just attempted to stage an update.
1551 function handleFallbackToCompleteUpdate(update, postStaging) {
1552 cleanupActiveUpdate();
1554 update.statusText = gUpdateBundle.GetStringFromName("patchApplyFailure");
1555 var oldType = update.selectedPatch ? update.selectedPatch.type
1557 if (update.selectedPatch && oldType == "partial" && update.patchCount == 2) {
1558 // Partial patch application failed, try downloading the complete
1559 // update in the background instead.
1560 LOG("handleFallbackToCompleteUpdate - install of partial patch " +
1561 "failed, downloading complete patch");
1562 var status = Cc["@mozilla.org/updates/update-service;1"].
1563 getService(Ci.nsIApplicationUpdateService).
1564 downloadUpdate(update, !postStaging);
1565 if (status == STATE_NONE)
1566 cleanupActiveUpdate();
1569 LOG("handleFallbackToCompleteUpdate - install of complete or " +
1570 "only one patch offered failed.");
1572 update.QueryInterface(Ci.nsIWritablePropertyBag);
1573 update.setProperty("patchingFailed", oldType);
1579 * A <patch> element to initialize this object with
1580 * @throws if patch has a size of 0
1583 function UpdatePatch(patch) {
1584 this._properties = {};
1585 for (var i = 0; i < patch.attributes.length; ++i) {
1586 var attr = patch.attributes.item(i);
1587 attr.QueryInterface(Ci.nsIDOMAttr);
1588 switch (attr.name) {
1590 this.selected = attr.value == "true";
1593 if (0 == parseInt(attr.value)) {
1594 LOG("UpdatePatch:init - 0-sized patch!");
1595 throw Cr.NS_ERROR_ILLEGAL_VALUE;
1599 this[attr.name] = attr.value;
1604 UpdatePatch.prototype = {
1606 * See nsIUpdateService.idl
1608 serialize: function UpdatePatch_serialize(updates) {
1609 var patch = updates.createElementNS(URI_UPDATE_NS, "patch");
1610 patch.setAttribute("type", this.type);
1611 patch.setAttribute("URL", this.URL);
1612 // finalURL is not available until after the download has started
1614 patch.setAttribute("finalURL", this.finalURL);
1615 patch.setAttribute("hashFunction", this.hashFunction);
1616 patch.setAttribute("hashValue", this.hashValue);
1617 patch.setAttribute("size", this.size);
1618 patch.setAttribute("selected", this.selected);
1619 patch.setAttribute("state", this.state);
1621 for (var p in this._properties) {
1622 if (this._properties[p].present)
1623 patch.setAttribute(p, this._properties[p].data);
1630 * A hash of custom properties
1635 * See nsIWritablePropertyBag.idl
1637 setProperty: function UpdatePatch_setProperty(name, value) {
1638 this._properties[name] = { data: value, present: true };
1642 * See nsIWritablePropertyBag.idl
1644 deleteProperty: function UpdatePatch_deleteProperty(name) {
1645 if (name in this._properties)
1646 this._properties[name].present = false;
1648 throw Cr.NS_ERROR_FAILURE;
1652 * See nsIPropertyBag.idl
1655 var properties = [];
1656 for (var p in this._properties)
1657 properties.push(this._properties[p].data);
1658 return new ArrayEnumerator(properties);
1662 * See nsIPropertyBag.idl
1664 getProperty: function UpdatePatch_getProperty(name) {
1665 if (name in this._properties &&
1666 this._properties[name].present)
1667 return this._properties[name].data;
1668 throw Cr.NS_ERROR_FAILURE;
1672 * Returns whether or not the update.status file for this patch exists at the
1673 * appropriate location.
1675 get statusFileExists() {
1676 var statusFile = getUpdatesDir();
1677 statusFile.append(FILE_UPDATE_STATUS);
1678 return statusFile.exists();
1682 * See nsIUpdateService.idl
1685 if (this._properties.state)
1686 return this._properties.state;
1690 this._properties.state = val;
1693 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePatch,
1695 Ci.nsIWritablePropertyBag])
1700 * Implements nsIUpdate
1702 * An <update> element to initialize this object with
1703 * @throws if the update contains no patches
1706 function Update(update) {
1707 this._properties = {};
1709 this.isCompleteUpdate = false;
1710 this.isOSUpdate = false;
1711 this.showPrompt = false;
1712 this.showNeverForVersion = false;
1713 this.unsupported = false;
1714 this.channel = "default";
1715 this.promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
1717 // Null <update>, assume this is a message container and do no
1718 // further initialization
1722 const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
1723 for (var i = 0; i < update.childNodes.length; ++i) {
1724 var patchElement = update.childNodes.item(i);
1725 if (patchElement.nodeType != ELEMENT_NODE ||
1726 patchElement.localName != "patch")
1729 patchElement.QueryInterface(Ci.nsIDOMElement);
1731 var patch = new UpdatePatch(patchElement);
1735 this._patches.push(patch);
1738 if (this._patches.length == 0 && !update.hasAttribute("unsupported"))
1739 throw Cr.NS_ERROR_ILLEGAL_VALUE;
1741 // Fallback to the behavior prior to bug 530872 if the update does not have an
1742 // appVersion attribute.
1743 if (!update.hasAttribute("appVersion")) {
1744 if (update.getAttribute("type") == "major") {
1745 if (update.hasAttribute("detailsURL")) {
1746 this.billboardURL = update.getAttribute("detailsURL");
1747 this.showPrompt = true;
1748 this.showNeverForVersion = true;
1753 for (var i = 0; i < update.attributes.length; ++i) {
1754 var attr = update.attributes.item(i);
1755 attr.QueryInterface(Ci.nsIDOMAttr);
1756 if (attr.value == "undefined")
1758 else if (attr.name == "detailsURL")
1759 this._detailsURL = attr.value;
1760 else if (attr.name == "extensionVersion") {
1761 // Prevent extensionVersion from replacing appVersion if appVersion is
1762 // present in the update xml.
1763 if (!this.appVersion)
1764 this.appVersion = attr.value;
1766 else if (attr.name == "installDate" && attr.value)
1767 this.installDate = parseInt(attr.value);
1768 else if (attr.name == "isCompleteUpdate")
1769 this.isCompleteUpdate = attr.value == "true";
1770 else if (attr.name == "isSecurityUpdate")
1771 this.isSecurityUpdate = attr.value == "true";
1772 else if (attr.name == "isOSUpdate")
1773 this.isOSUpdate = attr.value == "true";
1774 else if (attr.name == "showNeverForVersion")
1775 this.showNeverForVersion = attr.value == "true";
1776 else if (attr.name == "showPrompt")
1777 this.showPrompt = attr.value == "true";
1778 else if (attr.name == "promptWaitTime")
1780 if(!isNaN(attr.value))
1781 this.promptWaitTime = parseInt(attr.value);
1783 else if (attr.name == "unsupported")
1784 this.unsupported = attr.value == "true";
1785 else if (attr.name == "version") {
1786 // Prevent version from replacing displayVersion if displayVersion is
1787 // present in the update xml.
1788 if (!this.displayVersion)
1789 this.displayVersion = attr.value;
1792 this[attr.name] = attr.value;
1794 switch (attr.name) {
1796 case "billboardURL":
1799 case "displayVersion":
1802 case "platformVersion":
1803 case "previousAppVersion":
1809 // Save custom attributes when serializing to the local xml file but
1810 // don't use this method for the expected attributes which are already
1811 // handled in serialize.
1812 this.setProperty(attr.name, attr.value);
1818 // Set the initial value with the current time when it doesn't already have a
1819 // value or the value is already set to 0 (bug 316328).
1820 if (!this.installDate && this.installDate != 0)
1821 this.installDate = (new Date()).getTime();
1823 // The Update Name is either the string provided by the <update> element, or
1824 // the string: "<App Name> <Update App Version>"
1826 if (update.hasAttribute("name"))
1827 name = update.getAttribute("name");
1829 var brandBundle = Services.strings.createBundle(URI_BRAND_PROPERTIES);
1830 var appName = brandBundle.GetStringFromName("brandShortName");
1831 name = gUpdateBundle.formatStringFromName("updateName",
1832 [appName, this.displayVersion], 2);
1836 Update.prototype = {
1838 * See nsIUpdateService.idl
1841 return this._patches.length;
1845 * See nsIUpdateService.idl
1847 getPatchAt: function Update_getPatchAt(index) {
1848 return this._patches[index];
1852 * See nsIUpdateService.idl
1854 * We use a copy of the state cached on this object in |_state| only when
1855 * there is no selected patch, i.e. in the case when we could not load
1856 * |.activeUpdate| from the update manager for some reason but still have
1857 * the update.status file to work with.
1861 if (this.selectedPatch)
1862 this.selectedPatch.state = state;
1863 this._state = state;
1867 if (this.selectedPatch)
1868 return this.selectedPatch.state;
1873 * See nsIUpdateService.idl
1878 * See nsIUpdateService.idl
1880 get selectedPatch() {
1881 for (var i = 0; i < this.patchCount; ++i) {
1882 if (this._patches[i].selected)
1883 return this._patches[i];
1889 * See nsIUpdateService.idl
1892 if (!this._detailsURL) {
1894 // Try using a default details URL supplied by the distribution
1895 // if the update XML does not supply one.
1896 return Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_DETAILS);
1901 return this._detailsURL || "";
1905 * See nsIUpdateService.idl
1907 serialize: function Update_serialize(updates) {
1908 var update = updates.createElementNS(URI_UPDATE_NS, "update");
1909 update.setAttribute("appVersion", this.appVersion);
1910 update.setAttribute("buildID", this.buildID);
1911 update.setAttribute("channel", this.channel);
1912 update.setAttribute("displayVersion", this.displayVersion);
1913 // for backwards compatibility in case the user downgrades
1914 update.setAttribute("extensionVersion", this.appVersion);
1915 update.setAttribute("installDate", this.installDate);
1916 update.setAttribute("isCompleteUpdate", this.isCompleteUpdate);
1917 update.setAttribute("isOSUpdate", this.isOSUpdate);
1918 update.setAttribute("name", this.name);
1919 update.setAttribute("serviceURL", this.serviceURL);
1920 update.setAttribute("showNeverForVersion", this.showNeverForVersion);
1921 update.setAttribute("showPrompt", this.showPrompt);
1922 update.setAttribute("promptWaitTime", this.promptWaitTime);
1923 update.setAttribute("type", this.type);
1924 // for backwards compatibility in case the user downgrades
1925 update.setAttribute("version", this.displayVersion);
1927 // Optional attributes
1928 if (this.billboardURL)
1929 update.setAttribute("billboardURL", this.billboardURL);
1930 if (this.detailsURL)
1931 update.setAttribute("detailsURL", this.detailsURL);
1932 if (this.licenseURL)
1933 update.setAttribute("licenseURL", this.licenseURL);
1934 if (this.platformVersion)
1935 update.setAttribute("platformVersion", this.platformVersion);
1936 if (this.previousAppVersion)
1937 update.setAttribute("previousAppVersion", this.previousAppVersion);
1938 if (this.statusText)
1939 update.setAttribute("statusText", this.statusText);
1940 if (this.unsupported)
1941 update.setAttribute("unsupported", this.unsupported);
1942 updates.documentElement.appendChild(update);
1944 for (var p in this._properties) {
1945 if (this._properties[p].present)
1946 update.setAttribute(p, this._properties[p].data);
1949 for (var i = 0; i < this.patchCount; ++i)
1950 update.appendChild(this.getPatchAt(i).serialize(updates));
1956 * A hash of custom properties
1961 * See nsIWritablePropertyBag.idl
1963 setProperty: function Update_setProperty(name, value) {
1964 this._properties[name] = { data: value, present: true };
1968 * See nsIWritablePropertyBag.idl
1970 deleteProperty: function Update_deleteProperty(name) {
1971 if (name in this._properties)
1972 this._properties[name].present = false;
1974 throw Cr.NS_ERROR_FAILURE;
1978 * See nsIPropertyBag.idl
1981 var properties = [];
1982 for (var p in this._properties)
1983 properties.push(this._properties[p].data);
1984 return new ArrayEnumerator(properties);
1988 * See nsIPropertyBag.idl
1990 getProperty: function Update_getProperty(name) {
1991 if (name in this._properties && this._properties[name].present)
1992 return this._properties[name].data;
1993 throw Cr.NS_ERROR_FAILURE;
1996 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdate,
1998 Ci.nsIWritablePropertyBag])
2001 const UpdateServiceFactory = {
2003 createInstance: function (outer, iid) {
2005 throw Cr.NS_ERROR_NO_AGGREGATION;
2006 return this._instance == null ? this._instance = new UpdateService() :
2013 * A Service for managing the discovery and installation of software updates.
2016 function UpdateService() {
2017 LOG("Creating UpdateService");
2018 Services.obs.addObserver(this, "xpcom-shutdown", false);
2019 Services.prefs.addObserver(PREF_APP_UPDATE_LOG, this, false);
2020 #ifdef MOZ_WIDGET_GONK
2021 // PowerManagerService::SyncProfile (which is called for Reboot, PowerOff
2022 // and Restart) sends the profile-change-net-teardown event. We can then
2023 // pause the download in a similar manner to xpcom-shutdown.
2024 Services.obs.addObserver(this, "profile-change-net-teardown", false);
2028 UpdateService.prototype = {
2030 * The downloader we are using to download updates. There is only ever one of
2036 * Incompatible add-on count.
2038 _incompatAddonsCount: 0,
2041 * Whether or not the service registered the "online" observer.
2043 _registeredOnlineObserver: false,
2046 * The current number of consecutive socket errors
2048 _consecutiveSocketErrors: 0,
2051 * A timer used to retry socket errors
2056 * Whether or not a background update check was initiated by the
2057 * application update timer notification.
2062 * Handle Observer Service notifications
2064 * The subject of the notification
2066 * The notification name
2070 observe: function AUS_observe(subject, topic, data) {
2072 case "post-update-processing":
2073 // Clean up any extant updates
2074 this._postUpdateProcessing();
2076 case "network:offline-status-changed":
2077 this._offlineStatusChanged(data);
2079 case "nsPref:changed":
2080 if (data == PREF_APP_UPDATE_LOG) {
2081 gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
2084 #ifdef MOZ_WIDGET_GONK
2085 case "profile-change-net-teardown": // fall thru
2087 case "xpcom-shutdown":
2088 Services.obs.removeObserver(this, topic);
2089 Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, this);
2091 if (this._retryTimer) {
2092 this._retryTimer.cancel();
2095 this.pauseDownload();
2096 // Prevent leaking the downloader (bug 454964)
2097 this._downloader = null;
2103 * The following needs to happen during the post-update-processing
2104 * notification from nsUpdateServiceStub.js:
2105 * 1. post update processing
2106 * 2. resume of a download that was in progress during a previous session
2107 * 3. start of a complete update download after the failure to apply a partial
2112 * Perform post-processing on updates lingering in the updates directory
2113 * from a previous application session - either report install failures (and
2114 * optionally attempt to fetch a different version if appropriate) or
2115 * notify the user of install success.
2117 _postUpdateProcessing: function AUS__postUpdateProcessing() {
2118 // canCheckForUpdates will return false when metro-only updates are disabled
2119 // from within metro. In that case we still want _postUpdateProcessing to
2120 // run. gMetroUpdatesEnabled returns true on non Windows 8 platforms.
2121 // We want _postUpdateProcessing to run so that it will update the history
2122 // XML. Without updating the history XML, the about flyout will continue to
2123 // have the "Restart to Apply Update" button (history xml indicates update
2125 // TODO: I think this whole if-block should be removed since updates can
2126 // always be applied via the about dialog, we should be running post update
2128 if (!this.canCheckForUpdates && gMetroUpdatesEnabled) {
2129 LOG("UpdateService:_postUpdateProcessing - unable to check for " +
2130 "updates... returning early");
2134 if (!this.canApplyUpdates) {
2135 LOG("UpdateService:_postUpdateProcessing - unable to apply " +
2136 "updates... returning early");
2137 // If the update is present in the update directly somehow,
2138 // it would prevent us from notifying the user of futher updates.
2139 cleanupActiveUpdate();
2143 var status = readStatusFile(getUpdatesDir());
2144 // STATE_NONE status means that the update.status file is present but a
2145 // background download error occurred.
2146 if (status == STATE_NONE) {
2147 LOG("UpdateService:_postUpdateProcessing - no status, no update");
2148 cleanupActiveUpdate();
2152 var um = Cc["@mozilla.org/updates/update-manager;1"].
2153 getService(Ci.nsIUpdateManager);
2155 #ifdef MOZ_WIDGET_GONK
2156 // This code is called very early in the boot process, before we've even
2157 // had a chance to setup the UI so we can give feedback to the user.
2159 // Since the download may be occuring over a link which has associated
2160 // cost, we want to require user-consent before resuming the download.
2161 // Also, applying an already downloaded update now is undesireable,
2162 // since the phone will look dead while the update is being applied.
2163 // Applying the update can take several minutes. Instead we wait until
2164 // the UI is initialized so it is possible to give feedback to and get
2165 // consent to update from the user.
2166 if (isInterruptedUpdate(status)) {
2167 LOG("UpdateService:_postUpdateProcessing - interrupted update detected - wait for user consent");
2172 var update = um.activeUpdate;
2174 if (status == STATE_DOWNLOADING) {
2175 LOG("UpdateService:_postUpdateProcessing - patch found in downloading " +
2177 if (update && update.state != STATE_SUCCEEDED) {
2179 var status = this.downloadUpdate(update, true);
2180 if (status == STATE_NONE)
2181 cleanupActiveUpdate();
2186 if (status == STATE_APPLYING) {
2187 // This indicates that the background updater service is in either of the
2188 // following two states:
2189 // 1. It is in the process of applying an update in the background, and
2190 // we just happen to be racing against that.
2191 // 2. It has failed to apply an update for some reason, and we hit this
2192 // case because the updater process has set the update status to
2193 // applying, but has never finished.
2194 // In order to differentiate between these two states, we look at the
2195 // state field of the update object. If it's "pending", then we know
2196 // that this is the first time we're hitting this case, so we switch
2197 // that state to "applying" and we just wait and hope for the best.
2198 // If it's "applying", we know that we've already been here once, so
2199 // we really want to start from a clean state.
2201 (update.state == STATE_PENDING || update.state == STATE_PENDING_SVC)) {
2202 LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
2203 "state for the first time");
2204 update.state = STATE_APPLYING;
2206 } else { // We get here even if we don't have an update object
2207 LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
2208 "state for the second time");
2209 cleanupActiveUpdate();
2214 #ifdef MOZ_WIDGET_GONK
2215 // The update is only applied but not selected to be installed
2216 if (status == STATE_APPLIED && update && update.isOSUpdate) {
2217 LOG("UpdateService:_postUpdateProcessing - update staged as applied found");
2221 if (status == STATE_APPLIED_OS && update && update.isOSUpdate) {
2222 // In gonk, we need to check for OS update status after startup, since
2223 // the recovery partition won't write to update.status for us
2224 var recoveryService = Cc["@mozilla.org/recovery-service;1"].
2225 getService(Ci.nsIRecoveryService);
2227 var fotaStatus = recoveryService.getFotaUpdateStatus();
2228 switch (fotaStatus) {
2229 case Ci.nsIRecoveryService.FOTA_UPDATE_SUCCESS:
2230 status = STATE_SUCCEEDED;
2232 case Ci.nsIRecoveryService.FOTA_UPDATE_FAIL:
2233 status = STATE_FAILED + ": " + FOTA_GENERAL_ERROR;
2235 case Ci.nsIRecoveryService.FOTA_UPDATE_UNKNOWN:
2237 status = STATE_FAILED + ": " + FOTA_UNKNOWN_ERROR;
2244 if (status != STATE_SUCCEEDED) {
2245 LOG("UpdateService:_postUpdateProcessing - previous patch failed " +
2246 "and no patch available");
2247 cleanupActiveUpdate();
2250 update = new Update(null);
2253 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
2254 createInstance(Ci.nsIUpdatePrompt);
2256 update.state = status;
2257 this._sendStatusCodeTelemetryPing(status);
2259 if (status == STATE_SUCCEEDED) {
2260 update.statusText = gUpdateBundle.GetStringFromName("installSuccess");
2262 // Update the patch's metadata.
2263 um.activeUpdate = update;
2264 Services.prefs.setBoolPref(PREF_APP_UPDATE_POSTUPDATE, true);
2265 prompter.showUpdateInstalled();
2267 // Done with this update. Clean it up.
2268 cleanupActiveUpdate();
2271 // If we hit an error, then the error code will be included in the status
2272 // string following a colon and a space. If we had an I/O error, then we
2273 // assume that the patch is not invalid, and we re-stage the patch so that
2274 // it can be attempted again the next time we restart. This will leave a
2275 // space at the beginning of the error code when there is a failure which
2276 // will be removed by using parseInt below. This prevents panic which has
2277 // occurred numerous times previously (see bug 569642 comment #9 for one
2278 // example) when testing releases due to forgetting to include the space.
2279 var ary = status.split(":");
2280 update.state = ary[0];
2281 if (update.state == STATE_FAILED && ary[1]) {
2282 if (handleUpdateFailure(update, ary[1])) {
2287 // Something went wrong with the patch application process.
2288 handleFallbackToCompleteUpdate(update, false);
2290 prompter.showUpdateError(update);
2293 // Now trash the MozUpdater folders which staged/bgupdates uses.
2294 cleanUpMozUpdaterDirs();
2298 * Submit a telemetry ping with the boolean value of a pref for a histogram
2301 * The preference to report
2303 * The histogram ID to report to
2305 _sendBoolPrefTelemetryPing: function AUS__boolTelemetryPing(pref, histogram) {
2307 // The getPref is already wrapped in a try/catch but we never
2308 // want telemetry pings breaking app update so we just put it
2309 // inside the try to be safe.
2310 let val = getPref("getBoolPref", pref, false);
2311 Services.telemetry.getHistogramById(histogram).add(+val);
2313 // Don't allow any exception to be propagated.
2320 * Submit a telemetry ping with a boolean value which indicates if the service
2322 * Also submits a telemetry ping with a boolean value which indicates if the
2323 * service was at some point installed, but is now uninstalled.
2325 _sendServiceInstalledTelemetryPing: function AUS__svcInstallTelemetryPing() {
2326 let installed = isServiceInstalled(); // Is the service installed?
2329 let wrk = Cc["@mozilla.org/windows-registry-key;1"].
2330 createInstance(Ci.nsIWindowsRegKey);
2331 wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
2332 "SOFTWARE\\Mozilla\\MaintenanceService",
2333 wrk.ACCESS_READ | wrk.WOW64_64);
2334 // Was the service at some point installed, but is now uninstalled?
2335 attempted = wrk.readIntValue("Attempted");
2340 let h = Services.telemetry.getHistogramById("UPDATER_SERVICE_INSTALLED");
2341 h.add(Number(installed));
2343 // Don't allow any exception to be propagated.
2347 let h = Services.telemetry.getHistogramById("UPDATER_SERVICE_MANUALLY_UNINSTALLED");
2348 h.add(!installed && attempted ? 1 : 0);
2350 // Don't allow any exception to be propagated.
2357 * Submit a telemetry ping with the int value of a pref for a histogram
2360 * The preference to report
2362 * The histogram ID to report to
2364 _sendIntPrefTelemetryPing: function AUS__intTelemetryPing(pref, histogram) {
2366 // The getPref is already wrapped in a try/catch but we never
2367 // want telemetry pings breaking app update so we just put it
2368 // inside the try to be safe.
2369 let val = getPref("getIntPref", pref, 0);
2370 Services.telemetry.getHistogramById(histogram).add(val);
2372 // Don't allow any exception to be propagated.
2379 * Submit the results of applying the update via telemetry.
2382 * The status of the update as read from the update.status file
2384 _sendStatusCodeTelemetryPing: function AUS__statusTelemetryPing(status) {
2386 let parts = status.split(":");
2387 if ((parts.length == 1 && status != STATE_SUCCEEDED) ||
2388 (parts.length > 1 && parts[0] != STATE_FAILED)) {
2389 // Should also report STATE_DOWNLOAD_FAILED
2392 let result = 0; // 0 means success
2393 if (parts.length > 1) {
2394 result = parseInt(parts[1]) || INVALID_UPDATER_STATUS_CODE;
2396 Services.telemetry.getHistogramById("UPDATER_STATUS_CODES").add(result);
2398 // Don't allow any exception to be propagated.
2404 * Submit the interval in days since the last notification for this background
2407 _sendLastNotifyIntervalPing: function AUS__notifyIntervalPing() {
2408 if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LASTUPDATETIME)) {
2409 let idSuffix = this._isNotify ? "NOTIFY" : "EXTERNAL";
2410 let lastUpdateTimeSeconds = getPref("getIntPref",
2411 PREF_APP_UPDATE_LASTUPDATETIME, 0);
2412 if (lastUpdateTimeSeconds) {
2413 let currentTimeSeconds = Math.round(Date.now() / 1000);
2414 if (lastUpdateTimeSeconds > currentTimeSeconds) {
2417 getHistogramById("UPDATER_INVALID_LASTUPDATETIME_" + idSuffix).
2424 let intervalDays = (currentTimeSeconds - lastUpdateTimeSeconds) /
2428 getHistogramById("UPDATER_INVALID_LASTUPDATETIME_" + idSuffix).
2431 getHistogramById("UPDATER_LAST_NOTIFY_INTERVAL_DAYS_" + idSuffix).
2442 * Submit the result for the background update check.
2445 * An integer value as defined by the PING_BGUC_* constants.
2447 _backgroundUpdateCheckCodePing: function AUS__backgroundUpdateCheckCodePing(code) {
2449 let idSuffix = this._isNotify ? "NOTIFY" : "EXTERNAL";
2451 getHistogramById("UPDATER_BACKGROUND_CHECK_CODE_" + idSuffix).add(code);
2459 * Register an observer when the network comes online, so we can short-circuit
2460 * the app.update.interval when there isn't connectivity
2462 _registerOnlineObserver: function AUS__registerOnlineObserver() {
2463 if (this._registeredOnlineObserver) {
2464 LOG("UpdateService:_registerOnlineObserver - observer already registered");
2468 LOG("UpdateService:_registerOnlineObserver - waiting for the network to " +
2469 "be online, then forcing another check");
2471 Services.obs.addObserver(this, "network:offline-status-changed", false);
2472 this._registeredOnlineObserver = true;
2476 * Called from the network:offline-status-changed observer.
2478 _offlineStatusChanged: function AUS__offlineStatusChanged(status) {
2479 if (status !== "online") {
2483 Services.obs.removeObserver(this, "network:offline-status-changed");
2484 this._registeredOnlineObserver = false;
2486 LOG("UpdateService:_offlineStatusChanged - network is online, forcing " +
2487 "another background check");
2489 // the background checker is contained in notify
2490 this._attemptResume();
2493 onCheckComplete: function AUS_onCheckComplete(request, updates, updateCount) {
2494 this._selectAndInstallUpdate(updates);
2497 onError: function AUS_onError(request, update) {
2498 LOG("UpdateService:onError - error during background update. error code: " +
2499 update.errorCode + ", status text: " + update.statusText);
2503 if (update.errorCode == NETWORK_ERROR_OFFLINE) {
2504 // Register an online observer to try again
2505 this._registerOnlineObserver();
2506 this._backgroundUpdateCheckCodePing(PING_BGUC_OFFLINE);
2510 if (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
2511 update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE) {
2512 errCount = getPref("getIntPref", PREF_APP_UPDATE_CERT_ERRORS, 0);
2514 Services.prefs.setIntPref(PREF_APP_UPDATE_CERT_ERRORS, errCount);
2515 maxErrors = getPref("getIntPref", PREF_APP_UPDATE_CERT_MAXERRORS, 5);
2518 update.errorCode = BACKGROUNDCHECK_MULTIPLE_FAILURES;
2519 errCount = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDERRORS, 0);
2521 Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, errCount);
2522 maxErrors = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS,
2527 if (errCount >= maxErrors) {
2528 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
2529 createInstance(Ci.nsIUpdatePrompt);
2530 prompter.showUpdateError(update);
2532 switch (update.errorCode) {
2533 case CERT_ATTR_CHECK_FAILED_NO_UPDATE:
2534 pingCode = PING_BGUC_CERT_ATTR_NO_UPDATE_NOTIFY;
2536 case CERT_ATTR_CHECK_FAILED_HAS_UPDATE:
2537 pingCode = PING_BGUC_CERT_ATTR_WITH_UPDATE_NOTIFY;
2540 pingCode = PING_BGUC_GENERAL_ERROR_NOTIFY;
2544 switch (update.errorCode) {
2545 case CERT_ATTR_CHECK_FAILED_NO_UPDATE:
2546 pingCode = PING_BGUC_CERT_ATTR_NO_UPDATE_SILENT;
2548 case CERT_ATTR_CHECK_FAILED_HAS_UPDATE:
2549 pingCode = PING_BGUC_CERT_ATTR_WITH_UPDATE_SILENT;
2552 pingCode = PING_BGUC_GENERAL_ERROR_SILENT;
2555 this._backgroundUpdateCheckCodePing(pingCode);
2559 * Called when a connection should be resumed
2561 _attemptResume: function AUS_attemptResume() {
2562 LOG("UpdateService:_attemptResume")
2563 // If a download is in progress, then resume it.
2564 if (this._downloader && this._downloader._patch &&
2565 this._downloader._patch.state == STATE_DOWNLOADING &&
2566 this._downloader._update) {
2567 LOG("UpdateService:_attemptResume - _patch.state: " +
2568 this._downloader._patch.state);
2569 // Make sure downloading is the state for selectPatch to work correctly
2570 writeStatusFile(getUpdatesDir(), STATE_DOWNLOADING);
2571 var status = this.downloadUpdate(this._downloader._update,
2572 this._downloader.background);
2573 LOG("UpdateService:_attemptResume - downloadUpdate status: " + status);
2574 if (status == STATE_NONE) {
2575 cleanupActiveUpdate();
2580 this.backgroundChecker.checkForUpdates(this, false);
2584 * Notified when a timer fires
2586 * The timer that fired
2588 notify: function AUS_notify(timer) {
2589 // The telemetry below is specific to background notification.
2590 this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_ENABLED,
2591 "UPDATER_UPDATES_ENABLED");
2592 this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_METRO_ENABLED,
2593 "UPDATER_UPDATES_METRO_ENABLED");
2594 this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_AUTO,
2595 "UPDATER_UPDATES_AUTOMATIC");
2596 this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_STAGING_ENABLED,
2597 "UPDATER_STAGE_ENABLED");
2600 this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ENABLED,
2601 "UPDATER_SERVICE_ENABLED");
2602 this._sendIntPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ERRORS,
2603 "UPDATER_SERVICE_ERRORS");
2604 this._sendServiceInstalledTelemetryPing();
2607 this._checkForBackgroundUpdates(true);
2611 * See nsIUpdateService.idl
2613 checkForBackgroundUpdates: function AUS_checkForBackgroundUpdates() {
2614 this._checkForBackgroundUpdates(false);
2618 * Checks for updates in the background.
2620 * Whether or not a background update check was initiated by the
2621 * application update timer notification.
2623 _checkForBackgroundUpdates: function AUS__checkForBackgroundUpdates(isNotify) {
2624 this._isNotify = isNotify;
2625 // From this point on, the telemetry reported differentiates between a call
2626 // to notify and a call to checkForBackgroundUpdates so they are reported
2628 this._sendLastNotifyIntervalPing();
2630 // If a download is in progress or the patch has been staged do nothing.
2631 if (this.isDownloading) {
2632 this._backgroundUpdateCheckCodePing(PING_BGUC_IS_DOWNLOADING);
2636 if (this._downloader && this._downloader.patchIsStaged) {
2637 this._backgroundUpdateCheckCodePing(PING_BGUC_IS_STAGED);
2641 // The following checks will return early without notification in the call
2642 // to checkForUpdates below. To simplify the background update check ping
2643 // their values are checked here.
2645 if (!this.backgroundChecker.getUpdateURL(false)) {
2646 let prefs = Services.prefs;
2647 if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE)) {
2648 if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL)) {
2649 this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_DEFAULT_URL);
2652 this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_CUSTOM_URL);
2656 this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_OVERRIDE_URL);
2659 else if (!gMetroUpdatesEnabled) {
2660 this._backgroundUpdateCheckCodePing(PING_BGUC_METRO_DISABLED);
2662 else if (!getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true)) {
2663 this._backgroundUpdateCheckCodePing(PING_BGUC_PREF_DISABLED);
2665 else if (!(gCanCheckForUpdates && hasUpdateMutex())) {
2666 this._backgroundUpdateCheckCodePing(PING_BGUC_UNABLE_TO_CHECK);
2668 else if (!this.backgroundChecker._enabled) {
2669 this._backgroundUpdateCheckCodePing(PING_BGUC_DISABLED_FOR_SESSION);
2676 this.backgroundChecker.checkForUpdates(this, false);
2680 * Determine the update from the specified updates that should be offered.
2681 * If both valid major and minor updates are available the minor update will
2684 * An array of available nsIUpdate items
2685 * @return The nsIUpdate to offer.
2687 selectUpdate: function AUS_selectUpdate(updates) {
2688 if (updates.length == 0) {
2689 this._backgroundUpdateCheckCodePing(PING_BGUC_NO_UPDATE_FOUND);
2693 // The ping for unsupported is sent after the call to showPrompt.
2694 if (updates.length == 1 && updates[0].unsupported)
2697 // Choose the newest of the available minor and major updates.
2698 var majorUpdate = null;
2699 var minorUpdate = null;
2700 var vc = Services.vc;
2701 var lastPingCode = PING_BGUC_NO_COMPAT_UPDATE_FOUND;
2703 updates.forEach(function(aUpdate) {
2704 // Ignore updates for older versions of the application and updates for
2705 // the same version of the application with the same build ID.
2706 if (vc.compare(aUpdate.appVersion, Services.appinfo.version) < 0 ||
2707 vc.compare(aUpdate.appVersion, Services.appinfo.version) == 0 &&
2708 aUpdate.buildID == Services.appinfo.appBuildID) {
2709 LOG("UpdateService:selectUpdate - skipping update because the " +
2710 "update's application version is less than the current " +
2711 "application version");
2712 lastPingCode = PING_BGUC_UPDATE_PREVIOUS_VERSION;
2716 // Skip the update if the user responded with "never" to this update's
2717 // application version and the update specifies showNeverForVersion
2718 // (see bug 350636).
2719 let neverPrefName = PREF_APP_UPDATE_NEVER_BRANCH + aUpdate.appVersion;
2720 if (aUpdate.showNeverForVersion &&
2721 getPref("getBoolPref", neverPrefName, false)) {
2722 LOG("UpdateService:selectUpdate - skipping update because the " +
2723 "preference " + neverPrefName + " is true");
2724 lastPingCode = PING_BGUC_UPDATE_NEVER_PREF;
2728 switch (aUpdate.type) {
2731 majorUpdate = aUpdate;
2732 else if (vc.compare(majorUpdate.appVersion, aUpdate.appVersion) <= 0)
2733 majorUpdate = aUpdate;
2737 minorUpdate = aUpdate;
2738 else if (vc.compare(minorUpdate.appVersion, aUpdate.appVersion) <= 0)
2739 minorUpdate = aUpdate;
2742 LOG("UpdateService:selectUpdate - skipping unknown update type: " +
2744 lastPingCode = PING_BGUC_UPDATE_INVALID_TYPE;
2749 var update = minorUpdate || majorUpdate;
2751 this._backgroundUpdateCheckCodePing(lastPingCode);
2757 * Reference to the currently selected update for when add-on compatibility
2763 * Determine which of the specified updates should be installed and begin the
2764 * download/installation process or notify the user about the update.
2766 * An array of available updates
2768 _selectAndInstallUpdate: function AUS__selectAndInstallUpdate(updates) {
2769 // Return early if there's an active update. The user is already aware and
2770 // is downloading or performed some user action to prevent notification.
2771 var um = Cc["@mozilla.org/updates/update-manager;1"].
2772 getService(Ci.nsIUpdateManager);
2773 if (um.activeUpdate) {
2774 #ifdef MOZ_WIDGET_GONK
2775 // For gonk, the user isn't necessarily aware of the update, so we need
2776 // to show the prompt to make sure.
2777 this._showPrompt(um.activeUpdate);
2779 this._backgroundUpdateCheckCodePing(PING_BGUC_HAS_ACTIVEUPDATE);
2783 var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
2784 if (!updateEnabled) {
2785 this._backgroundUpdateCheckCodePing(PING_BGUC_PREF_DISABLED);
2786 LOG("UpdateService:_selectAndInstallUpdate - not prompting because " +
2787 "update is disabled");
2791 if (!gMetroUpdatesEnabled) {
2792 this._backgroundUpdateCheckCodePing(PING_BGUC_METRO_DISABLED);
2796 var update = this.selectUpdate(updates, updates.length);
2801 if (update.unsupported) {
2802 LOG("UpdateService:_selectAndInstallUpdate - update not supported for " +
2804 if (!getPref("getBoolPref", PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, false)) {
2805 LOG("UpdateService:_selectAndInstallUpdate - notifying that the " +
2806 "update is not supported for this system");
2807 this._showPrompt(update);
2809 this._backgroundUpdateCheckCodePing(PING_BGUC_UNSUPPORTED);
2813 if (!(gCanApplyUpdates && hasUpdateMutex())) {
2814 LOG("UpdateService:_selectAndInstallUpdate - the user is unable to " +
2815 "apply updates... prompting");
2816 this._showPrompt(update);
2817 this._backgroundUpdateCheckCodePing(PING_BGUC_UNABLE_TO_APPLY);
2822 # From this point on there are two possible outcomes:
2823 # 1. download and install the update automatically
2824 # 2. notify the user about the availability of an update
2827 # a) if the app.update.auto preference is false then automatic download and
2828 # install is disabled and the user will be notified.
2829 # b) if the update has a showPrompt attribute the user will be notified.
2830 # c) Mode is determined by the value of the app.update.mode preference.
2832 # If the update when it is first read has an appVersion attribute the
2833 # following behavior implemented in bug 530872 will occur:
2834 # Mode Incompatible Add-ons Outcome
2835 # 0 N/A Auto Install
2839 # If the update when it is first read does not have an appVersion attribute
2840 # the following deprecated behavior will occur:
2841 # Update Type Mode Incompatible Add-ons Outcome
2842 # Major all N/A Notify
2843 # Minor 0 N/A Auto Install
2844 # Minor 1 Yes Notify
2845 # Minor 1 No Auto Install
2847 if (update.showPrompt) {
2848 LOG("UpdateService:_selectAndInstallUpdate - prompting because the " +
2849 "update snippet specified showPrompt");
2850 this._showPrompt(update);
2851 if (!Services.metro || !Services.metro.immersive) {
2852 this._backgroundUpdateCheckCodePing(PING_BGUC_SHOWPROMPT_SNIPPET);
2857 if (!getPref("getBoolPref", PREF_APP_UPDATE_AUTO, true)) {
2858 LOG("UpdateService:_selectAndInstallUpdate - prompting because silent " +
2859 "install is disabled");
2860 this._showPrompt(update);
2861 if (!Services.metro || !Services.metro.immersive) {
2862 this._backgroundUpdateCheckCodePing(PING_BGUC_SHOWPROMPT_PREF);
2867 if (getPref("getIntPref", PREF_APP_UPDATE_MODE, 1) == 0) {
2868 // Do not prompt regardless of add-on incompatibilities
2869 LOG("UpdateService:_selectAndInstallUpdate - add-on compatibility " +
2870 "check disabled by preference, just download the update");
2871 var status = this.downloadUpdate(update, true);
2872 if (status == STATE_NONE)
2873 cleanupActiveUpdate();
2874 this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_PREF_DISABLED);
2878 // Only check add-on compatibility when the version changes.
2879 if (update.appVersion &&
2880 Services.vc.compare(update.appVersion, Services.appinfo.version) != 0) {
2881 this._update = update;
2882 this._checkAddonCompatibility();
2885 LOG("UpdateService:_selectAndInstallUpdate - add-on compatibility " +
2886 "check not performed due to the update version being the same as " +
2887 "the current application version, just download the update");
2888 var status = this.downloadUpdate(update, true);
2889 if (status == STATE_NONE)
2890 cleanupActiveUpdate();
2891 this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_SAME_APP_VER);
2895 _showPrompt: function AUS__showPrompt(update) {
2896 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
2897 createInstance(Ci.nsIUpdatePrompt);
2898 prompter.showUpdateAvailable(update);
2901 _checkAddonCompatibility: function AUS__checkAddonCompatibility() {
2903 var hotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID);
2907 // Get all the installed add-ons
2909 AddonManager.getAllAddons(function(addons) {
2910 self._incompatibleAddons = [];
2911 addons.forEach(function(addon) {
2912 // Protect against code that overrides the add-ons manager and doesn't
2913 // implement the isCompatibleWith or the findUpdates method.
2914 if (!("isCompatibleWith" in addon) || !("findUpdates" in addon)) {
2915 let errMsg = "Add-on doesn't implement either the isCompatibleWith " +
2916 "or the findUpdates method!";
2918 errMsg += " Add-on ID: " + addon.id;
2919 Cu.reportError(errMsg);
2923 // If an add-on isn't appDisabled and isn't userDisabled then it is
2924 // either active now or the user expects it to be active after the
2925 // restart. If that is the case and the add-on is not installed by the
2926 // application and is not compatible with the new application version
2927 // then the user should be warned that the add-on will become
2928 // incompatible. If an addon's type equals plugin it is skipped since
2929 // checking plugins compatibility information isn't supported and
2930 // getting the scope property of a plugin breaks in some environments
2931 // (see bug 566787). The hotfix add-on is also ignored as it shouldn't
2932 // block the user from upgrading.
2934 if (addon.type != "plugin" && addon.id != hotfixID &&
2935 !addon.appDisabled && !addon.userDisabled &&
2936 addon.scope != AddonManager.SCOPE_APPLICATION &&
2937 addon.isCompatible &&
2938 !addon.isCompatibleWith(self._update.appVersion,
2939 self._update.platformVersion))
2940 self._incompatibleAddons.push(addon);
2947 if (self._incompatibleAddons.length > 0) {
2949 # PREF_APP_UPDATE_INCOMPATIBLE_MODE
2950 # Controls the mode in which we check for updates as follows.
2952 # PREF_APP_UPDATE_INCOMPATIBLE_MODE != 1
2953 # We check for VersionInfo _and_ NewerVersion updates for the
2954 # incompatible add-ons - i.e. if Foo 1.2 is installed and it is
2955 # incompatible with the update, and we find Foo 2.0 which is but has
2956 # not been installed, then we do NOT prompt because the user can
2957 # download Foo 2.0 when they restart after the update during the add-on
2958 # mismatch checking UI. This is the default, since it suppresses most
2961 # PREF_APP_UPDATE_INCOMPATIBLE_MODE == 1
2962 # We check for VersionInfo updates for the incompatible add-ons - i.e.
2963 # if the situation above with Foo 1.2 and available update to 2.0
2964 # applies, we DO show the prompt since a download operation will be
2965 # required after the update. This is not the default and is supplied
2966 # only as a hidden option for those that want it.
2968 self._updateCheckCount = self._incompatibleAddons.length;
2969 LOG("UpdateService:_checkAddonCompatibility - checking for " +
2970 "incompatible add-ons");
2972 self._incompatibleAddons.forEach(function(addon) {
2973 addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED,
2974 this._update.appVersion, this._update.platformVersion);
2978 LOG("UpdateService:_checkAddonCompatibility - no incompatible " +
2979 "add-ons found, just download the update");
2980 var status = self.downloadUpdate(self._update, true);
2981 if (status == STATE_NONE)
2982 cleanupActiveUpdate();
2983 self._update = null;
2984 this._backgroundUpdateCheckCodePing(PING_BGUC_CHECK_NO_INCOMPAT);
2989 // AddonUpdateListener
2990 onCompatibilityUpdateAvailable: function(addon) {
2991 // Remove the add-on from the list of add-ons that will become incompatible
2992 // with the new version of the application.
2993 for (var i = 0; i < this._incompatibleAddons.length; ++i) {
2994 if (this._incompatibleAddons[i].id == addon.id) {
2995 LOG("UpdateService:onCompatibilityUpdateAvailable - found update for " +
2996 "add-on ID: " + addon.id);
2997 this._incompatibleAddons.splice(i, 1);
3002 onUpdateAvailable: function(addon, install) {
3003 if (getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE, 0) == 1)
3006 // If the new version of this add-on is blocklisted for the new application
3007 // then it isn't a valid update and the user should still be warned that
3008 // the add-on will become incompatible.
3009 let bs = Cc["@mozilla.org/extensions/blocklist;1"].
3010 getService(Ci.nsIBlocklistService);
3011 if (bs.isAddonBlocklisted(addon,
3012 gUpdates.update.appVersion,
3013 gUpdates.update.platformVersion))
3016 // Compatibility or new version updates mean the same thing here.
3017 this.onCompatibilityUpdateAvailable(addon);
3020 onUpdateFinished: function(addon) {
3021 if (--this._updateCheckCount > 0)
3024 if (this._incompatibleAddons.length > 0 ||
3025 !(gCanApplyUpdates && hasUpdateMutex())) {
3026 LOG("UpdateService:onUpdateEnded - prompting because there are " +
3027 "incompatible add-ons");
3028 this._showPrompt(this._update);
3029 this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_HAVE_INCOMPAT);
3032 LOG("UpdateService:_selectAndInstallUpdate - updates for all " +
3033 "incompatible add-ons found, just download the update");
3034 var status = this.downloadUpdate(this._update, true);
3035 if (status == STATE_NONE)
3036 cleanupActiveUpdate();
3037 this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_UPDATES_FOR_INCOMPAT);
3039 this._update = null;
3043 * The Checker used for background update checks.
3045 _backgroundChecker: null,
3048 * See nsIUpdateService.idl
3050 get backgroundChecker() {
3051 if (!this._backgroundChecker)
3052 this._backgroundChecker = new Checker();
3053 return this._backgroundChecker;
3057 * See nsIUpdateService.idl
3059 get canCheckForUpdates() {
3060 return gCanCheckForUpdates && hasUpdateMutex();
3064 * See nsIUpdateService.idl
3066 get canApplyUpdates() {
3067 return gCanApplyUpdates && hasUpdateMutex();
3071 * See nsIUpdateService.idl
3073 get canStageUpdates() {
3074 return getCanStageUpdates();
3078 * See nsIUpdateService.idl
3080 get isOtherInstanceHandlingUpdates() {
3081 return !hasUpdateMutex();
3086 * See nsIUpdateService.idl
3088 addDownloadListener: function AUS_addDownloadListener(listener) {
3089 if (!this._downloader) {
3090 LOG("UpdateService:addDownloadListener - no downloader!");
3093 this._downloader.addDownloadListener(listener);
3097 * See nsIUpdateService.idl
3099 removeDownloadListener: function AUS_removeDownloadListener(listener) {
3100 if (!this._downloader) {
3101 LOG("UpdateService:removeDownloadListener - no downloader!");
3104 this._downloader.removeDownloadListener(listener);
3108 * See nsIUpdateService.idl
3110 downloadUpdate: function AUS_downloadUpdate(update, background) {
3112 throw Cr.NS_ERROR_NULL_POINTER;
3114 // Don't download the update if the update's version is less than the
3115 // current application's version or the update's version is the same as the
3116 // application's version and the build ID is the same as the application's
3118 if (update.appVersion &&
3119 (Services.vc.compare(update.appVersion, Services.appinfo.version) < 0 ||
3120 update.buildID && update.buildID == Services.appinfo.appBuildID &&
3121 update.appVersion == Services.appinfo.version)) {
3122 LOG("UpdateService:downloadUpdate - canceling download of update since " +
3123 "it is for an earlier or same application version and build ID.\n" +
3124 "current application version: " + Services.appinfo.version + "\n" +
3125 "update application version : " + update.appVersion + "\n" +
3126 "current build ID: " + Services.appinfo.appBuildID + "\n" +
3127 "update build ID : " + update.buildID);
3128 cleanupActiveUpdate();
3132 // If a download request is in progress vs. a download ready to resume
3133 if (this.isDownloading) {
3134 if (update.isCompleteUpdate == this._downloader.isCompleteUpdate &&
3135 background == this._downloader.background) {
3136 LOG("UpdateService:downloadUpdate - no support for downloading more " +
3137 "than one update at a time");
3138 return readStatusFile(getUpdatesDir());
3140 this._downloader.cancel();
3142 #ifdef MOZ_WIDGET_GONK
3143 var um = Cc["@mozilla.org/updates/update-manager;1"].
3144 getService(Ci.nsIUpdateManager);
3145 var activeUpdate = um.activeUpdate;
3147 (activeUpdate.appVersion != update.appVersion ||
3148 activeUpdate.buildID != update.buildID)) {
3149 // We have an activeUpdate (which presumably was interrupted), and are
3150 // about start downloading a new one. Make sure we remove all traces
3151 // of the active one (otherwise we'll start appending the new update.mar
3152 // the the one that's been partially downloaded).
3153 LOG("UpdateService:downloadUpdate - removing stale active update.");
3154 cleanupActiveUpdate();
3157 // Set the previous application version prior to downloading the update.
3158 update.previousAppVersion = Services.appinfo.version;
3159 this._downloader = new Downloader(background, this);
3160 return this._downloader.downloadUpdate(update);
3164 * See nsIUpdateService.idl
3166 pauseDownload: function AUS_pauseDownload() {
3167 if (this.isDownloading) {
3168 this._downloader.cancel();
3169 } else if (this._retryTimer) {
3170 // Download status is still consider as 'downloading' during retry.
3171 // We need to cancel both retry and download at this stage.
3172 this._retryTimer.cancel();
3173 this._retryTimer = null;
3174 this._downloader.cancel();
3179 * See nsIUpdateService.idl
3181 getUpdatesDirectory: getUpdatesDir,
3184 * See nsIUpdateService.idl
3186 get isDownloading() {
3187 return this._downloader && this._downloader.isBusy;
3191 * See nsIUpdateService.idl
3193 applyOsUpdate: function AUS_applyOsUpdate(aUpdate) {
3194 if (!aUpdate.isOSUpdate || aUpdate.state != STATE_APPLIED) {
3195 aUpdate.statusText = "fota-state-error";
3196 throw Cr.NS_ERROR_FAILURE;
3201 aUpdate.QueryInterface(Ci.nsIWritablePropertyBag);
3202 osApplyToDir = aUpdate.getProperty("osApplyToDir");
3205 if (!osApplyToDir) {
3206 LOG("UpdateService:applyOsUpdate - Error: osApplyToDir is not defined" +
3207 "in the nsIUpdate!");
3208 handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
3212 let updateFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
3213 updateFile.initWithPath(osApplyToDir + "/update.zip");
3214 if (!updateFile.exists()) {
3215 LOG("UpdateService:applyOsUpdate - Error: OS update is not found at " +
3217 handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
3221 writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED_OS);
3222 LOG("UpdateService:applyOsUpdate - Rebooting into recovery to apply " +
3223 "FOTA update: " + updateFile.path);
3225 let recoveryService = Cc["@mozilla.org/recovery-service;1"]
3226 .getService(Ci.nsIRecoveryService);
3227 recoveryService.installFotaUpdate(updateFile.path);
3229 LOG("UpdateService:applyOsUpdate - Error: Couldn't reboot into recovery" +
3230 " to apply FOTA update " + updateFile.path);
3231 writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED);
3232 handleUpdateFailure(aUpdate, FOTA_RECOVERY_ERROR);
3236 classID: UPDATESERVICE_CID,
3237 classInfo: XPCOMUtils.generateCI({classID: UPDATESERVICE_CID,
3238 contractID: UPDATESERVICE_CONTRACTID,
3239 interfaces: [Ci.nsIApplicationUpdateService,
3240 Ci.nsITimerCallback,
3242 flags: Ci.nsIClassInfo.SINGLETON}),
3244 _xpcom_factory: UpdateServiceFactory,
3245 QueryInterface: XPCOMUtils.generateQI([Ci.nsIApplicationUpdateService,
3246 Ci.nsIUpdateCheckListener,
3247 Ci.nsITimerCallback,
3252 * A service to manage active and past updates.
3255 function UpdateManager() {
3256 // Ensure the Active Update file is loaded
3257 var updates = this._loadXMLFileIntoArray(getUpdateFile(
3258 [FILE_UPDATE_ACTIVE]));
3259 if (updates.length > 0) {
3260 // Under some edgecases such as Windows system restore the active-update.xml
3261 // will contain a pending update without the status file which will return
3262 // STATE_NONE. To recover from this situation clean the updates dir and
3263 // rewrite the active-update.xml file without the broken update.
3264 if (readStatusFile(getUpdatesDir()) == STATE_NONE) {
3265 cleanUpUpdatesDir();
3266 this._writeUpdatesToXMLFile([], getUpdateFile([FILE_UPDATE_ACTIVE]));
3269 this._activeUpdate = updates[0];
3272 UpdateManager.prototype = {
3274 * All previously downloaded and installed updates, as an array of nsIUpdate
3280 * The current actively downloading/installing update, as a nsIUpdate object.
3282 _activeUpdate: null,
3285 * Handle Observer Service notifications
3287 * The subject of the notification
3289 * The notification name
3293 observe: function UM_observe(subject, topic, data) {
3294 // Hack to be able to run and cleanup tests by reloading the update data.
3295 if (topic == "um-reload-update-data") {
3296 this._updates = this._loadXMLFileIntoArray(getUpdateFile(
3297 [FILE_UPDATES_DB]));
3298 this._activeUpdate = null;
3299 var updates = this._loadXMLFileIntoArray(getUpdateFile(
3300 [FILE_UPDATE_ACTIVE]));
3301 if (updates.length > 0)
3302 this._activeUpdate = updates[0];
3307 * Loads an updates.xml formatted file into an array of nsIUpdate items.
3309 * A nsIFile for the updates.xml file
3310 * @return The array of nsIUpdate items held in the file.
3312 _loadXMLFileIntoArray: function UM__loadXMLFileIntoArray(file) {
3313 if (!file.exists()) {
3314 LOG("UpdateManager:_loadXMLFileIntoArray: XML file does not exist");
3319 var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
3320 createInstance(Ci.nsIFileInputStream);
3321 fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
3323 var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
3324 createInstance(Ci.nsIDOMParser);
3325 var doc = parser.parseFromStream(fileStream, "UTF-8",
3326 fileStream.available(), "text/xml");
3328 const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
3329 var updateCount = doc.documentElement.childNodes.length;
3330 for (var i = 0; i < updateCount; ++i) {
3331 var updateElement = doc.documentElement.childNodes.item(i);
3332 if (updateElement.nodeType != ELEMENT_NODE ||
3333 updateElement.localName != "update")
3336 updateElement.QueryInterface(Ci.nsIDOMElement);
3338 var update = new Update(updateElement);
3340 LOG("UpdateManager:_loadXMLFileIntoArray - invalid update");
3343 result.push(update);
3347 LOG("UpdateManager:_loadXMLFileIntoArray - error constructing update " +
3348 "list. Exception: " + e);
3355 * Load the update manager, initializing state from state files.
3357 _ensureUpdates: function UM__ensureUpdates() {
3358 if (!this._updates) {
3359 this._updates = this._loadXMLFileIntoArray(getUpdateFile(
3360 [FILE_UPDATES_DB]));
3361 var activeUpdates = this._loadXMLFileIntoArray(getUpdateFile(
3362 [FILE_UPDATE_ACTIVE]));
3363 if (activeUpdates.length > 0)
3364 this._activeUpdate = activeUpdates[0];
3369 * See nsIUpdateService.idl
3371 getUpdateAt: function UM_getUpdateAt(index) {
3372 this._ensureUpdates();
3373 return this._updates[index];
3377 * See nsIUpdateService.idl
3380 this._ensureUpdates();
3381 return this._updates.length;
3385 * See nsIUpdateService.idl
3387 get activeUpdate() {
3388 if (this._activeUpdate &&
3389 this._activeUpdate.channel != UpdateChannel.get()) {
3390 LOG("UpdateManager:get activeUpdate - channel has changed, " +
3391 "reloading default preferences to workaround bug 802022");
3392 // Workaround to get distribution preferences loaded (Bug 774618). This
3393 // can be removed after bug 802022 is fixed.
3394 let prefSvc = Services.prefs.QueryInterface(Ci.nsIObserver);
3395 prefSvc.observe(null, "reload-default-prefs", null);
3396 if (this._activeUpdate.channel != UpdateChannel.get()) {
3397 // User switched channels, clear out any old active updates and remove
3398 // partial downloads
3399 this._activeUpdate = null;
3402 // Destroy the updates directory, since we're done with it.
3403 cleanUpUpdatesDir();
3406 return this._activeUpdate;
3408 set activeUpdate(activeUpdate) {
3409 this._addUpdate(activeUpdate);
3410 this._activeUpdate = activeUpdate;
3411 if (!activeUpdate) {
3412 // If |activeUpdate| is null, we have updated both lists - the active list
3413 // and the history list, so we want to write both files.
3417 this._writeUpdatesToXMLFile([this._activeUpdate],
3418 getUpdateFile([FILE_UPDATE_ACTIVE]));
3419 return activeUpdate;
3423 * Add an update to the Updates list. If the item already exists in the list,
3424 * replace the existing value with the new value.
3426 * The nsIUpdate object to add.
3428 _addUpdate: function UM__addUpdate(update) {
3431 this._ensureUpdates();
3432 if (this._updates) {
3433 for (var i = 0; i < this._updates.length; ++i) {
3434 if (this._updates[i] &&
3435 this._updates[i].appVersion == update.appVersion &&
3436 this._updates[i].buildID == update.buildID) {
3437 // Replace the existing entry with the new value, updating
3439 this._updates[i] = update;
3444 // Otherwise add it to the front of the list.
3445 this._updates.unshift(update);
3449 * Serializes an array of updates to an XML file
3451 * An array of nsIUpdate objects
3453 * The nsIFile object to serialize to
3455 _writeUpdatesToXMLFile: function UM__writeUpdatesToXMLFile(updates, file) {
3456 var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
3457 createInstance(Ci.nsIFileOutputStream);
3458 var modeFlags = FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
3459 FileUtils.MODE_TRUNCATE;
3461 file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
3462 fos.init(file, modeFlags, FileUtils.PERMS_FILE, 0);
3465 var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
3466 createInstance(Ci.nsIDOMParser);
3467 const EMPTY_UPDATES_DOCUMENT = "<?xml version=\"1.0\"?><updates xmlns=\"http://www.mozilla.org/2005/app-update\"></updates>";
3468 var doc = parser.parseFromString(EMPTY_UPDATES_DOCUMENT, "text/xml");
3470 for (var i = 0; i < updates.length; ++i) {
3472 doc.documentElement.appendChild(updates[i].serialize(doc));
3475 var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
3476 createInstance(Ci.nsIDOMSerializer);
3477 serializer.serializeToStream(doc.documentElement, fos, null);
3482 FileUtils.closeSafeFileOutputStream(fos);
3486 * See nsIUpdateService.idl
3488 saveUpdates: function UM_saveUpdates() {
3489 this._writeUpdatesToXMLFile([this._activeUpdate],
3490 getUpdateFile([FILE_UPDATE_ACTIVE]));
3491 if (this._activeUpdate)
3492 this._addUpdate(this._activeUpdate);
3494 this._ensureUpdates();
3495 // Don't write updates that have a temporary state to the updates.xml file.
3496 if (this._updates) {
3497 let updates = this._updates.slice();
3498 for (let i = updates.length - 1; i >= 0; --i) {
3499 let state = updates[i].state;
3500 if (state == STATE_NONE || state == STATE_DOWNLOADING ||
3501 state == STATE_APPLIED || state == STATE_APPLIED_SVC ||
3502 state == STATE_PENDING || state == STATE_PENDING_SVC) {
3503 updates.splice(i, 1);
3507 this._writeUpdatesToXMLFile(updates.slice(0, 10),
3508 getUpdateFile([FILE_UPDATES_DB]));
3512 refreshUpdateStatus: function UM_refreshUpdateStatus(update) {
3513 var updateSucceeded = true;
3514 var status = readStatusFile(getUpdatesDir());
3515 var ary = status.split(":");
3516 update.state = ary[0];
3517 if (update.state == STATE_FAILED && ary[1]) {
3518 updateSucceeded = false;
3519 if (!handleUpdateFailure(update, ary[1])) {
3520 handleFallbackToCompleteUpdate(update, true);
3523 if (update.state == STATE_APPLIED && shouldUseService()) {
3524 writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SVC);
3526 var um = Cc["@mozilla.org/updates/update-manager;1"].
3527 getService(Ci.nsIUpdateManager);
3530 if (update.state != STATE_PENDING && update.state != STATE_PENDING_SVC) {
3531 // Destroy the updates directory, since we're done with it.
3532 // Make sure to not do this when the updater has fallen back to
3533 // non-staged updates.
3534 cleanUpUpdatesDir(updateSucceeded);
3537 // Send an observer notification which the update wizard uses in
3538 // order to update its UI.
3539 LOG("UpdateManager:refreshUpdateStatus - Notifying observers that " +
3540 "the update was staged. state: " + update.state + ", status: " + status);
3541 Services.obs.notifyObservers(null, "update-staged", update.state);
3543 // Do this after *everything* else, since it will likely cause the app
3545 #ifdef MOZ_WIDGET_GONK
3546 if (update.state == STATE_APPLIED) {
3547 // Notify the user that an update has been staged and is ready for
3548 // installation (i.e. that they should restart the application). We do
3549 // not notify on failed update attempts.
3550 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
3551 createInstance(Ci.nsIUpdatePrompt);
3552 prompter.showUpdateDownloaded(update, true);
3554 releaseSDCardMountLock();
3557 // Only prompt when the UI isn't already open.
3558 let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
3559 if (Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) ||
3560 windowType && Services.wm.getMostRecentWindow(windowType)) {
3564 if (update.state == STATE_APPLIED || update.state == STATE_APPLIED_SVC ||
3565 update.state == STATE_PENDING || update.state == STATE_PENDING_SVC) {
3566 // Notify the user that an update has been staged and is ready for
3567 // installation (i.e. that they should restart the application).
3568 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
3569 createInstance(Ci.nsIUpdatePrompt);
3570 prompter.showUpdateDownloaded(update, true);
3575 classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
3576 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager, Ci.nsIObserver])
3581 * Checks for new Updates
3584 function Checker() {
3586 Checker.prototype = {
3588 * The XMLHttpRequest object that performs the connection.
3593 * The nsIUpdateCheckListener callback
3598 * The URL of the update service XML file to connect to that contains details
3599 * about available updates.
3601 getUpdateURL: function UC_getUpdateURL(force) {
3602 this._forced = force;
3604 // Use the override URL if specified.
3605 var url = getPref("getCharPref", PREF_APP_UPDATE_URL_OVERRIDE, null);
3607 // Otherwise, construct the update URL from component parts.
3610 url = Services.prefs.getDefaultBranch(null).
3611 getCharPref(PREF_APP_UPDATE_URL);
3616 if (!url || url == "") {
3617 LOG("Checker:getUpdateURL - update URL not defined");
3621 url = url.replace(/%PRODUCT%/g, Services.appinfo.name);
3622 url = url.replace(/%VERSION%/g, Services.appinfo.version);
3623 url = url.replace(/%BUILD_ID%/g, Services.appinfo.appBuildID);
3624 url = url.replace(/%BUILD_TARGET%/g, Services.appinfo.OS + "_" + gABI);
3625 url = url.replace(/%OS_VERSION%/g, gOSVersion);
3626 if (/%LOCALE%/.test(url))
3627 url = url.replace(/%LOCALE%/g, getLocale());
3628 url = url.replace(/%CHANNEL%/g, UpdateChannel.get());
3629 url = url.replace(/%PLATFORM_VERSION%/g, Services.appinfo.platformVersion);
3630 url = url.replace(/%DISTRIBUTION%/g,
3631 getDistributionPrefValue(PREF_APP_DISTRIBUTION));
3632 url = url.replace(/%DISTRIBUTION_VERSION%/g,
3633 getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
3634 url = url.replace(/%CUSTOM%/g, getPref("getCharPref", PREF_APP_UPDATE_CUSTOM, ""));
3635 url = url.replace(/\+/g, "%2B");
3637 #ifdef MOZ_WIDGET_GONK
3638 url = url.replace(/%PRODUCT_MODEL%/g, gProductModel);
3639 url = url.replace(/%PRODUCT_DEVICE%/g, gProductDevice);
3640 url = url.replace(/%B2G_VERSION%/g, getPref("getCharPref", PREF_APP_B2G_VERSION, null));
3644 url += (url.indexOf("?") != -1 ? "&" : "?") + "force=1";
3646 LOG("Checker:getUpdateURL - update URL: " + url);
3651 * See nsIUpdateService.idl
3653 checkForUpdates: function UC_checkForUpdates(listener, force) {
3654 LOG("Checker: checkForUpdates, force: " + force);
3656 throw Cr.NS_ERROR_NULL_POINTER;
3658 Services.obs.notifyObservers(null, "update-check-start", null);
3660 var url = this.getUpdateURL(force);
3661 if (!url || (!this.enabled && !force))
3664 recordInHealthReport(UpdaterHealthReportFields.CHECK_START, 0);
3666 this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
3667 createInstance(Ci.nsISupports);
3668 // This is here to let unit test code override XHR
3669 if (this._request.wrappedJSObject) {
3670 this._request = this._request.wrappedJSObject;
3672 this._request.open("GET", url, true);
3673 var allowNonBuiltIn = !getPref("getBoolPref",
3674 PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true);
3675 this._request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(allowNonBuiltIn);
3676 // Prevent the request from reading from the cache.
3677 this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
3678 // Prevent the request from writing to the cache.
3679 this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
3681 this._request.overrideMimeType("text/xml");
3682 // The Cache-Control header is only interpreted by proxies and the
3683 // final destination. It does not help if a resource is already
3685 this._request.setRequestHeader("Cache-Control", "no-cache");
3686 // HTTP/1.0 servers might not implement Cache-Control and
3687 // might only implement Pragma: no-cache
3688 this._request.setRequestHeader("Pragma", "no-cache");
3691 this._request.addEventListener("error", function(event) { self.onError(event); } ,false);
3692 this._request.addEventListener("load", function(event) { self.onLoad(event); }, false);
3694 LOG("Checker:checkForUpdates - sending request to: " + url);
3695 this._request.send(null);
3697 this._callback = listener;
3701 * Returns an array of nsIUpdate objects discovered by the update check.
3702 * @throws if the XML document element node name is not updates.
3705 var updatesElement = this._request.responseXML.documentElement;
3706 if (!updatesElement) {
3707 LOG("Checker:_updates get - empty updates document?!");
3711 if (updatesElement.nodeName != "updates") {
3712 LOG("Checker:_updates get - unexpected node name!");
3713 throw new Error("Unexpected node name, expected: updates, got: " +
3714 updatesElement.nodeName);
3717 const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
3719 for (var i = 0; i < updatesElement.childNodes.length; ++i) {
3720 var updateElement = updatesElement.childNodes.item(i);
3721 if (updateElement.nodeType != ELEMENT_NODE ||
3722 updateElement.localName != "update")
3725 updateElement.QueryInterface(Ci.nsIDOMElement);
3727 var update = new Update(updateElement);
3729 LOG("Checker:_updates get - invalid <update/>, ignoring...");
3732 update.serviceURL = this.getUpdateURL(this._forced);
3733 update.channel = UpdateChannel.get();
3734 updates.push(update);
3741 * Returns the status code for the XMLHttpRequest
3743 _getChannelStatus: function UC__getChannelStatus(request) {
3746 status = request.status;
3752 status = request.channel.QueryInterface(Ci.nsIRequest).status;
3756 _isHttpStatusCode: function UC__isHttpStatusCode(status) {
3757 return status >= 100 && status <= 599;
3761 * The XMLHttpRequest succeeded and the document was loaded.
3763 * The nsIDOMEvent for the load
3765 onLoad: function UC_onLoad(event) {
3766 LOG("Checker:onLoad - request completed downloading document");
3768 var prefs = Services.prefs;
3770 if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE) &&
3771 getPref("getBoolPref", PREF_APP_UPDATE_CERT_CHECKATTRS, true)) {
3772 certs = gCertUtils.readCertPrefs(PREF_APP_UPDATE_CERTS_BRANCH);
3776 // Analyze the resulting DOM and determine the set of updates.
3777 var updates = this._updates;
3778 LOG("Checker:onLoad - number of updates available: " + updates.length);
3779 var allowNonBuiltIn = !getPref("getBoolPref",
3780 PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true);
3781 gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs);
3783 if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CERT_ERRORS))
3784 Services.prefs.clearUserPref(PREF_APP_UPDATE_CERT_ERRORS);
3786 if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS))
3787 Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
3789 recordInHealthReport(UpdaterHealthReportFields.CHECK_SUCCESS, 0);
3791 // Tell the callback about the updates
3792 this._callback.onCheckComplete(event.target, updates, updates.length);
3795 LOG("Checker:onLoad - there was a problem checking for updates. " +
3797 var request = event.target;
3798 var status = this._getChannelStatus(request);
3799 LOG("Checker:onLoad - request.status: " + status);
3800 var update = new Update(null);
3801 update.errorCode = status;
3802 update.statusText = getStatusTextFromCode(status, 404);
3804 if (this._isHttpStatusCode(status)) {
3805 update.errorCode = HTTP_ERROR_OFFSET + status;
3807 if (e.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
3808 update.errorCode = updates[0] ? CERT_ATTR_CHECK_FAILED_HAS_UPDATE
3809 : CERT_ATTR_CHECK_FAILED_NO_UPDATE;
3812 recordInHealthReport(UpdaterHealthReportFields.CHECK_FAILED, update.errorCode);
3814 this._callback.onError(request, update);
3817 this._callback = null;
3818 this._request = null;
3822 * There was an error of some kind during the XMLHttpRequest
3824 * The nsIDOMEvent for the error
3826 onError: function UC_onError(event) {
3827 var request = event.target;
3828 var status = this._getChannelStatus(request);
3829 LOG("Checker:onError - request.status: " + status);
3831 // If we can't find an error string specific to this status code,
3832 // just use the 200 message from above, which means everything
3833 // "looks" fine but there was probably an XML error or a bogus file.
3834 var update = new Update(null);
3835 update.errorCode = status;
3836 update.statusText = getStatusTextFromCode(status, 200);
3838 if (status == Cr.NS_ERROR_OFFLINE) {
3839 // We use a separate constant here because nsIUpdate.errorCode is signed
3840 update.errorCode = NETWORK_ERROR_OFFLINE;
3841 } else if (this._isHttpStatusCode(status)) {
3842 update.errorCode = HTTP_ERROR_OFFSET + status;
3845 recordInHealthReport(UpdaterHealthReportFields.CHECK_FAILED, update.errorCode);
3847 this._callback.onError(request, update);
3849 this._request = null;
3853 * Whether or not we are allowed to do update checking.
3857 if (!gMetroUpdatesEnabled) {
3861 return getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) &&
3862 gCanCheckForUpdates && hasUpdateMutex() && this._enabled;
3866 * See nsIUpdateService.idl
3868 stopChecking: function UC_stopChecking(duration) {
3869 // Always stop the current check
3871 this._request.abort();
3874 case Ci.nsIUpdateChecker.CURRENT_SESSION:
3875 this._enabled = false;
3877 case Ci.nsIUpdateChecker.ANY_CHECKS:
3878 this._enabled = false;
3879 Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled);
3883 this._callback = null;
3886 classID: Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"),
3887 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateChecker])
3891 * Manages the download of updates
3893 * Whether or not this downloader is operating in background
3895 * @param updateService
3896 * The update service that created this downloader.
3899 function Downloader(background, updateService) {
3900 LOG("Creating Downloader");
3901 this.background = background;
3902 this.updateService = updateService;
3904 Downloader.prototype = {
3906 * The nsIUpdatePatch that we are downloading
3911 * The nsIUpdate that we are downloading
3916 * The nsIIncrementalDownload object handling the download
3921 * Whether or not the update being downloaded is a complete replacement of
3922 * the user's existing installation or a patch representing the difference
3923 * between the new version and the previous version.
3925 isCompleteUpdate: null,
3928 * Cancels the active download.
3930 cancel: function Downloader_cancel(cancelError) {
3931 LOG("Downloader: cancel");
3932 if (cancelError === undefined) {
3933 cancelError = Cr.NS_BINDING_ABORTED;
3935 if (this._request && this._request instanceof Ci.nsIRequest) {
3936 this._request.cancel(cancelError);
3938 releaseSDCardMountLock();
3942 * Whether or not a patch has been downloaded and staged for installation.
3944 get patchIsStaged() {
3945 var readState = readStatusFile(getUpdatesDir());
3946 // Note that if we decide to download and apply new updates after another
3947 // update has been successfully applied in the background, we need to stop
3948 // checking for the APPLIED state here.
3949 return readState == STATE_PENDING || readState == STATE_PENDING_SVC ||
3950 readState == STATE_APPLIED || readState == STATE_APPLIED_SVC;
3954 * Verify the downloaded file. We assume that the download is complete at
3957 _verifyDownload: function Downloader__verifyDownload() {
3958 LOG("Downloader:_verifyDownload called");
3962 var destination = this._request.destination;
3964 // Ensure that the file size matches the expected file size.
3965 if (destination.fileSize != this._patch.size) {
3966 LOG("Downloader:_verifyDownload downloaded size != expected size.");
3970 LOG("Downloader:_verifyDownload downloaded size == expected size.");
3971 var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
3972 createInstance(Ci.nsIFileInputStream);
3973 fileStream.init(destination, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
3976 var hash = Cc["@mozilla.org/security/hash;1"].
3977 createInstance(Ci.nsICryptoHash);
3978 var hashFunction = Ci.nsICryptoHash[this._patch.hashFunction.toUpperCase()];
3979 if (hashFunction == undefined)
3980 throw Cr.NS_ERROR_UNEXPECTED;
3981 hash.init(hashFunction);
3982 hash.updateFromStream(fileStream, -1);
3983 // NOTE: For now, we assume that the format of _patch.hashValue is hex
3984 // encoded binary (such as what is typically output by programs like
3985 // sha1sum). In the future, this may change to base64 depending on how
3986 // we choose to compute these hashes.
3987 digest = binaryToHex(hash.finish(false));
3989 LOG("Downloader:_verifyDownload - failed to compute hash of the " +
3990 "downloaded update archive");
3996 if (digest == this._patch.hashValue.toLowerCase()) {
3997 LOG("Downloader:_verifyDownload hashes match.");
4001 LOG("Downloader:_verifyDownload hashes do not match. ");
4006 * Select the patch to use given the current state of updateDir and the given
4007 * set of update patches.
4009 * A nsIUpdate object to select a patch from
4011 * A nsIFile representing the update directory
4012 * @return A nsIUpdatePatch object to download
4014 _selectPatch: function Downloader__selectPatch(update, updateDir) {
4015 // Given an update to download, we will always try to download the patch
4016 // for a partial update over the patch for a full update.
4019 * Return the first UpdatePatch with the given type.
4021 * The type of the patch ("complete" or "partial")
4022 * @return A nsIUpdatePatch object matching the type specified
4024 function getPatchOfType(type) {
4025 for (var i = 0; i < update.patchCount; ++i) {
4026 var patch = update.getPatchAt(i);
4027 if (patch && patch.type == type)
4033 // Look to see if any of the patches in the Update object has been
4034 // pre-selected for download, otherwise we must figure out which one
4035 // to select ourselves.
4036 var selectedPatch = update.selectedPatch;
4038 var state = readStatusFile(updateDir);
4040 // If this is a patch that we know about, then select it. If it is a patch
4041 // that we do not know about, then remove it and use our default logic.
4042 var useComplete = false;
4043 if (selectedPatch) {
4044 LOG("Downloader:_selectPatch - found existing patch with state: " +
4047 case STATE_DOWNLOADING:
4048 LOG("Downloader:_selectPatch - resuming download");
4049 return selectedPatch;
4050 #ifdef MOZ_WIDGET_GONK
4052 case STATE_APPLYING:
4053 LOG("Downloader:_selectPatch - resuming interrupted apply");
4054 return selectedPatch;
4056 LOG("Downloader:_selectPatch - already downloaded and staged");
4059 case STATE_PENDING_SVC:
4061 LOG("Downloader:_selectPatch - already downloaded and staged");
4065 // Something went wrong when we tried to apply the previous patch.
4066 // Try the complete patch next time.
4067 if (update && selectedPatch.type == "partial") {
4070 // This is a pretty fatal error. Just bail.
4071 LOG("Downloader:_selectPatch - failed to apply complete patch!");
4072 writeStatusFile(updateDir, STATE_NONE);
4073 writeVersionFile(getUpdatesDir(), null);
4078 selectedPatch = null;
4081 // If we were not able to discover an update from a previous download, we
4082 // select the best patch from the given set.
4083 var partialPatch = getPatchOfType("partial");
4085 selectedPatch = partialPatch;
4086 if (!selectedPatch) {
4088 partialPatch.selected = false;
4089 selectedPatch = getPatchOfType("complete");
4092 // Restore the updateDir since we may have deleted it.
4093 updateDir = getUpdatesDir();
4095 // if update only contains a partial patch, selectedPatch == null here if
4096 // the partial patch has been attempted and fails and we're trying to get a
4099 selectedPatch.selected = true;
4101 update.isCompleteUpdate = useComplete;
4103 // Reset the Active Update object on the Update Manager and flush the
4104 // Active Update DB.
4105 var um = Cc["@mozilla.org/updates/update-manager;1"].
4106 getService(Ci.nsIUpdateManager);
4107 um.activeUpdate = update;
4109 return selectedPatch;
4113 * Whether or not we are currently downloading something.
4116 return this._request != null;
4120 * Get the nsIFile to use for downloading the active update's selected patch
4122 _getUpdateArchiveFile: function Downloader__getUpdateArchiveFile() {
4124 #ifdef USE_UPDATE_ARCHIVE_DIR
4126 updateArchive = FileUtils.getDir(KEY_UPDATE_ARCHIVE_DIR, [], true);
4131 updateArchive = getUpdatesDir().clone();
4134 updateArchive.append(FILE_UPDATE_ARCHIVE);
4135 return updateArchive;
4139 * Download and stage the given update.
4141 * A nsIUpdate object to download a patch for. Cannot be null.
4143 downloadUpdate: function Downloader_downloadUpdate(update) {
4144 LOG("UpdateService:_downloadUpdate");
4146 throw Cr.NS_ERROR_NULL_POINTER;
4148 var updateDir = getUpdatesDir();
4150 this._update = update;
4152 // This function may return null, which indicates that there are no patches
4154 this._patch = this._selectPatch(update, updateDir);
4156 LOG("Downloader:downloadUpdate - no patch to download");
4157 return readStatusFile(updateDir);
4159 this.isCompleteUpdate = this._patch.type == "complete";
4161 recordInHealthReport(
4162 this.isCompleteUpdate ? UpdaterHealthReportFields.COMPLETE_START :
4163 UpdaterHealthReportFields.PARTIAL_START, 0);
4165 var patchFile = null;
4167 #ifdef MOZ_WIDGET_GONK
4168 let status = readStatusFile(updateDir);
4169 if (isInterruptedUpdate(status)) {
4170 LOG("Downloader:downloadUpdate - interruptted update");
4171 // The update was interrupted. Try to locate the existing patch file.
4172 // For an interrupted download, this allows a resume rather than a
4174 patchFile = getFileFromUpdateLink(updateDir);
4176 // No link file. We'll just assume that the update.mar is in the
4177 // update directory.
4178 patchFile = updateDir.clone();
4179 patchFile.append(FILE_UPDATE_ARCHIVE);
4181 if (patchFile.exists()) {
4182 LOG("Downloader:downloadUpdate - resuming with patchFile " + patchFile.path);
4183 if (patchFile.fileSize == this._patch.size) {
4184 LOG("Downloader:downloadUpdate - patchFile appears to be fully downloaded");
4185 // Bump the status along so that we don't try to redownload again.
4186 status = STATE_PENDING;
4189 LOG("Downloader:downloadUpdate - patchFile " + patchFile.path +
4190 " doesn't exist - performing full download");
4191 // The patchfile doesn't exist, we might as well treat this like
4195 if (patchFile && (status != STATE_DOWNLOADING)) {
4196 // It looks like the patch was downloaded, but got interrupted while it
4197 // was being verified or applied. So we'll fake the downloading portion.
4199 writeStatusFile(updateDir, STATE_PENDING);
4201 // Since the code expects the onStopRequest callback to happen
4202 // asynchronously (And you have to call AUS_addDownloadListener
4203 // after calling AUS_downloadUpdate) we need to defer this.
4205 this._downloadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
4206 this._downloadTimer.initWithCallback(function() {
4207 this._downloadTimer = null;
4208 // Send a fake onStopRequest. Filling in the destination allows
4209 // _verifyDownload to work, and then the update will be applied.
4210 this._request = {destination: patchFile};
4211 this.onStopRequest(this._request, null, Cr.NS_OK);
4212 }.bind(this), 0, Ci.nsITimer.TYPE_ONE_SHOT);
4214 // Returning STATE_DOWNLOADING makes UpdatePrompt think we're
4215 // downloading. The onStopRequest that we spoofed above will make it
4216 // look like the download finished.
4217 return STATE_DOWNLOADING;
4222 // Find a place to put the patchfile that we're going to download.
4223 patchFile = this._getUpdateArchiveFile();
4229 #ifdef MOZ_WIDGET_GONK
4230 if (patchFile.path.indexOf(updateDir.path) != 0) {
4231 // The patchFile is in a directory which is different from the
4232 // updateDir, create a link file.
4233 writeLinkFile(updateDir, patchFile);
4235 if (!isInterruptedUpdate(status) && patchFile.exists()) {
4236 // Remove stale patchFile
4237 patchFile.remove(false);
4242 var uri = Services.io.newURI(this._patch.URL, null, null);
4244 this._request = Cc["@mozilla.org/network/incremental-download;1"].
4245 createInstance(Ci.nsIIncrementalDownload);
4247 LOG("Downloader:downloadUpdate - downloading from " + uri.spec + " to " +
4249 var interval = this.background ? getPref("getIntPref",
4250 PREF_APP_UPDATE_BACKGROUND_INTERVAL,
4251 DOWNLOAD_BACKGROUND_INTERVAL)
4252 : DOWNLOAD_FOREGROUND_INTERVAL;
4253 this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval);
4254 this._request.start(this, null);
4256 writeStatusFile(updateDir, STATE_DOWNLOADING);
4257 this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
4258 this._patch.state = STATE_DOWNLOADING;
4259 var um = Cc["@mozilla.org/updates/update-manager;1"].
4260 getService(Ci.nsIUpdateManager);
4262 return STATE_DOWNLOADING;
4266 * An array of download listeners to notify when we receive
4267 * nsIRequestObserver or nsIProgressEventSink method calls.
4272 * Adds a listener to the download process
4274 * A download listener, implementing nsIRequestObserver and
4275 * nsIProgressEventSink
4277 addDownloadListener: function Downloader_addDownloadListener(listener) {
4278 for (var i = 0; i < this._listeners.length; ++i) {
4279 if (this._listeners[i] == listener)
4282 this._listeners.push(listener);
4286 * Removes a download listener
4288 * The listener to remove.
4290 removeDownloadListener: function Downloader_removeDownloadListener(listener) {
4291 for (var i = 0; i < this._listeners.length; ++i) {
4292 if (this._listeners[i] == listener) {
4293 this._listeners.splice(i, 1);
4300 * When the async request begins
4302 * The nsIRequest object for the transfer
4306 onStartRequest: function Downloader_onStartRequest(request, context) {
4307 if (request instanceof Ci.nsIIncrementalDownload)
4308 LOG("Downloader:onStartRequest - original URI spec: " + request.URI.spec +
4309 ", final URI spec: " + request.finalURI.spec);
4310 // Always set finalURL in onStartRequest since it can change.
4311 this._patch.finalURL = request.finalURI.spec;
4312 var um = Cc["@mozilla.org/updates/update-manager;1"].
4313 getService(Ci.nsIUpdateManager);
4316 var listeners = this._listeners.concat();
4317 var listenerCount = listeners.length;
4318 for (var i = 0; i < listenerCount; ++i)
4319 listeners[i].onStartRequest(request, context);
4323 * When new data has been downloaded
4325 * The nsIRequest object for the transfer
4329 * The current number of bytes transferred
4330 * @param maxProgress
4331 * The total number of bytes that must be transferred
4333 onProgress: function Downloader_onProgress(request, context, progress,
4335 LOG("Downloader:onProgress - progress: " + progress + "/" + maxProgress);
4337 if (progress > this._patch.size) {
4338 LOG("Downloader:onProgress - progress: " + progress +
4339 " is higher than patch size: " + this._patch.size);
4340 // It's important that we use a different code than
4341 // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
4342 // between a hash error and a wrong download error.
4343 this.cancel(Cr.NS_ERROR_UNEXPECTED);
4347 if (maxProgress != this._patch.size) {
4348 LOG("Downloader:onProgress - maxProgress: " + maxProgress +
4349 " is not equal to expectd patch size: " + this._patch.size);
4350 // It's important that we use a different code than
4351 // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
4352 // between a hash error and a wrong download error.
4353 this.cancel(Cr.NS_ERROR_UNEXPECTED);
4357 var listeners = this._listeners.concat();
4358 var listenerCount = listeners.length;
4359 for (var i = 0; i < listenerCount; ++i) {
4360 var listener = listeners[i];
4361 if (listener instanceof Ci.nsIProgressEventSink)
4362 listener.onProgress(request, context, progress, maxProgress);
4364 this.updateService._consecutiveSocketErrors = 0;
4368 * When we have new status text
4370 * The nsIRequest object for the transfer
4376 * Human readable version of |status|
4378 onStatus: function Downloader_onStatus(request, context, status, statusText) {
4379 LOG("Downloader:onStatus - status: " + status + ", statusText: " +
4382 var listeners = this._listeners.concat();
4383 var listenerCount = listeners.length;
4384 for (var i = 0; i < listenerCount; ++i) {
4385 var listener = listeners[i];
4386 if (listener instanceof Ci.nsIProgressEventSink)
4387 listener.onStatus(request, context, status, statusText);
4392 * When data transfer ceases
4394 * The nsIRequest object for the transfer
4398 * Status code containing the reason for the cessation.
4400 onStopRequest: function Downloader_onStopRequest(request, context, status) {
4401 if (request instanceof Ci.nsIIncrementalDownload)
4402 LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec +
4403 ", final URI spec: " + request.finalURI.spec + ", status: " + status);
4405 // XXX ehsan shouldShowPrompt should always be false here.
4406 // But what happens when there is already a UI showing?
4407 var state = this._patch.state;
4408 var shouldShowPrompt = false;
4409 var shouldRegisterOnlineObserver = false;
4410 var shouldRetrySoon = false;
4411 var deleteActiveUpdate = false;
4412 var retryTimeout = getPref("getIntPref", PREF_APP_UPDATE_RETRY_TIMEOUT,
4413 DEFAULT_UPDATE_RETRY_TIMEOUT);
4414 var maxFail = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_ERRORS,
4415 DEFAULT_SOCKET_MAX_ERRORS);
4416 LOG("Downloader:onStopRequest - status: " + status + ", " +
4417 "current fail: " + this.updateService._consecutiveSocketErrors + ", " +
4418 "max fail: " + maxFail + ", " + "retryTimeout: " + retryTimeout);
4419 if (Components.isSuccessCode(status)) {
4420 recordInHealthReport(
4421 this.isCompleteUpdate ? UpdaterHealthReportFields.COMPLETE_SUCCESS :
4422 UpdaterHealthReportFields.PARTIAL_SUCCESS, 0);
4424 if (this._verifyDownload()) {
4425 state = shouldUseService() ? STATE_PENDING_SVC : STATE_PENDING;
4426 if (this.background) {
4427 shouldShowPrompt = !getCanStageUpdates();
4430 // Tell the updater.exe we're ready to apply.
4431 writeStatusFile(getUpdatesDir(), state);
4432 writeVersionFile(getUpdatesDir(), this._update.appVersion);
4433 this._update.installDate = (new Date()).getTime();
4434 this._update.statusText = gUpdateBundle.GetStringFromName("installPending");
4437 LOG("Downloader:onStopRequest - download verification failed");
4438 state = STATE_DOWNLOAD_FAILED;
4439 status = Cr.NS_ERROR_CORRUPTED_CONTENT;
4441 // Yes, this code is a string.
4442 const vfCode = "verification_failed";
4443 var message = getStatusTextFromCode(vfCode, vfCode);
4444 this._update.statusText = message;
4446 if (this._update.isCompleteUpdate || this._update.patchCount != 2)
4447 deleteActiveUpdate = true;
4449 // Destroy the updates directory, since we're done with it.
4450 cleanUpUpdatesDir();
4453 recordInHealthReport(UpdaterHealthReportFields.FAILED, status);
4455 if (status == Cr.NS_ERROR_OFFLINE) {
4456 // Register an online observer to try again.
4457 // The online observer will continue the incremental download by
4458 // calling downloadUpdate on the active update which continues
4459 // downloading the file from where it was.
4460 LOG("Downloader:onStopRequest - offline, register online observer: true");
4461 shouldRegisterOnlineObserver = true;
4462 deleteActiveUpdate = false;
4463 // Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED, and
4464 // NS_ERROR_NET_RESET can be returned when disconnecting the internet while
4465 // a download of a MAR is in progress. There may be others but I have not
4466 // encountered them during testing.
4467 } else if ((status == Cr.NS_ERROR_NET_TIMEOUT ||
4468 status == Cr.NS_ERROR_CONNECTION_REFUSED ||
4469 status == Cr.NS_ERROR_NET_RESET) &&
4470 this.updateService._consecutiveSocketErrors < maxFail) {
4471 LOG("Downloader:onStopRequest - socket error, shouldRetrySoon: true");
4472 shouldRetrySoon = true;
4473 deleteActiveUpdate = false;
4474 } else if (status != Cr.NS_BINDING_ABORTED &&
4475 status != Cr.NS_ERROR_ABORT &&
4476 status != Cr.NS_ERROR_DOCUMENT_NOT_CACHED) {
4477 LOG("Downloader:onStopRequest - non-verification failure");
4478 // Some sort of other failure, log this in the |statusText| property
4479 state = STATE_DOWNLOAD_FAILED;
4481 // XXXben - if |request| (The Incremental Download) provided a means
4482 // for accessing the http channel we could do more here.
4484 this._update.statusText = getStatusTextFromCode(status,
4485 Cr.NS_BINDING_FAILED);
4487 #ifdef MOZ_WIDGET_GONK
4488 // bug891009: On FirefoxOS, manaully retry OTA download will reuse
4489 // the Update object. We need to remove selected patch so that download
4490 // can be triggered again successfully.
4491 this._update.selectedPatch.selected = false;
4494 // Destroy the updates directory, since we're done with it.
4495 cleanUpUpdatesDir();
4497 deleteActiveUpdate = true;
4500 LOG("Downloader:onStopRequest - setting state to: " + state);
4501 this._patch.state = state;
4502 var um = Cc["@mozilla.org/updates/update-manager;1"].
4503 getService(Ci.nsIUpdateManager);
4504 if (deleteActiveUpdate) {
4505 this._update.installDate = (new Date()).getTime();
4506 um.activeUpdate = null;
4509 if (um.activeUpdate)
4510 um.activeUpdate.state = state;
4514 // Only notify listeners about the stopped state if we
4515 // aren't handling an internal retry.
4516 if (!shouldRetrySoon && !shouldRegisterOnlineObserver) {
4517 var listeners = this._listeners.concat();
4518 var listenerCount = listeners.length;
4519 for (var i = 0; i < listenerCount; ++i) {
4520 listeners[i].onStopRequest(request, context, status);
4524 this._request = null;
4526 if (state == STATE_DOWNLOAD_FAILED) {
4527 var allFailed = true;
4528 // Check if there is a complete update patch that can be downloaded.
4529 if (!this._update.isCompleteUpdate && this._update.patchCount == 2) {
4530 LOG("Downloader:onStopRequest - verification of patch failed, " +
4531 "downloading complete update patch");
4532 this._update.isCompleteUpdate = true;
4533 let updateStatus = this.downloadUpdate(this._update);
4535 if (updateStatus == STATE_NONE) {
4536 cleanupActiveUpdate();
4543 LOG("Downloader:onStopRequest - all update patch downloads failed");
4544 // If the update UI is not open (e.g. the user closed the window while
4545 // downloading) and if at any point this was a foreground download
4546 // notify the user about the error. If the update was a background
4547 // update there is no notification since the user won't be expecting it.
4548 if (!Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME)) {
4550 this._update.QueryInterface(Ci.nsIWritablePropertyBag);
4551 var fgdl = this._update.getProperty("foregroundDownload");
4556 if (fgdl == "true") {
4557 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
4558 createInstance(Ci.nsIUpdatePrompt);
4559 prompter.showUpdateError(this._update);
4563 #ifdef MOZ_WIDGET_GONK
4564 // We always forward errors in B2G, since Gaia controls the update UI
4565 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
4566 createInstance(Ci.nsIUpdatePrompt);
4567 prompter.showUpdateError(this._update);
4570 // Prevent leaking the update object (bug 454964).
4571 this._update = null;
4573 // A complete download has been initiated or the failure was handled.
4577 if (state == STATE_PENDING || state == STATE_PENDING_SVC) {
4578 if (getCanStageUpdates()) {
4579 LOG("Downloader:onStopRequest - attempting to stage update: " +
4582 // Initiate the update in the background
4584 Cc["@mozilla.org/updates/update-processor;1"].
4585 createInstance(Ci.nsIUpdateProcessor).
4586 processUpdate(this._update);
4588 // Fail gracefully in case the application does not support the update
4589 // processor service.
4590 LOG("Downloader:onStopRequest - failed to stage update. Exception: " +
4592 if (this.background) {
4593 shouldShowPrompt = true;
4599 // Do this after *everything* else, since it will likely cause the app
4601 if (shouldShowPrompt) {
4602 // Notify the user that an update has been downloaded and is ready for
4603 // installation (i.e. that they should restart the application). We do
4604 // not notify on failed update attempts.
4605 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
4606 createInstance(Ci.nsIUpdatePrompt);
4607 prompter.showUpdateDownloaded(this._update, true);
4610 if (shouldRegisterOnlineObserver) {
4611 LOG("Downloader:onStopRequest - Registering online observer");
4612 this.updateService._registerOnlineObserver();
4613 } else if (shouldRetrySoon) {
4614 LOG("Downloader:onStopRequest - Retrying soon");
4615 this.updateService._consecutiveSocketErrors++;
4616 if (this.updateService._retryTimer) {
4617 this.updateService._retryTimer.cancel();
4619 this.updateService._retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
4620 this.updateService._retryTimer.initWithCallback(function() {
4621 this._attemptResume();
4622 }.bind(this.updateService), retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
4624 // Prevent leaking the update object (bug 454964)
4625 this._update = null;
4630 * See nsIInterfaceRequestor.idl
4632 getInterface: function Downloader_getInterface(iid) {
4633 // The network request may require proxy authentication, so provide the
4634 // default nsIAuthPrompt if requested.
4635 if (iid.equals(Ci.nsIAuthPrompt)) {
4636 var prompt = Cc["@mozilla.org/network/default-auth-prompt;1"].
4638 return prompt.QueryInterface(iid);
4640 throw Cr.NS_NOINTERFACE;
4643 QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
4644 Ci.nsIProgressEventSink,
4645 Ci.nsIInterfaceRequestor])
4650 * An object which can prompt the user with information about updates, request
4651 * action, etc. Embedding clients can override this component with one that
4652 * invokes a native front end.
4655 function UpdatePrompt() {
4657 UpdatePrompt.prototype = {
4659 * See nsIUpdateService.idl
4661 checkForUpdates: function UP_checkForUpdates() {
4662 if (this._getAltUpdateWindow())
4665 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
4670 * See nsIUpdateService.idl
4672 showUpdateAvailable: function UP_showUpdateAvailable(update) {
4673 if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
4674 this._getUpdateWindow() || this._getAltUpdateWindow())
4677 var stringsPrefix = "updateAvailable_" + update.type + ".";
4678 var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title",
4680 var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
4682 this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
4683 UPDATE_WINDOW_NAME, "updatesavailable", update,
4684 title, text, imageUrl);
4688 * See nsIUpdateService.idl
4690 showUpdateDownloaded: function UP_showUpdateDownloaded(update, background) {
4691 if (this._getAltUpdateWindow())
4695 if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false))
4698 var stringsPrefix = "updateDownloaded_" + update.type + ".";
4699 var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title",
4701 var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
4703 this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
4704 UPDATE_WINDOW_NAME, "finishedBackground", update,
4705 title, text, imageUrl);
4707 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
4708 UPDATE_WINDOW_NAME, "finishedBackground", update);
4713 * See nsIUpdateService.idl
4715 showUpdateInstalled: function UP_showUpdateInstalled() {
4716 if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
4717 !getPref("getBoolPref", PREF_APP_UPDATE_SHOW_INSTALLED_UI, false) ||
4718 this._getUpdateWindow())
4721 var page = "installed";
4722 var win = this._getUpdateWindow();
4724 if (page && "setCurrentPage" in win)
4725 win.setCurrentPage(page);
4729 var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
4730 var arg = Cc["@mozilla.org/supports-string;1"].
4731 createInstance(Ci.nsISupportsString);
4733 Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, null, openFeatures, arg);
4738 * See nsIUpdateService.idl
4740 showUpdateError: function UP_showUpdateError(update) {
4741 if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
4742 this._getAltUpdateWindow())
4745 // In some cases, we want to just show a simple alert dialog:
4746 if (update.state == STATE_FAILED &&
4747 (update.errorCode == WRITE_ERROR ||
4748 update.errorCode == WRITE_ERROR_ACCESS_DENIED ||
4749 update.errorCode == WRITE_ERROR_SHARING_VIOLATION_SIGNALED ||
4750 update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID ||
4751 update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPID ||
4752 update.errorCode == WRITE_ERROR_CALLBACK_APP ||
4753 update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR ||
4754 update.errorCode == FOTA_GENERAL_ERROR ||
4755 update.errorCode == FOTA_FILE_OPERATION_ERROR ||
4756 update.errorCode == FOTA_RECOVERY_ERROR ||
4757 update.errorCode == FOTA_UNKNOWN_ERROR)) {
4758 var title = gUpdateBundle.GetStringFromName("updaterIOErrorTitle");
4759 var text = gUpdateBundle.formatStringFromName("updaterIOErrorMsg",
4760 [Services.appinfo.name,
4761 Services.appinfo.name], 2);
4762 Services.ww.getNewPrompter(null).alert(title, text);
4766 if (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
4767 update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE ||
4768 update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) {
4769 this._showUIWhenIdle(null, URI_UPDATE_PROMPT_DIALOG, null,
4770 UPDATE_WINDOW_NAME, null, update);
4774 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
4779 * See nsIUpdateService.idl
4781 showUpdateHistory: function UP_showUpdateHistory(parent) {
4782 this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal,dialog=yes",
4783 "Update:History", null, null);
4787 * Returns the update window if present.
4789 _getUpdateWindow: function UP__getUpdateWindow() {
4790 return Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME);
4794 * Returns an alternative update window if present. When a window with this
4795 * windowtype is open the application update service won't open the normal
4796 * application update user interface window.
4798 _getAltUpdateWindow: function UP__getAltUpdateWindow() {
4799 let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
4802 return Services.wm.getMostRecentWindow(windowType);
4806 * Initiate a less obtrusive UI, starting with a non-modal notification alert
4808 * A parent window, can be null
4810 * The URI string of the dialog to show
4812 * The Window Name of the dialog to show, in case it is already open
4813 * and can merely be focused
4815 * The page of the wizard to be displayed, if one is already open.
4817 * An update to pass to the UI in the window arguments.
4820 * The title for the notification alert.
4822 * The contents of the notification alert.
4824 * A URL identifying the image to put in the notification alert.
4826 _showUnobtrusiveUI: function UP__showUnobUI(parent, uri, features, name, page,
4827 update, title, text, imageUrl) {
4832 notify: function () {
4833 // the user hasn't restarted yet => prompt when idle
4834 this.service.removeObserver(this, "quit-application");
4835 // If the update window is already open skip showing the UI
4836 if (this.updatePrompt._getUpdateWindow())
4838 this.updatePrompt._showUIWhenIdle(parent, uri, features, name, page, update);
4840 observe: function (aSubject, aTopic, aData) {
4842 case "alertclickcallback":
4843 this.updatePrompt._showUI(parent, uri, features, name, page, update);
4845 case "quit-application":
4847 this.timer.cancel();
4848 this.service.removeObserver(this, "quit-application");
4854 // bug 534090 - show the UI for update available notifications when the
4855 // the system has been idle for at least IDLE_TIME without displaying an
4856 // alert notification.
4857 if (page == "updatesavailable") {
4858 var idleService = Cc["@mozilla.org/widget/idleservice;1"].
4859 getService(Ci.nsIIdleService);
4861 const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
4862 if (idleService.idleTime / 1000 >= IDLE_TIME) {
4863 this._showUI(parent, uri, features, name, page, update);
4869 var notifier = Cc["@mozilla.org/alerts-service;1"].
4870 getService(Ci.nsIAlertsService);
4871 notifier.showAlertNotification(imageUrl, title, text, true, "", observer);
4874 // Failed to retrieve alerts service, platform unsupported
4875 this._showUIWhenIdle(parent, uri, features, name, page, update);
4879 observer.service = Services.obs;
4880 observer.service.addObserver(observer, "quit-application", false);
4882 // bug 534090 - show the UI when idle for update available notifications.
4883 if (page == "updatesavailable") {
4884 this._showUIWhenIdle(parent, uri, features, name, page, update);
4888 // Give the user x seconds to react before prompting as defined by
4890 observer.timer = Cc["@mozilla.org/timer;1"].
4891 createInstance(Ci.nsITimer);
4892 observer.timer.initWithCallback(observer, update.promptWaitTime * 1000,
4893 observer.timer.TYPE_ONE_SHOT);
4897 * Show the UI when the user was idle
4899 * A parent window, can be null
4901 * The URI string of the dialog to show
4903 * The Window Name of the dialog to show, in case it is already open
4904 * and can merely be focused
4906 * The page of the wizard to be displayed, if one is already open.
4908 * An update to pass to the UI in the window arguments.
4911 _showUIWhenIdle: function UP__showUIWhenIdle(parent, uri, features, name,
4913 var idleService = Cc["@mozilla.org/widget/idleservice;1"].
4914 getService(Ci.nsIIdleService);
4916 const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
4917 if (idleService.idleTime / 1000 >= IDLE_TIME) {
4918 this._showUI(parent, uri, features, name, page, update);
4922 observe: function (aSubject, aTopic, aData) {
4925 // If the update window is already open skip showing the UI
4926 if (!this.updatePrompt._getUpdateWindow())
4927 this.updatePrompt._showUI(parent, uri, features, name, page, update);
4929 case "quit-application":
4930 idleService.removeIdleObserver(this, IDLE_TIME);
4931 Services.obs.removeObserver(this, "quit-application");
4936 idleService.addIdleObserver(observer, IDLE_TIME);
4937 Services.obs.addObserver(observer, "quit-application", false);
4942 * Show the Update Checking UI
4944 * A parent window, can be null
4946 * The URI string of the dialog to show
4948 * The Window Name of the dialog to show, in case it is already open
4949 * and can merely be focused
4951 * The page of the wizard to be displayed, if one is already open.
4953 * An update to pass to the UI in the window arguments.
4956 _showUI: function UP__showUI(parent, uri, features, name, page, update) {
4959 ary = Cc["@mozilla.org/supports-array;1"].
4960 createInstance(Ci.nsISupportsArray);
4961 ary.AppendElement(update);
4964 var win = this._getUpdateWindow();
4966 if (page && "setCurrentPage" in win)
4967 win.setCurrentPage(page);
4971 var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
4973 openFeatures += "," + features;
4974 Services.ww.openWindow(parent, uri, "", openFeatures, ary);
4978 classDescription: "Update Prompt",
4979 contractID: "@mozilla.org/updates/update-prompt;1",
4980 classID: Components.ID("{27ABA825-35B5-4018-9FDD-F99250A0E722}"),
4981 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt])
4984 var components = [UpdateService, Checker, UpdatePrompt, UpdateManager];
4985 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
4989 * Logs a message and stack trace to the console.
4991 * The string to write to the console.
4993 function STACK(string) {
4994 dump("*** " + string + "\n");
4995 stackTrace(arguments.callee.caller.arguments, -1);
4998 function stackTraceFunctionFormat(aFunctionName) {
4999 var classDelimiter = aFunctionName.indexOf("_");
5000 var className = aFunctionName.substr(0, classDelimiter);
5002 className = "<global>";
5003 var functionName = aFunctionName.substr(classDelimiter + 1, aFunctionName.length);
5005 functionName = "<anonymous>";
5006 return className + "::" + functionName;
5009 function stackTraceArgumentsFormat(aArguments) {
5011 for (var i = 0; i < aArguments.length; i++) {
5012 arglist += aArguments[i];
5013 if (i < aArguments.length - 1)
5019 function stackTrace(aArguments, aMaxCount) {
5020 dump("=[STACKTRACE]=====================================================\n");
5021 dump("*** at: " + stackTraceFunctionFormat(aArguments.callee.name) + "(" +
5022 stackTraceArgumentsFormat(aArguments) + ")\n");
5023 var temp = aArguments.callee.caller;
5026 dump("*** " + stackTraceFunctionFormat(temp.name) + "(" +
5027 stackTraceArgumentsFormat(temp.arguments) + ")\n");
5029 temp = temp.arguments.callee.caller;
5030 if (aMaxCount > 0 && ++count == aMaxCount)
5033 dump("==================================================================\n");
5036 function dumpFile(file) {
5037 dump("*** file = " + file.path + ", exists = " + file.exists() + "\n");