Bumping manifests a=b2g-bump
[gecko.git] / toolkit / mozapps / update / nsUpdateService.js
blobfe88c1e56bc362dc4ea342be90d1b476717cca67
1 #filter substitution
3 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
4 /*
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/.
8 */
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");
17 #endif
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
83 #endif
85 #ifdef USE_UPDATE_ARCHIVE_DIR
86 const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD"
87 #endif
89 #ifdef XP_WIN
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
95 #endif
97 const DIR_UPDATES         = "updates";
98 #ifdef XP_MACOSX
99 const UPDATED_DIR         = "Updated.app";
100 #else
101 const UPDATED_DIR         = "updated";
102 #endif
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";
107 #else
108 const FILE_UPDATE_ARCHIVE = "update.mar";
109 #endif
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()
203 // (no notification)
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
219 // (no notification)
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
225 // (no notification)
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
231 // (no notification)
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"
276 var gLocale = null;
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");
285 #endif
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() {
300   let temp = { };
301   Cu.import("resource://gre/modules/CertUtils.jsm", temp);
302   return temp;
305 XPCOMUtils.defineLazyGetter(this, "gABI", function aus_gABI() {
306   let abi = null;
307   try {
308     abi = Services.appinfo.XPCOMABI;
309   }
310   catch (e) {
311     LOG("gABI - XPCOM ABI unknown: updates are not possible.");
312   }
313 #ifdef XP_MACOSX
314   // Mac universal build should report a different ABI than either macppc
315   // or mactel.
316   let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
317                  getService(Ci.nsIMacUtils);
319   if (macutils.isUniversalBinary)
320     abi += "-u-" + macutils.architecturesInBinary;
321 #ifdef MOZ_SHARK
322   // Disambiguate optimised and shark nightlies
323   abi += "-shark"
324 #endif
325 #endif
326   return abi;
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");
338 #endif
340 XPCOMUtils.defineLazyGetter(this, "gOSVersion", function aus_gOSVersion() {
341   let osVersion;
342   let sysInfo = Cc["@mozilla.org/system-info;1"].
343                 getService(Ci.nsIPropertyBag2);
344   try {
345     osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
346   }
347   catch (e) {
348     LOG("gOSVersion - OS Version unknown: updates are not possible.");
349   }
351   if (osVersion) {
352 #ifdef XP_WIN
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',
363         [
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},
372         {wSuiteMask: WORD},
373         {wProductType: BYTE},
374         {wReserved: BYTE}
375         ]);
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',
380         [
381         {wProcessorArchitecture: WORD},
382         {wReserved: WORD},
383         {dwPageSize: DWORD},
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}
392         ]);
394     let kernel32 = false;
395     try {
396       kernel32 = ctypes.open("Kernel32");
397     } catch (e) {
398       LOG("gOSVersion - Unable to open kernel32! " + e);
399       osVersion += ".unknown (unknown)";
400     }
402     if(kernel32) {
403       try {
404         // Get Service pack info
405         try {
406           let GetVersionEx = kernel32.declare("GetVersionExW",
407                                               ctypes.default_abi,
408                                               BOOL,
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;
416           } else {
417             LOG("gOSVersion - Unknown failure in GetVersionEX (returned 0)");
418             osVersion += ".unknown";
419           }
420         } catch (e) {
421           LOG("gOSVersion - error getting service pack information. Exception: " + e);
422           osVersion += ".unknown";
423         }
425         // Get processor architecture
426         let arch = "unknown";
427         try {
428           let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
429                                                      ctypes.default_abi,
430                                                      ctypes.void_t,
431                                                      SYSTEM_INFO.ptr);
432           let sysInfo = SYSTEM_INFO();
433           // Default to unknown
434           sysInfo.wProcessorArchitecture = 0xffff;
436           GetNativeSystemInfo(sysInfo.address());
437           switch(sysInfo.wProcessorArchitecture) {
438             case 9:
439               arch = "x64";
440               break;
441             case 6:
442               arch = "IA64";
443               break;
444             case 0:
445               arch = "x86";
446               break;
447           }
448         } catch (e) {
449           LOG("gOSVersion - error getting processor architecture.  Exception: " + e);
450         } finally {
451           osVersion += " (" + arch + ")";
452         }
453       } finally {
454         kernel32.close();
455       }
456     }
457 #endif
459     try {
460       osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
461     }
462     catch (e) {
463       // Not all platforms have a secondary widget library, so an error is nothing to worry about.
464     }
465     osVersion = encodeURIComponent(osVersion);
466   }
467   return 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.
476  */
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);
487 #ifdef XP_WIN
490  * Closes a Win32 handle
492  * @param handle The handle to close
493  */
494 function closeHandle(handle) {
495   var lib = ctypes.open("kernel32.dll");
496   var CloseHandle = lib.declare("CloseHandle",
497                                 ctypes.winapi_abi,
498                                 ctypes.int32_t, /* success */
499                                 ctypes.void_t.ptr); /* handle */
500   CloseHandle(handle);
501   lib.close();
505  * Creates a mutex.
507  * @param  aName
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.
512  */
513 function createMutex(aName, aAllowExisting) {
514   if (aAllowExisting === undefined) {
515     aAllowExisting = true;
516   }
518   const INITIAL_OWN = 1;
519   const ERROR_ALREADY_EXISTS = 0xB7;
520   var lib = ctypes.open("kernel32.dll");
521   var CreateMutexW = lib.declare("CreateMutexW",
522                                  ctypes.winapi_abi,
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) {
531     closeHandle(handle);
532     handle = null;
533   }
534   lib.close();
536   if (handle && handle.isNull())
537     handle = null;
539   return handle;
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
548  */
549 function getPerInstallationMutexName(aGlobal) {
550   if (aGlobal === undefined) {
551     aGobal = true;
552   }
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);
567 #endif // XP_WIN
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
574  *    path
575  * 3) 2 applications running in 2 different user sessions from the same path
577  * @return true if this instance holds the update mutex
578  */
579 function hasUpdateMutex() {
580 #ifdef XP_WIN
581   if (!gUpdateMutexHandle) {
582     gUpdateMutexHandle = createMutex(getPerInstallationMutexName(true), false);
583   }
585   return !!gUpdateMutexHandle;
586 #else
587   return true;
588 #endif // XP_WIN
591 XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() {
592   function submitHasPermissionsTelemetryPing(val) {
593     try {
594       let h = Services.telemetry.getHistogramById("UPDATER_HAS_PERMISSIONS");
595       h.add(+val);
596     } catch(e) {
597       // Don't allow any exception to be propagated.
598       Components.utils.reportError(e);
599     }
600   }
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");
607     useService = true;
608   }
610   if (!useService) {
611     try {
612       var updateTestFile = getUpdateFile([FILE_PERMS_TEST]);
613       LOG("gCanApplyUpdates - testing write access " + updateTestFile.path);
614       testWriteAccess(updateTestFile, false);
615 #ifdef XP_MACOSX
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)
622       }
623       appDirTestFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
624       appDirTestFile.remove(false);
625 #elifdef XP_WIN
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);
633     /**
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).
639   #
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.
643      */
644       var userCanElevate = false;
646       if (parseFloat(windowsVersion) >= 6) {
647         try {
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).
655                            userCanElevate;
656           LOG("gCanApplyUpdates - on Vista, userCanElevate: " + userCanElevate);
657         }
658         catch (ex) {
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");
663         }
664       }
666       /**
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
684 #         elevated again
685        */
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);
695       }
696 #endif //XP_WIN
697     }
698     catch (e) {
699        LOG("gCanApplyUpdates - unable to apply updates. Exception: " + e);
700       // No write privileges to install directory
701       submitHasPermissionsTelemetryPing(false);
702       return false;
703     }
704   } // if (!useService)
706   LOG("gCanApplyUpdates - able to apply updates");
707   submitHasPermissionsTelemetryPing(true);
708   return true;
712  * Whether or not the application can stage an update.
714  * @return true if updates can be staged.
715  */
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);
721     return false;
722   }
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");
729     return true;
730   }
731 #endif
733   if (!hasUpdateMutex()) {
734     LOG("getCanStageUpdates - unable to apply updates because another " +
735         "instance of the application is already handling updates for this " +
736         "installation.");
737     return false;
738   }
740   /**
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.
743    *
744    * @return true if updates can be staged for this session.
745    */
746   XPCOMUtils.defineLazyGetter(this, "canStageUpdatesSession", function canStageUpdatesSession() {
747     try {
748       var updateTestFile = getInstallDirRoot();
749       updateTestFile.append(FILE_PERMS_TEST);
750       LOG("canStageUpdatesSession - testing write access " +
751           updateTestFile.path);
752       testWriteAccess(updateTestFile, true);
753 #ifndef XP_MACOSX
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
756       // replacing step.
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);
764 #endif
765     }
766     catch (e) {
767        LOG("canStageUpdatesSession - unable to stage updates. Exception: " +
768            e);
769       // No write privileges
770       return false;
771     }
773     LOG("canStageUpdatesSession - able to stage updates");
774     return true;
775   });
777   return canStageUpdatesSession;
780 XPCOMUtils.defineLazyGetter(this, "gMetroUpdatesEnabled", function aus_gMetroUpdatesEnabled() {
781 #ifdef XP_WIN
782 #ifdef MOZ_METRO
783   if (Services.metro && Services.metro.immersive) {
784     let metroUpdate = getPref("getBoolPref", PREF_APP_UPDATE_METRO_ENABLED, true);
785     if (!metroUpdate) {
786       LOG("gMetroUpdatesEnabled - unable to automatically check for metro " +
787           "updates, disabled by pref");
788       return false;
789     }
790   }
791 #endif
792 #endif
794   return true;
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.");
805     return false;
806   }
808   if (!gMetroUpdatesEnabled) {
809     return false;
810   }
812   // If we don't know the binary platform we're updating, we can't update.
813   if (!gABI) {
814     LOG("gCanCheckForUpdates - unable to check for updates, unknown ABI");
815     return false;
816   }
818   // If we don't know the OS version we're updating, we can't update.
819   if (!gOSVersion) {
820     LOG("gCanCheckForUpdates - unable to check for updates, unknown OS " +
821         "version");
822     return false;
823   }
825   LOG("gCanCheckForUpdates - able to check for updates");
826   return true;
830  * Logs a string to the error console.
831  * @param   string
832  *          The string to write to the error console.
833  */
834 function LOG(string) {
835   if (gLogEnabled) {
836     dump("*** AUS:SVC " + string + "\n");
837     Services.console.logStringMessage("AUS:SVC " + string);
838   }
842 #  Gets a preference value, handling the case where there is no default.
843 #  @param   func
844 #           The name of the preference function to call, on nsIPrefBranch
845 #  @param   preference
846 #           The name of the preference
847 #  @param   defaultValue
848 #           The default value to return in the event the preference has
849 #           no setting
850 #  @return  The value of the preference, or undefined if there was no
851 #           user or default value.
852  */
853 function getPref(func, preference, defaultValue) {
854   try {
855     return Services.prefs[func](preference);
856   }
857   catch (e) {
858   }
859   return defaultValue;
863  * Convert a string containing binary values to hex.
864  */
865 function binaryToHex(input) {
866   var result = "";
867   for (var i = 0; i < input.length; ++i) {
868     var hex = input.charCodeAt(i).toString(16);
869     if (hex.length == 1)
870       hex = "0" + hex;
871     result += hex;
872   }
873   return result;
877 #  Gets the specified directory at the specified hierarchy under the
878 #  update root directory and creates it if it doesn't exist.
879 #  @param   pathArray
880 #           An array of path components to locate beneath the directory
881 #           specified by |key|
882 #  @return  nsIFile object for the location specified.
883  */
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.
891 #  @param   pathArray
892 #           An array of path components to locate beneath the directory
893 #           specified by |key|
894 #  @return  nsIFile object for the location specified.
895  */
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.
904  */
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
915  */
916 function getInstallDirRoot() {
917   var dir = getAppBaseDir();
918 #ifdef XP_MACOSX
919   // On Mac, we store the Updated.app directory inside the bundle directory.
920   dir = dir.parent.parent;
921 #endif
922   return dir;
926  * Gets the file at the specified hierarchy under the update root directory.
927  * @param   pathArray
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
933  *          the way are.
934  */
935 function getUpdateFile(pathArray) {
936   var file = getUpdateDirCreate(pathArray.slice(0, -1));
937   file.append(pathArray[pathArray.length - 1]);
938   return file;
942  * Returns human readable status text from the updates.properties bundle
943  * based on an error code
944  * @param   code
945  *          The error code to look up human readable status text for
946  * @param   defaultCode
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
950  */
951 function getStatusTextFromCode(code, defaultCode) {
952   var reason;
953   try {
954     reason = gUpdateBundle.GetStringFromName("check_error-" + code);
955     LOG("getStatusTextFromCode - transfer error: " + reason + ", code: " +
956         code);
957   }
958   catch (e) {
959     // Use the default reason
960     reason = gUpdateBundle.GetStringFromName("check_error-" + defaultCode);
961     LOG("getStatusTextFromCode - transfer error: " + reason +
962         ", default code: " + defaultCode);
963   }
964   return reason;
968  * Record count in the health report.
969  * @param field
970  *        The field name to record
971  * @param status
972  *        Status code for errors, 0 otherwise
973  */
974 function recordInHealthReport(field, status) {
975 #ifdef MOZ_SERVICES_HEALTHREPORT
976   try {
977     LOG("recordInHealthReport - " + field + " - " + status);
979     let reporter = Cc["@mozilla.org/datareporting/service;1"]
980                       .getService().wrappedJSObject.healthReporter;
982     if (reporter) {
983       reporter.onInit().then(function recordUpdateInHealthReport() {
984         try {
985           reporter.getProvider("org.mozilla.update").recordUpdate(field, status);
986         } catch (ex) {
987           Cu.reportError(ex);
988         }
989       });
990     }
991   // If getting the heath reporter service fails, don't fail updating.
992   } catch (ex) {
993     LOG("recordInHealthReport - could not initialize health reporter");
994   }
995 #endif
999  * Get the Active Updates directory
1000  * @return The active updates directory, as a nsIFile object
1001  */
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
1010  * staged update.
1011  * @return The active updates directory inside the updated directory, as a
1012  *         nsIFile object.
1013  */
1014 function getUpdatesDirInApplyToDir() {
1015   var dir = getAppBaseDir();
1016 #ifdef XP_MACOSX
1017   dir = dir.parent.parent; // the bundle directory
1018 #endif
1019   dir.append(UPDATED_DIR);
1020 #ifdef XP_MACOSX
1021   dir.append("Contents");
1022   dir.append("MacOS");
1023 #endif
1024   dir.append(DIR_UPDATES);
1025   if (!dir.exists()) {
1026     dir.create(Ci.nsILocalFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
1027   }
1028   return dir;
1032  * Reads the update state from the update.status file in the specified
1033  * directory.
1034  * @param   dir
1035  *          The dir to look for an update.status file in
1036  * @return  The status value of the update.
1037  */
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);
1043   return status;
1047  * Writes the current update operation/state to a file in the patch
1048  * directory, indicating to the patching system that operations need
1049  * to be performed.
1050  * @param   dir
1051  *          The patch directory where the update.status file should be
1052  *          written.
1053  * @param   state
1054  *          The state value to write.
1055  */
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.
1067  * @param   dir
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
1071  *          doesn't exist.
1072  */
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);
1078   if (!link) {
1079     return null;
1080   }
1081   let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
1082   file.initWithPath(link);
1083   return file;
1087  * Creates a link file, which allows the actual patch to live in
1088  * a directory different from the update directory.
1089  * @param   dir
1090  *          The patch directory where the update.link file
1091  *          should be written.
1092  * @param   patchFile
1093  *          The fully qualified filename of the patchfile.
1094  */
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
1102     // to it.
1103     acquireSDCardMountLock();
1104   }
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.
1112  */
1113 function acquireSDCardMountLock() {
1114   let volsvc = Cc["@mozilla.org/telephony/volume-service;1"].
1115                     getService(Ci.nsIVolumeService);
1116   if (volsvc) {
1117     gSDCardMountLock = volsvc.createMountLock("sdcard");
1118   }
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
1127  *         update.
1128  */
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.
1143  */
1144 function releaseSDCardMountLock() {
1145 #ifdef MOZ_WIDGET_GONK
1146   if (gSDCardMountLock) {
1147     gSDCardMountLock.unlock();
1148     gSDCardMountLock = null;
1149   }
1150 #endif
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.
1159  */
1160 function shouldUseService() {
1161 #ifdef MOZ_MAINTENANCE_SERVICE
1162   return getPref("getBoolPref",
1163                  PREF_APP_UPDATE_SERVICE_ENABLED, false);
1164 #else
1165   return false;
1166 #endif
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.
1174  */
1175 function isServiceInstalled() {
1176 #ifdef XP_WIN
1177   let installed = 0;
1178   try {
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");
1185     wrk.close();
1186   } catch(e) {
1187   }
1188   installed = installed == 1;  // convert to bool
1189   LOG("isServiceInstalled = " + installed);
1190   return installed;
1191 #else
1192   return false;
1193 #endif
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.
1204 #  @param   dir
1205 #           The patch directory where the update.version file should be
1206 #           written.
1207 #  @param   version
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.
1210  */
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.
1219  */
1220 function cleanUpMozUpdaterDirs() {
1221   try {
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");
1235       let i = 0;
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") {
1240           file.remove(true);
1241           i++;
1242         }
1243       }
1244       // If you enumerate the whole temp directory and the count of deleted
1245       // items is less than 10, then delete MozUpdate-1.
1246       if (i < 10) {
1247         mozUpdaterDir1.remove(true);
1248       }
1249     }
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);
1258     }
1259   } catch (e) {
1260     LOG("cleanUpMozUpdaterDirs - Exception: " + e);
1261   }
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
1270  *        later on.
1271  */
1272 function cleanUpUpdatesDir(aBackgroundUpdate) {
1273   // Bail out if we don't have appropriate permissions
1274   try {
1275     var updateDir = getUpdatesDir();
1276   } catch (e) {
1277     return;
1278   }
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()) {
1284     let dir;
1285     if (aBackgroundUpdate && getUpdateDirNoCreate([]).equals(getAppBaseDir())) {
1286       dir = getUpdatesDirInApplyToDir();
1287     } else {
1288       dir = updateDir.parent;
1289     }
1290     let logFile = dir.clone();
1291     logFile.append(FILE_LAST_LOG);
1292     if (logFile.exists()) {
1293       try {
1294         logFile.moveTo(dir, FILE_BACKUP_LOG);
1295       } catch (e) {
1296         LOG("cleanUpUpdatesDir - failed to rename file " + logFile.path +
1297             " to " + FILE_BACKUP_LOG);
1298       }
1299     }
1301     try {
1302       file.moveTo(dir, FILE_LAST_LOG);
1303     } catch (e) {
1304       LOG("cleanUpUpdatesDir - failed to rename file " + file.path +
1305           " to " + FILE_LAST_LOG);
1306     }
1307   }
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);
1318         }
1319       }
1320 #endif
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.
1325       try {
1326         f.remove(true);
1327       } catch (e) {
1328         LOG("cleanUpUpdatesDir - failed to remove file " + f.path);
1329       }
1330     }
1331   }
1332   releaseSDCardMountLock();
1336  * Clean up updates list and the updates directory.
1337  */
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;
1343   um.saveUpdates();
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.
1354  */
1355 function getLocale() {
1356   if (gLocale)
1357     return gLocale;
1359   for (let res of ['app', 'gre']) {
1360     var channel = Services.io.newChannel("resource://" + res + "/" + FILE_UPDATE_LOCALE, null, null);
1361     try {
1362       var inputStream = channel.open();
1363       gLocale = readStringFromInputStream(inputStream);
1364     } catch(e) {}
1365     if (gLocale)
1366       break;
1367   }
1369   if (!gLocale)
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);
1376   return gLocale;
1379 /* Get the distribution pref values, from defaults only */
1380 function getDistributionPrefValue(aPrefName) {
1381   var prefValue = "default";
1383   try {
1384     prefValue = Services.prefs.getDefaultBranch(null).getCharPref(aPrefName);
1385   } catch (e) {
1386     // use default when pref not found
1387   }
1389   return prefValue;
1393  * An enumeration of items in a JS array.
1394  * @constructor
1395  */
1396 function ArrayEnumerator(aItems) {
1397   this._index = 0;
1398   if (aItems) {
1399     for (var i = 0; i < aItems.length; ++i) {
1400       if (!aItems[i])
1401         aItems.splice(i, 1);
1402     }
1403   }
1404   this._contents = aItems;
1407 ArrayEnumerator.prototype = {
1408   _index: 0,
1409   _contents: [],
1411   hasMoreElements: function ArrayEnumerator_hasMoreElements() {
1412     return this._index < this._contents.length;
1413   },
1415   getNext: function ArrayEnumerator_getNext() {
1416     return this._contents[this._index++];
1417   }
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.
1423  */
1424 function writeStringToFile(file, text) {
1425   var fos = FileUtils.openSafeFileOutputStream(file)
1426   text += "\n";
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());
1436   sis.close();
1437   if (text[text.length - 1] == "\n")
1438     text = text.slice(0, -1);
1439   return text;
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.
1445  */
1446 function readStringFromFile(file) {
1447   if (!file.exists()) {
1448     LOG("readStringFromFile - file doesn't exist: " + file.path);
1449     return null;
1450   }
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
1465     // we want.
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();
1473     return true;
1474   }
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);
1487     return true;
1488   }
1490   if (update.errorCode == ELEVATION_CANCELED) {
1491     writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
1492     return true;
1493   }
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);
1519     } else {
1520       failCount++;
1521       Services.prefs.setIntPref(PREF_APP_UPDATE_SERVICE_ERRORS,
1522                                 failCount);
1523     }
1525     writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
1526     try {
1527       Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE").
1528         add(update.errorCode);
1529     }
1530     catch (e) {
1531       Cu.reportError(e);
1532     }
1533     return true;
1534   }
1536   try {
1537     Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE").add(0);
1538   }
1539   catch (e) {
1540     Cu.reportError(e);
1541   }
1542   return false;
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.
1550  */
1551 function handleFallbackToCompleteUpdate(update, postStaging) {
1552   cleanupActiveUpdate();
1554   update.statusText = gUpdateBundle.GetStringFromName("patchApplyFailure");
1555   var oldType = update.selectedPatch ? update.selectedPatch.type
1556                                      : "complete";
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();
1567   }
1568   else {
1569     LOG("handleFallbackToCompleteUpdate - install of complete or " +
1570         "only one patch offered failed.");
1571   }
1572   update.QueryInterface(Ci.nsIWritablePropertyBag);
1573   update.setProperty("patchingFailed", oldType);
1577  * Update Patch
1578  * @param   patch
1579  *          A <patch> element to initialize this object with
1580  * @throws if patch has a size of 0
1581  * @constructor
1582  */
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) {
1589     case "selected":
1590       this.selected = attr.value == "true";
1591       break;
1592     case "size":
1593       if (0 == parseInt(attr.value)) {
1594         LOG("UpdatePatch:init - 0-sized patch!");
1595         throw Cr.NS_ERROR_ILLEGAL_VALUE;
1596       }
1597       // fall through
1598     default:
1599       this[attr.name] = attr.value;
1600       break;
1601     };
1602   }
1604 UpdatePatch.prototype = {
1605   /**
1606    * See nsIUpdateService.idl
1607    */
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
1613     if (this.finalURL)
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);
1624     }
1626     return patch;
1627   },
1629   /**
1630    * A hash of custom properties
1631    */
1632   _properties: null,
1634   /**
1635    * See nsIWritablePropertyBag.idl
1636    */
1637   setProperty: function UpdatePatch_setProperty(name, value) {
1638     this._properties[name] = { data: value, present: true };
1639   },
1641   /**
1642    * See nsIWritablePropertyBag.idl
1643    */
1644   deleteProperty: function UpdatePatch_deleteProperty(name) {
1645     if (name in this._properties)
1646       this._properties[name].present = false;
1647     else
1648       throw Cr.NS_ERROR_FAILURE;
1649   },
1651   /**
1652    * See nsIPropertyBag.idl
1653    */
1654   get enumerator() {
1655     var properties = [];
1656     for (var p in this._properties)
1657       properties.push(this._properties[p].data);
1658     return new ArrayEnumerator(properties);
1659   },
1661   /**
1662    * See nsIPropertyBag.idl
1663    */
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;
1669   },
1671   /**
1672    * Returns whether or not the update.status file for this patch exists at the
1673    * appropriate location.
1674    */
1675   get statusFileExists() {
1676     var statusFile = getUpdatesDir();
1677     statusFile.append(FILE_UPDATE_STATUS);
1678     return statusFile.exists();
1679   },
1681   /**
1682    * See nsIUpdateService.idl
1683    */
1684   get state() {
1685     if (this._properties.state)
1686       return this._properties.state;
1687     return STATE_NONE;
1688   },
1689   set state(val) {
1690     this._properties.state = val;
1691   },
1693   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePatch,
1694                                          Ci.nsIPropertyBag,
1695                                          Ci.nsIWritablePropertyBag])
1699  * Update
1700  * Implements nsIUpdate
1701  * @param   update
1702  *          An <update> element to initialize this object with
1703  * @throws if the update contains no patches
1704  * @constructor
1705  */
1706 function Update(update) {
1707   this._properties = {};
1708   this._patches = [];
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
1719   if (!update)
1720     return;
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")
1727       continue;
1729     patchElement.QueryInterface(Ci.nsIDOMElement);
1730     try {
1731       var patch = new UpdatePatch(patchElement);
1732     } catch (e) {
1733       continue;
1734     }
1735     this._patches.push(patch);
1736   }
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;
1749       }
1750     }
1751   }
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")
1757       continue;
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;
1765     }
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")
1779     {
1780       if(!isNaN(attr.value))
1781         this.promptWaitTime = parseInt(attr.value);
1782     }
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;
1790     }
1791     else {
1792       this[attr.name] = attr.value;
1794       switch (attr.name) {
1795       case "appVersion":
1796       case "billboardURL":
1797       case "buildID":
1798       case "channel":
1799       case "displayVersion":
1800       case "licenseURL":
1801       case "name":
1802       case "platformVersion":
1803       case "previousAppVersion":
1804       case "serviceURL":
1805       case "statusText":
1806       case "type":
1807         break;
1808       default:
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);
1813         break;
1814       };
1815     }
1816   }
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>"
1825   var name = "";
1826   if (update.hasAttribute("name"))
1827     name = update.getAttribute("name");
1828   else {
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);
1833   }
1834   this.name = name;
1836 Update.prototype = {
1837   /**
1838    * See nsIUpdateService.idl
1839    */
1840   get patchCount() {
1841     return this._patches.length;
1842   },
1844   /**
1845    * See nsIUpdateService.idl
1846    */
1847   getPatchAt: function Update_getPatchAt(index) {
1848     return this._patches[index];
1849   },
1851   /**
1852    * See nsIUpdateService.idl
1853    *
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.
1858    */
1859   _state: "",
1860   set state(state) {
1861     if (this.selectedPatch)
1862       this.selectedPatch.state = state;
1863     this._state = state;
1864     return state;
1865   },
1866   get state() {
1867     if (this.selectedPatch)
1868       return this.selectedPatch.state;
1869     return this._state;
1870   },
1872   /**
1873    * See nsIUpdateService.idl
1874    */
1875   errorCode: 0,
1877   /**
1878    * See nsIUpdateService.idl
1879    */
1880   get selectedPatch() {
1881     for (var i = 0; i < this.patchCount; ++i) {
1882       if (this._patches[i].selected)
1883         return this._patches[i];
1884     }
1885     return null;
1886   },
1888   /**
1889    * See nsIUpdateService.idl
1890    */
1891   get detailsURL() {
1892     if (!this._detailsURL) {
1893       try {
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);
1897       }
1898       catch (e) {
1899       }
1900     }
1901     return this._detailsURL || "";
1902   },
1904   /**
1905    * See nsIUpdateService.idl
1906    */
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);
1947     }
1949     for (var i = 0; i < this.patchCount; ++i)
1950       update.appendChild(this.getPatchAt(i).serialize(updates));
1952     return update;
1953   },
1955   /**
1956    * A hash of custom properties
1957    */
1958   _properties: null,
1960   /**
1961    * See nsIWritablePropertyBag.idl
1962    */
1963   setProperty: function Update_setProperty(name, value) {
1964     this._properties[name] = { data: value, present: true };
1965   },
1967   /**
1968    * See nsIWritablePropertyBag.idl
1969    */
1970   deleteProperty: function Update_deleteProperty(name) {
1971     if (name in this._properties)
1972       this._properties[name].present = false;
1973     else
1974       throw Cr.NS_ERROR_FAILURE;
1975   },
1977   /**
1978    * See nsIPropertyBag.idl
1979    */
1980   get enumerator() {
1981     var properties = [];
1982     for (var p in this._properties)
1983       properties.push(this._properties[p].data);
1984     return new ArrayEnumerator(properties);
1985   },
1987   /**
1988    * See nsIPropertyBag.idl
1989    */
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;
1994   },
1996   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdate,
1997                                          Ci.nsIPropertyBag,
1998                                          Ci.nsIWritablePropertyBag])
2001 const UpdateServiceFactory = {
2002   _instance: null,
2003   createInstance: function (outer, iid) {
2004     if (outer != null)
2005       throw Cr.NS_ERROR_NO_AGGREGATION;
2006     return this._instance == null ? this._instance = new UpdateService() :
2007                                     this._instance;
2008   }
2012  * UpdateService
2013  * A Service for managing the discovery and installation of software updates.
2014  * @constructor
2015  */
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);
2025 #endif
2028 UpdateService.prototype = {
2029   /**
2030    * The downloader we are using to download updates. There is only ever one of
2031    * these.
2032    */
2033   _downloader: null,
2035   /**
2036    * Incompatible add-on count.
2037    */
2038   _incompatAddonsCount: 0,
2040   /**
2041    * Whether or not the service registered the "online" observer.
2042    */
2043   _registeredOnlineObserver: false,
2045   /**
2046    * The current number of consecutive socket errors
2047    */
2048   _consecutiveSocketErrors: 0,
2050   /**
2051    * A timer used to retry socket errors
2052    */
2053   _retryTimer: null,
2055   /**
2056    * Whether or not a background update check was initiated by the
2057    * application update timer notification.
2058    */
2059   _isNotify: true,
2061   /**
2062    * Handle Observer Service notifications
2063    * @param   subject
2064    *          The subject of the notification
2065    * @param   topic
2066    *          The notification name
2067    * @param   data
2068    *          Additional data
2069    */
2070   observe: function AUS_observe(subject, topic, data) {
2071     switch (topic) {
2072     case "post-update-processing":
2073       // Clean up any extant updates
2074       this._postUpdateProcessing();
2075       break;
2076     case "network:offline-status-changed":
2077       this._offlineStatusChanged(data);
2078       break;
2079     case "nsPref:changed":
2080       if (data == PREF_APP_UPDATE_LOG) {
2081         gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
2082       }
2083       break;
2084 #ifdef MOZ_WIDGET_GONK
2085     case "profile-change-net-teardown": // fall thru
2086 #endif
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();
2093       }
2095       this.pauseDownload();
2096       // Prevent leaking the downloader (bug 454964)
2097       this._downloader = null;
2098       break;
2099     }
2100   },
2102   /**
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
2108    *    update
2109    */
2111   /**
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.
2116    */
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
2124     // is applied).
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
2127     // in those cases.
2128     if (!this.canCheckForUpdates && gMetroUpdatesEnabled) {
2129       LOG("UpdateService:_postUpdateProcessing - unable to check for " +
2130           "updates... returning early");
2131       return;
2132     }
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();
2140       return;
2141     }
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();
2149       return;
2150     }
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.
2158     //
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");
2168       return;
2169     }
2170 #endif
2172     var update = um.activeUpdate;
2174     if (status == STATE_DOWNLOADING) {
2175       LOG("UpdateService:_postUpdateProcessing - patch found in downloading " +
2176           "state");
2177       if (update && update.state != STATE_SUCCEEDED) {
2178         // Resume download
2179         var status = this.downloadUpdate(update, true);
2180         if (status == STATE_NONE)
2181           cleanupActiveUpdate();
2182       }
2183       return;
2184     }
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.
2200       if (update &&
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;
2205         um.saveUpdates();
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();
2210       }
2211       return;
2212     }
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");
2218       return;
2219     }
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;
2231           break;
2232         case Ci.nsIRecoveryService.FOTA_UPDATE_FAIL:
2233           status = STATE_FAILED + ": " + FOTA_GENERAL_ERROR;
2234           break;
2235         case Ci.nsIRecoveryService.FOTA_UPDATE_UNKNOWN:
2236         default:
2237           status = STATE_FAILED + ": " + FOTA_UNKNOWN_ERROR;
2238           break;
2239       }
2240     }
2241 #endif
2243     if (!update) {
2244       if (status != STATE_SUCCEEDED) {
2245         LOG("UpdateService:_postUpdateProcessing - previous patch failed " +
2246             "and no patch available");
2247         cleanupActiveUpdate();
2248         return;
2249       }
2250       update = new Update(null);
2251     }
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();
2269     }
2270     else {
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])) {
2283           return;
2284         }
2285       }
2287       // Something went wrong with the patch application process.
2288       handleFallbackToCompleteUpdate(update, false);
2290       prompter.showUpdateError(update);
2291     }
2293     // Now trash the MozUpdater folders which staged/bgupdates uses.
2294     cleanUpMozUpdaterDirs();
2295   },
2297   /**
2298    * Submit a telemetry ping with the boolean value of a pref for a histogram
2299    *
2300    * @param  pref
2301    *         The preference to report
2302    * @param  histogram
2303    *         The histogram ID to report to
2304    */
2305   _sendBoolPrefTelemetryPing: function AUS__boolTelemetryPing(pref, histogram) {
2306     try {
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);
2312     } catch(e) {
2313       // Don't allow any exception to be propagated.
2314       Cu.reportError(e);
2315     }
2316   },
2318 #ifdef XP_WIN
2319   /**
2320    * Submit a telemetry ping with a boolean value which indicates if the service
2321    * is installed.
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.
2324    */
2325   _sendServiceInstalledTelemetryPing: function AUS__svcInstallTelemetryPing() {
2326     let installed = isServiceInstalled(); // Is the service installed?
2327     let attempted = 0;
2328     try {
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");
2336       wrk.close();
2337     } catch(e) {
2338     }
2339     try {
2340       let h = Services.telemetry.getHistogramById("UPDATER_SERVICE_INSTALLED");
2341       h.add(Number(installed));
2342     } catch(e) {
2343       // Don't allow any exception to be propagated.
2344       Cu.reportError(e);
2345     }
2346     try {
2347       let h = Services.telemetry.getHistogramById("UPDATER_SERVICE_MANUALLY_UNINSTALLED");
2348       h.add(!installed && attempted ? 1 : 0);
2349     } catch(e) {
2350       // Don't allow any exception to be propagated.
2351       Cu.reportError(e);
2352     }
2353   },
2354 #endif
2356   /**
2357    * Submit a telemetry ping with the int value of a pref for a histogram
2358    *
2359    * @param  pref
2360    *         The preference to report
2361    * @param histogram
2362    *         The histogram ID to report to
2363    */
2364   _sendIntPrefTelemetryPing: function AUS__intTelemetryPing(pref, histogram) {
2365     try {
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);
2371     } catch(e) {
2372       // Don't allow any exception to be propagated.
2373       Cu.reportError(e);
2374     }
2375   },
2378   /**
2379    * Submit the results of applying the update via telemetry.
2380    *
2381    * @param  status
2382    *         The status of the update as read from the update.status file
2383    */
2384   _sendStatusCodeTelemetryPing: function AUS__statusTelemetryPing(status) {
2385     try {
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
2390         return;
2391       }
2392       let result = 0; // 0 means success
2393       if (parts.length > 1) {
2394         result = parseInt(parts[1]) || INVALID_UPDATER_STATUS_CODE;
2395       }
2396       Services.telemetry.getHistogramById("UPDATER_STATUS_CODES").add(result);
2397     } catch(e) {
2398       // Don't allow any exception to be propagated.
2399       Cu.reportError(e);
2400     }
2401   },
2403   /**
2404    * Submit the interval in days since the last notification for this background
2405    * update check.
2406    */
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) {
2415           try {
2416             Services.telemetry.
2417               getHistogramById("UPDATER_INVALID_LASTUPDATETIME_" + idSuffix).
2418               add(1);
2419           } catch(e) {
2420             Cu.reportError(e);
2421           }
2422         }
2423         else {
2424           let intervalDays = (currentTimeSeconds - lastUpdateTimeSeconds) /
2425                              (60 * 60 * 24);
2426           try {
2427             Services.telemetry.
2428               getHistogramById("UPDATER_INVALID_LASTUPDATETIME_" + idSuffix).
2429               add(0);
2430             Services.telemetry.
2431               getHistogramById("UPDATER_LAST_NOTIFY_INTERVAL_DAYS_" + idSuffix).
2432               add(intervalDays);
2433           } catch(e) {
2434             Cu.reportError(e);
2435           }
2436         }
2437       }
2438     }
2439   },
2441   /**
2442    * Submit the result for the background update check.
2443    *
2444    * @param  code
2445    *         An integer value as defined by the PING_BGUC_* constants.
2446    */
2447   _backgroundUpdateCheckCodePing: function AUS__backgroundUpdateCheckCodePing(code) {
2448     try {
2449       let idSuffix = this._isNotify ? "NOTIFY" : "EXTERNAL";
2450       Services.telemetry.
2451         getHistogramById("UPDATER_BACKGROUND_CHECK_CODE_" + idSuffix).add(code);
2452     }
2453     catch (e) {
2454       Cu.reportError(e);
2455     }
2456   },
2458   /**
2459    * Register an observer when the network comes online, so we can short-circuit
2460    * the app.update.interval when there isn't connectivity
2461    */
2462   _registerOnlineObserver: function AUS__registerOnlineObserver() {
2463     if (this._registeredOnlineObserver) {
2464       LOG("UpdateService:_registerOnlineObserver - observer already registered");
2465       return;
2466     }
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;
2473   },
2475   /**
2476    * Called from the network:offline-status-changed observer.
2477    */
2478   _offlineStatusChanged: function AUS__offlineStatusChanged(status) {
2479     if (status !== "online") {
2480       return;
2481     }
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();
2491   },
2493   onCheckComplete: function AUS_onCheckComplete(request, updates, updateCount) {
2494     this._selectAndInstallUpdate(updates);
2495   },
2497   onError: function AUS_onError(request, update) {
2498     LOG("UpdateService:onError - error during background update. error code: " +
2499         update.errorCode + ", status text: " + update.statusText);
2501     var maxErrors;
2502     var errCount;
2503     if (update.errorCode == NETWORK_ERROR_OFFLINE) {
2504       // Register an online observer to try again
2505       this._registerOnlineObserver();
2506       this._backgroundUpdateCheckCodePing(PING_BGUC_OFFLINE);
2507       return;
2508     }
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);
2513       errCount++;
2514       Services.prefs.setIntPref(PREF_APP_UPDATE_CERT_ERRORS, errCount);
2515       maxErrors = getPref("getIntPref", PREF_APP_UPDATE_CERT_MAXERRORS, 5);
2516     }
2517     else {
2518       update.errorCode = BACKGROUNDCHECK_MULTIPLE_FAILURES;
2519       errCount = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDERRORS, 0);
2520       errCount++;
2521       Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, errCount);
2522       maxErrors = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS,
2523                           10);
2524     }
2526     var pingCode;
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;
2535           break;
2536         case CERT_ATTR_CHECK_FAILED_HAS_UPDATE:
2537           pingCode = PING_BGUC_CERT_ATTR_WITH_UPDATE_NOTIFY;
2538           break;
2539         default:
2540           pingCode = PING_BGUC_GENERAL_ERROR_NOTIFY;
2541       }
2542     }
2543     else {
2544       switch (update.errorCode) {
2545         case CERT_ATTR_CHECK_FAILED_NO_UPDATE:
2546           pingCode = PING_BGUC_CERT_ATTR_NO_UPDATE_SILENT;
2547           break;
2548         case CERT_ATTR_CHECK_FAILED_HAS_UPDATE:
2549           pingCode = PING_BGUC_CERT_ATTR_WITH_UPDATE_SILENT;
2550           break;
2551         default:
2552           pingCode = PING_BGUC_GENERAL_ERROR_SILENT;
2553       }
2554     }
2555     this._backgroundUpdateCheckCodePing(pingCode);
2556   },
2558   /**
2559    * Called when a connection should be resumed
2560    */
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();
2576       }
2577       return;
2578     }
2580     this.backgroundChecker.checkForUpdates(this, false);
2581   },
2583   /**
2584    * Notified when a timer fires
2585    * @param   timer
2586    *          The timer that fired
2587    */
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");
2599 #ifdef XP_WIN
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();
2605 #endif
2607     this._checkForBackgroundUpdates(true);
2608   },
2610   /**
2611    * See nsIUpdateService.idl
2612    */
2613   checkForBackgroundUpdates: function AUS_checkForBackgroundUpdates() {
2614     this._checkForBackgroundUpdates(false);
2615   },
2617   /**
2618    * Checks for updates in the background.
2619    * @param   isNotify
2620    *          Whether or not a background update check was initiated by the
2621    *          application update timer notification.
2622    */
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
2627     // separately.
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);
2633       return;
2634     }
2636     if (this._downloader && this._downloader.patchIsStaged) {
2637       this._backgroundUpdateCheckCodePing(PING_BGUC_IS_STAGED);
2638       return;
2639     }
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.
2644     try {
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);
2650           }
2651           else {
2652             this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_CUSTOM_URL);
2653           }
2654         }
2655         else {
2656           this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_OVERRIDE_URL);
2657         }
2658       }
2659       else if (!gMetroUpdatesEnabled) {
2660         this._backgroundUpdateCheckCodePing(PING_BGUC_METRO_DISABLED);
2661       }
2662       else if (!getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true)) {
2663         this._backgroundUpdateCheckCodePing(PING_BGUC_PREF_DISABLED);
2664       }
2665       else if (!(gCanCheckForUpdates && hasUpdateMutex())) {
2666         this._backgroundUpdateCheckCodePing(PING_BGUC_UNABLE_TO_CHECK);
2667       }
2668       else if (!this.backgroundChecker._enabled) {
2669         this._backgroundUpdateCheckCodePing(PING_BGUC_DISABLED_FOR_SESSION);
2670       }
2671     }
2672     catch (e) {
2673       Cu.reportError(e);
2674     }
2676     this.backgroundChecker.checkForUpdates(this, false);
2677   },
2679   /**
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
2682    * be offered.
2683    * @param   updates
2684    *          An array of available nsIUpdate items
2685    * @return  The nsIUpdate to offer.
2686    */
2687   selectUpdate: function AUS_selectUpdate(updates) {
2688     if (updates.length == 0) {
2689       this._backgroundUpdateCheckCodePing(PING_BGUC_NO_UPDATE_FOUND);
2690       return null;
2691     }
2693     // The ping for unsupported is sent after the call to showPrompt.
2694     if (updates.length == 1 && updates[0].unsupported)
2695       return updates[0];
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;
2713         return;
2714       }
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;
2725         return;
2726       }
2728       switch (aUpdate.type) {
2729         case "major":
2730           if (!majorUpdate)
2731             majorUpdate = aUpdate;
2732           else if (vc.compare(majorUpdate.appVersion, aUpdate.appVersion) <= 0)
2733             majorUpdate = aUpdate;
2734           break;
2735         case "minor":
2736           if (!minorUpdate)
2737             minorUpdate = aUpdate;
2738           else if (vc.compare(minorUpdate.appVersion, aUpdate.appVersion) <= 0)
2739             minorUpdate = aUpdate;
2740           break;
2741         default:
2742           LOG("UpdateService:selectUpdate - skipping unknown update type: " +
2743               aUpdate.type);
2744           lastPingCode = PING_BGUC_UPDATE_INVALID_TYPE;
2745           break;
2746       }
2747     });
2749     var update = minorUpdate || majorUpdate;
2750     if (!update)
2751       this._backgroundUpdateCheckCodePing(lastPingCode);
2753     return update;
2754   },
2756   /**
2757    * Reference to the currently selected update for when add-on compatibility
2758    * is checked.
2759    */
2760   _update: null,
2762   /**
2763    * Determine which of the specified updates should be installed and begin the
2764    * download/installation process or notify the user about the update.
2765    * @param   updates
2766    *          An array of available updates
2767    */
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);
2778 #endif
2779       this._backgroundUpdateCheckCodePing(PING_BGUC_HAS_ACTIVEUPDATE);
2780       return;
2781     }
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");
2788       return;
2789     }
2791     if (!gMetroUpdatesEnabled) {
2792       this._backgroundUpdateCheckCodePing(PING_BGUC_METRO_DISABLED);
2793       return;
2794     }
2796     var update = this.selectUpdate(updates, updates.length);
2797     if (!update) {
2798       return;
2799     }
2801     if (update.unsupported) {
2802       LOG("UpdateService:_selectAndInstallUpdate - update not supported for " +
2803           "this system");
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);
2808       }
2809       this._backgroundUpdateCheckCodePing(PING_BGUC_UNSUPPORTED);
2810       return;
2811     }
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);
2818       return;
2819     }
2821     /**
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
2826 #      Notes:
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
2836 #      1      Yes                    Notify
2837 #      1      No                     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
2846      */
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);
2853         return;
2854       }
2855     }
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);
2863         return;
2864       }
2865     }
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);
2875       return;
2876     }
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();
2883     }
2884     else {
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);
2892     }
2893   },
2895   _showPrompt: function AUS__showPrompt(update) {
2896     var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
2897                    createInstance(Ci.nsIUpdatePrompt);
2898     prompter.showUpdateAvailable(update);
2899   },
2901   _checkAddonCompatibility: function AUS__checkAddonCompatibility() {
2902     try {
2903       var hotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID);
2904     }
2905     catch (e) { }
2907     // Get all the installed add-ons
2908     var self = this;
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!";
2917           if (addon.id)
2918             errMsg += " Add-on ID: " + addon.id;
2919           Cu.reportError(errMsg);
2920           return;
2921         }
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.
2933         try {
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);
2941         }
2942         catch (e) {
2943           Cu.reportError(e);
2944         }
2945       });
2947       if (self._incompatibleAddons.length > 0) {
2948       /**
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
2959 #          prompt dialogs.
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.
2967        */
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);
2975         }, self);
2976       }
2977       else {
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);
2985       }
2986     });
2987   },
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);
2998       }
2999     }
3000   },
3002   onUpdateAvailable: function(addon, install) {
3003     if (getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE, 0) == 1)
3004       return;
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))
3014       return;
3016     // Compatibility or new version updates mean the same thing here.
3017     this.onCompatibilityUpdateAvailable(addon);
3018   },
3020   onUpdateFinished: function(addon) {
3021     if (--this._updateCheckCount > 0)
3022       return;
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);
3030     }
3031     else {
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);
3038     }
3039     this._update = null;
3040   },
3042   /**
3043    * The Checker used for background update checks.
3044    */
3045   _backgroundChecker: null,
3047   /**
3048    * See nsIUpdateService.idl
3049    */
3050   get backgroundChecker() {
3051     if (!this._backgroundChecker)
3052       this._backgroundChecker = new Checker();
3053     return this._backgroundChecker;
3054   },
3056   /**
3057    * See nsIUpdateService.idl
3058    */
3059   get canCheckForUpdates() {
3060     return gCanCheckForUpdates && hasUpdateMutex();
3061   },
3063   /**
3064    * See nsIUpdateService.idl
3065    */
3066   get canApplyUpdates() {
3067     return gCanApplyUpdates && hasUpdateMutex();
3068   },
3070   /**
3071    * See nsIUpdateService.idl
3072    */
3073   get canStageUpdates() {
3074     return getCanStageUpdates();
3075   },
3077   /**
3078    * See nsIUpdateService.idl
3079    */
3080   get isOtherInstanceHandlingUpdates() {
3081     return !hasUpdateMutex();
3082   },
3085   /**
3086    * See nsIUpdateService.idl
3087    */
3088   addDownloadListener: function AUS_addDownloadListener(listener) {
3089     if (!this._downloader) {
3090       LOG("UpdateService:addDownloadListener - no downloader!");
3091       return;
3092     }
3093     this._downloader.addDownloadListener(listener);
3094   },
3096   /**
3097    * See nsIUpdateService.idl
3098    */
3099   removeDownloadListener: function AUS_removeDownloadListener(listener) {
3100     if (!this._downloader) {
3101       LOG("UpdateService:removeDownloadListener - no downloader!");
3102       return;
3103     }
3104     this._downloader.removeDownloadListener(listener);
3105   },
3107   /**
3108    * See nsIUpdateService.idl
3109    */
3110   downloadUpdate: function AUS_downloadUpdate(update, background) {
3111     if (!update)
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
3117     // build ID.
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();
3129       return STATE_NONE;
3130     }
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());
3139       }
3140       this._downloader.cancel();
3141     }
3142 #ifdef MOZ_WIDGET_GONK
3143     var um = Cc["@mozilla.org/updates/update-manager;1"].
3144              getService(Ci.nsIUpdateManager);
3145     var activeUpdate = um.activeUpdate;
3146     if (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();
3155     }
3156 #endif
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);
3161   },
3163   /**
3164    * See nsIUpdateService.idl
3165    */
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();
3175     }
3176   },
3178   /**
3179    * See nsIUpdateService.idl
3180    */
3181   getUpdatesDirectory: getUpdatesDir,
3183   /**
3184    * See nsIUpdateService.idl
3185    */
3186   get isDownloading() {
3187     return this._downloader && this._downloader.isBusy;
3188   },
3190   /**
3191    * See nsIUpdateService.idl
3192    */
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;
3197     }
3199     let osApplyToDir;
3200     try {
3201       aUpdate.QueryInterface(Ci.nsIWritablePropertyBag);
3202       osApplyToDir = aUpdate.getProperty("osApplyToDir");
3203     } catch (e) {}
3205     if (!osApplyToDir) {
3206       LOG("UpdateService:applyOsUpdate - Error: osApplyToDir is not defined" +
3207           "in the nsIUpdate!");
3208       handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
3209       return;
3210     }
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 " +
3216           updateFile.path);
3217       handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
3218       return;
3219     }
3221     writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED_OS);
3222     LOG("UpdateService:applyOsUpdate - Rebooting into recovery to apply " +
3223         "FOTA update: " + updateFile.path);
3224     try {
3225       let recoveryService = Cc["@mozilla.org/recovery-service;1"]
3226                             .getService(Ci.nsIRecoveryService);
3227       recoveryService.installFotaUpdate(updateFile.path);
3228     } catch (e) {
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);
3233     }
3234   },
3236   classID: UPDATESERVICE_CID,
3237   classInfo: XPCOMUtils.generateCI({classID: UPDATESERVICE_CID,
3238                                     contractID: UPDATESERVICE_CONTRACTID,
3239                                     interfaces: [Ci.nsIApplicationUpdateService,
3240                                                  Ci.nsITimerCallback,
3241                                                  Ci.nsIObserver],
3242                                     flags: Ci.nsIClassInfo.SINGLETON}),
3244   _xpcom_factory: UpdateServiceFactory,
3245   QueryInterface: XPCOMUtils.generateQI([Ci.nsIApplicationUpdateService,
3246                                          Ci.nsIUpdateCheckListener,
3247                                          Ci.nsITimerCallback,
3248                                          Ci.nsIObserver])
3252  * A service to manage active and past updates.
3253  * @constructor
3254  */
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]));
3267     }
3268     else
3269       this._activeUpdate = updates[0];
3270   }
3272 UpdateManager.prototype = {
3273   /**
3274    * All previously downloaded and installed updates, as an array of nsIUpdate
3275    * objects.
3276    */
3277   _updates: null,
3279   /**
3280    * The current actively downloading/installing update, as a nsIUpdate object.
3281    */
3282   _activeUpdate: null,
3284   /**
3285    * Handle Observer Service notifications
3286    * @param   subject
3287    *          The subject of the notification
3288    * @param   topic
3289    *          The notification name
3290    * @param   data
3291    *          Additional data
3292    */
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];
3303     }
3304   },
3306   /**
3307    * Loads an updates.xml formatted file into an array of nsIUpdate items.
3308    * @param   file
3309    *          A nsIFile for the updates.xml file
3310    * @return  The array of nsIUpdate items held in the file.
3311    */
3312   _loadXMLFileIntoArray: function UM__loadXMLFileIntoArray(file) {
3313     if (!file.exists()) {
3314       LOG("UpdateManager:_loadXMLFileIntoArray: XML file does not exist");
3315       return [];
3316     }
3318     var result = [];
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);
3322     try {
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")
3334           continue;
3336         updateElement.QueryInterface(Ci.nsIDOMElement);
3337         try {
3338           var update = new Update(updateElement);
3339         } catch (e) {
3340           LOG("UpdateManager:_loadXMLFileIntoArray - invalid update");
3341           continue;
3342         }
3343         result.push(update);
3344       }
3345     }
3346     catch (e) {
3347       LOG("UpdateManager:_loadXMLFileIntoArray - error constructing update " +
3348           "list. Exception: " + e);
3349     }
3350     fileStream.close();
3351     return result;
3352   },
3354   /**
3355    * Load the update manager, initializing state from state files.
3356    */
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];
3365     }
3366   },
3368   /**
3369    * See nsIUpdateService.idl
3370    */
3371   getUpdateAt: function UM_getUpdateAt(index) {
3372     this._ensureUpdates();
3373     return this._updates[index];
3374   },
3376   /**
3377    * See nsIUpdateService.idl
3378    */
3379   get updateCount() {
3380     this._ensureUpdates();
3381     return this._updates.length;
3382   },
3384   /**
3385    * See nsIUpdateService.idl
3386    */
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;
3400         this.saveUpdates();
3402         // Destroy the updates directory, since we're done with it.
3403         cleanUpUpdatesDir();
3404       }
3405     }
3406     return this._activeUpdate;
3407   },
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.
3414       this.saveUpdates();
3415     }
3416     else
3417       this._writeUpdatesToXMLFile([this._activeUpdate],
3418                                   getUpdateFile([FILE_UPDATE_ACTIVE]));
3419     return activeUpdate;
3420   },
3422   /**
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.
3425    * @param   update
3426    *          The nsIUpdate object to add.
3427    */
3428   _addUpdate: function UM__addUpdate(update) {
3429     if (!update)
3430       return;
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
3438           // all metadata.
3439           this._updates[i] = update;
3440           return;
3441         }
3442       }
3443     }
3444     // Otherwise add it to the front of the list.
3445     this._updates.unshift(update);
3446   },
3448   /**
3449    * Serializes an array of updates to an XML file
3450    * @param   updates
3451    *          An array of nsIUpdate objects
3452    * @param   file
3453    *          The nsIFile object to serialize to
3454    */
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;
3460     if (!file.exists())
3461       file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
3462     fos.init(file, modeFlags, FileUtils.PERMS_FILE, 0);
3464     try {
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) {
3471         if (updates[i])
3472           doc.documentElement.appendChild(updates[i].serialize(doc));
3473       }
3475       var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
3476                        createInstance(Ci.nsIDOMSerializer);
3477       serializer.serializeToStream(doc.documentElement, fos, null);
3478     }
3479     catch (e) {
3480     }
3482     FileUtils.closeSafeFileOutputStream(fos);
3483   },
3485   /**
3486    * See nsIUpdateService.idl
3487    */
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);
3504         }
3505       }
3507       this._writeUpdatesToXMLFile(updates.slice(0, 10),
3508                                   getUpdateFile([FILE_UPDATES_DB]));
3509     }
3510   },
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);
3521       }
3522     }
3523     if (update.state == STATE_APPLIED && shouldUseService()) {
3524       writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SVC);
3525     }
3526     var um = Cc["@mozilla.org/updates/update-manager;1"].
3527              getService(Ci.nsIUpdateManager);
3528     um.saveUpdates();
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);
3535     }
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
3544     // to shut down.
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);
3553     } else {
3554       releaseSDCardMountLock();
3555     }
3556 #else
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)) {
3561       return;
3562     }
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);
3571     }
3572 #endif
3573   },
3575   classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
3576   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager, Ci.nsIObserver])
3580  * Checker
3581  * Checks for new Updates
3582  * @constructor
3583  */
3584 function Checker() {
3586 Checker.prototype = {
3587   /**
3588    * The XMLHttpRequest object that performs the connection.
3589    */
3590   _request  : null,
3592   /**
3593    * The nsIUpdateCheckListener callback
3594    */
3595   _callback : null,
3597   /**
3598    * The URL of the update service XML file to connect to that contains details
3599    * about available updates.
3600    */
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.
3608     if (!url) {
3609       try {
3610         url = Services.prefs.getDefaultBranch(null).
3611               getCharPref(PREF_APP_UPDATE_URL);
3612       } catch (e) {
3613       }
3614     }
3616     if (!url || url == "") {
3617       LOG("Checker:getUpdateURL - update URL not defined");
3618       return null;
3619     }
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));
3641 #endif
3643     if (force)
3644       url += (url.indexOf("?") != -1 ? "&" : "?") + "force=1";
3646     LOG("Checker:getUpdateURL - update URL: " + url);
3647     return url;
3648   },
3650   /**
3651    * See nsIUpdateService.idl
3652    */
3653   checkForUpdates: function UC_checkForUpdates(listener, force) {
3654     LOG("Checker: checkForUpdates, force: " + force);
3655     if (!listener)
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))
3662       return;
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;
3671     }
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
3684     // cached locally.
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");
3690     var self = this;
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;
3698   },
3700   /**
3701    * Returns an array of nsIUpdate objects discovered by the update check.
3702    * @throws if the XML document element node name is not updates.
3703    */
3704   get _updates() {
3705     var updatesElement = this._request.responseXML.documentElement;
3706     if (!updatesElement) {
3707       LOG("Checker:_updates get - empty updates document?!");
3708       return [];
3709     }
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);
3715     }
3717     const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
3718     var updates = [];
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")
3723         continue;
3725       updateElement.QueryInterface(Ci.nsIDOMElement);
3726       try {
3727         var update = new Update(updateElement);
3728       } catch (e) {
3729         LOG("Checker:_updates get - invalid <update/>, ignoring...");
3730         continue;
3731       }
3732       update.serviceURL = this.getUpdateURL(this._forced);
3733       update.channel = UpdateChannel.get();
3734       updates.push(update);
3735     }
3737     return updates;
3738   },
3740   /**
3741    * Returns the status code for the XMLHttpRequest
3742    */
3743   _getChannelStatus: function UC__getChannelStatus(request) {
3744     var status = 0;
3745     try {
3746       status = request.status;
3747     }
3748     catch (e) {
3749     }
3751     if (status == 0)
3752       status = request.channel.QueryInterface(Ci.nsIRequest).status;
3753     return status;
3754   },
3756   _isHttpStatusCode: function UC__isHttpStatusCode(status) {
3757     return status >= 100 && status <= 599;
3758   },
3760   /**
3761    * The XMLHttpRequest succeeded and the document was loaded.
3762    * @param   event
3763    *          The nsIDOMEvent for the load
3764    */
3765   onLoad: function UC_onLoad(event) {
3766     LOG("Checker:onLoad - request completed downloading document");
3768     var prefs = Services.prefs;
3769     var certs = null;
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);
3773     }
3775     try {
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);
3793     }
3794     catch (e) {
3795       LOG("Checker:onLoad - there was a problem checking for updates. " +
3796           "Exception: " + e);
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;
3806       }
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;
3810       }
3812       recordInHealthReport(UpdaterHealthReportFields.CHECK_FAILED, update.errorCode);
3814       this._callback.onError(request, update);
3815     }
3817     this._callback = null;
3818     this._request = null;
3819   },
3821   /**
3822    * There was an error of some kind during the XMLHttpRequest
3823    * @param   event
3824    *          The nsIDOMEvent for the error
3825    */
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;
3843     }
3845     recordInHealthReport(UpdaterHealthReportFields.CHECK_FAILED, update.errorCode);
3847     this._callback.onError(request, update);
3849     this._request = null;
3850   },
3852   /**
3853    * Whether or not we are allowed to do update checking.
3854    */
3855   _enabled: true,
3856   get enabled() {
3857     if (!gMetroUpdatesEnabled) {
3858       return false;
3859     }
3861     return getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) &&
3862            gCanCheckForUpdates && hasUpdateMutex() && this._enabled;
3863   },
3865   /**
3866    * See nsIUpdateService.idl
3867    */
3868   stopChecking: function UC_stopChecking(duration) {
3869     // Always stop the current check
3870     if (this._request)
3871       this._request.abort();
3873     switch (duration) {
3874     case Ci.nsIUpdateChecker.CURRENT_SESSION:
3875       this._enabled = false;
3876       break;
3877     case Ci.nsIUpdateChecker.ANY_CHECKS:
3878       this._enabled = false;
3879       Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled);
3880       break;
3881     }
3883     this._callback = null;
3884   },
3886   classID: Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"),
3887   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateChecker])
3891  * Manages the download of updates
3892  * @param   background
3893  *          Whether or not this downloader is operating in background
3894  *          update mode.
3895  * @param   updateService
3896  *          The update service that created this downloader.
3897  * @constructor
3898  */
3899 function Downloader(background, updateService) {
3900   LOG("Creating Downloader");
3901   this.background = background;
3902   this.updateService = updateService;
3904 Downloader.prototype = {
3905   /**
3906    * The nsIUpdatePatch that we are downloading
3907    */
3908   _patch: null,
3910   /**
3911    * The nsIUpdate that we are downloading
3912    */
3913   _update: null,
3915   /**
3916    * The nsIIncrementalDownload object handling the download
3917    */
3918   _request: null,
3920   /**
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.
3924    */
3925   isCompleteUpdate: null,
3927   /**
3928    * Cancels the active download.
3929    */
3930   cancel: function Downloader_cancel(cancelError) {
3931     LOG("Downloader: cancel");
3932     if (cancelError === undefined) {
3933       cancelError = Cr.NS_BINDING_ABORTED;
3934     }
3935     if (this._request && this._request instanceof Ci.nsIRequest) {
3936       this._request.cancel(cancelError);
3937     }
3938     releaseSDCardMountLock();
3939   },
3941   /**
3942    * Whether or not a patch has been downloaded and staged for installation.
3943    */
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;
3951   },
3953   /**
3954    * Verify the downloaded file.  We assume that the download is complete at
3955    * this point.
3956    */
3957   _verifyDownload: function Downloader__verifyDownload() {
3958     LOG("Downloader:_verifyDownload called");
3959     if (!this._request)
3960       return false;
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.");
3967       return false;
3968     }
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);
3975     try {
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));
3988     } catch (e) {
3989       LOG("Downloader:_verifyDownload - failed to compute hash of the " +
3990           "downloaded update archive");
3991       digest = "";
3992     }
3994     fileStream.close();
3996     if (digest == this._patch.hashValue.toLowerCase()) {
3997       LOG("Downloader:_verifyDownload hashes match.");
3998       return true;
3999     }
4001     LOG("Downloader:_verifyDownload hashes do not match. ");
4002     return false;
4003   },
4005   /**
4006    * Select the patch to use given the current state of updateDir and the given
4007    * set of update patches.
4008    * @param   update
4009    *          A nsIUpdate object to select a patch from
4010    * @param   updateDir
4011    *          A nsIFile representing the update directory
4012    * @return  A nsIUpdatePatch object to download
4013    */
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.
4018     /**
4019      * Return the first UpdatePatch with the given type.
4020      * @param   type
4021      *          The type of the patch ("complete" or "partial")
4022      * @return  A nsIUpdatePatch object matching the type specified
4023      */
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)
4028           return patch;
4029       }
4030       return null;
4031     }
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: " +
4045           state);
4046       switch (state) {
4047       case STATE_DOWNLOADING:
4048         LOG("Downloader:_selectPatch - resuming download");
4049         return selectedPatch;
4050 #ifdef MOZ_WIDGET_GONK
4051       case STATE_PENDING:
4052       case STATE_APPLYING:
4053         LOG("Downloader:_selectPatch - resuming interrupted apply");
4054         return selectedPatch;
4055       case STATE_APPLIED:
4056         LOG("Downloader:_selectPatch - already downloaded and staged");
4057         return null;
4058 #else
4059       case STATE_PENDING_SVC:
4060       case STATE_PENDING:
4061         LOG("Downloader:_selectPatch - already downloaded and staged");
4062         return null;
4063 #endif
4064       default:
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") {
4068           useComplete = true;
4069         } else {
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);
4074           return null;
4075         }
4076       }
4078       selectedPatch = null;
4079     }
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");
4084     if (!useComplete)
4085       selectedPatch = partialPatch;
4086     if (!selectedPatch) {
4087       if (partialPatch)
4088         partialPatch.selected = false;
4089       selectedPatch = getPatchOfType("complete");
4090     }
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
4097     // complete patch
4098     if (selectedPatch)
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;
4110   },
4112   /**
4113    * Whether or not we are currently downloading something.
4114    */
4115   get isBusy() {
4116     return this._request != null;
4117   },
4119   /**
4120    * Get the nsIFile to use for downloading the active update's selected patch
4121    */
4122   _getUpdateArchiveFile: function Downloader__getUpdateArchiveFile() {
4123     var updateArchive;
4124 #ifdef USE_UPDATE_ARCHIVE_DIR
4125     try {
4126       updateArchive = FileUtils.getDir(KEY_UPDATE_ARCHIVE_DIR, [], true);
4127     } catch (e) {
4128       return null;
4129     }
4130 #else
4131     updateArchive = getUpdatesDir().clone();
4132 #endif
4134     updateArchive.append(FILE_UPDATE_ARCHIVE);
4135     return updateArchive;
4136   },
4138   /**
4139    * Download and stage the given update.
4140    * @param   update
4141    *          A nsIUpdate object to download a patch for. Cannot be null.
4142    */
4143   downloadUpdate: function Downloader_downloadUpdate(update) {
4144     LOG("UpdateService:_downloadUpdate");
4145     if (!update)
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
4153     // to download.
4154     this._patch = this._selectPatch(update, updateDir);
4155     if (!this._patch) {
4156       LOG("Downloader:downloadUpdate - no patch to download");
4157       return readStatusFile(updateDir);
4158     }
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
4173       // re-download.
4174       patchFile = getFileFromUpdateLink(updateDir);
4175       if (!patchFile) {
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);
4180       }
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;
4187         }
4188       } else {
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
4192         // a new download.
4193         patchFile = null;
4194       }
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;
4218       }
4219     }
4220 #endif
4221     if (!patchFile) {
4222       // Find a place to put the patchfile that we're going to download.
4223       patchFile = this._getUpdateArchiveFile();
4224     }
4225     if (!patchFile) {
4226       return STATE_NONE;
4227     }
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);
4238       }
4239     }
4240 #endif
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 " +
4248         patchFile.path);
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);
4261     um.saveUpdates();
4262     return STATE_DOWNLOADING;
4263   },
4265   /**
4266    * An array of download listeners to notify when we receive
4267    * nsIRequestObserver or nsIProgressEventSink method calls.
4268    */
4269   _listeners: [],
4271   /**
4272    * Adds a listener to the download process
4273    * @param   listener
4274    *          A download listener, implementing nsIRequestObserver and
4275    *          nsIProgressEventSink
4276    */
4277   addDownloadListener: function Downloader_addDownloadListener(listener) {
4278     for (var i = 0; i < this._listeners.length; ++i) {
4279       if (this._listeners[i] == listener)
4280         return;
4281     }
4282     this._listeners.push(listener);
4283   },
4285   /**
4286    * Removes a download listener
4287    * @param   listener
4288    *          The listener to remove.
4289    */
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);
4294         return;
4295       }
4296     }
4297   },
4299   /**
4300    * When the async request begins
4301    * @param   request
4302    *          The nsIRequest object for the transfer
4303    * @param   context
4304    *          Additional data
4305    */
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);
4314     um.saveUpdates();
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);
4320   },
4322   /**
4323    * When new data has been downloaded
4324    * @param   request
4325    *          The nsIRequest object for the transfer
4326    * @param   context
4327    *          Additional data
4328    * @param   progress
4329    *          The current number of bytes transferred
4330    * @param   maxProgress
4331    *          The total number of bytes that must be transferred
4332    */
4333   onProgress: function Downloader_onProgress(request, context, progress,
4334                                              maxProgress) {
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);
4344       return;
4345     }
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);
4354       return;
4355     }
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);
4363     }
4364     this.updateService._consecutiveSocketErrors = 0;
4365   },
4367   /**
4368    * When we have new status text
4369    * @param   request
4370    *          The nsIRequest object for the transfer
4371    * @param   context
4372    *          Additional data
4373    * @param   status
4374    *          A status code
4375    * @param   statusText
4376    *          Human readable version of |status|
4377    */
4378   onStatus: function Downloader_onStatus(request, context, status, statusText) {
4379     LOG("Downloader:onStatus - status: " + status + ", statusText: " +
4380         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);
4388     }
4389   },
4391   /**
4392    * When data transfer ceases
4393    * @param   request
4394    *          The nsIRequest object for the transfer
4395    * @param   context
4396    *          Additional data
4397    * @param   status
4398    *          Status code containing the reason for the cessation.
4399    */
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();
4428         }
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");
4435       }
4436       else {
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();
4451       }
4452     } else {
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;
4492 #endif
4494         // Destroy the updates directory, since we're done with it.
4495         cleanUpUpdatesDir();
4497         deleteActiveUpdate = true;
4498       }
4499     }
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;
4507     }
4508     else {
4509       if (um.activeUpdate)
4510         um.activeUpdate.state = state;
4511     }
4512     um.saveUpdates();
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);
4521       }
4522     }
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();
4537         } else {
4538           allFailed = false;
4539         }
4540       }
4542       if (allFailed) {
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)) {
4549           try {
4550             this._update.QueryInterface(Ci.nsIWritablePropertyBag);
4551             var fgdl = this._update.getProperty("foregroundDownload");
4552           }
4553           catch (e) {
4554           }
4556           if (fgdl == "true") {
4557             var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
4558                            createInstance(Ci.nsIUpdatePrompt);
4559             prompter.showUpdateError(this._update);
4560           }
4561         }
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);
4568 #endif
4570         // Prevent leaking the update object (bug 454964).
4571         this._update = null;
4572       }
4573       // A complete download has been initiated or the failure was handled.
4574       return;
4575     }
4577     if (state == STATE_PENDING || state == STATE_PENDING_SVC) {
4578       if (getCanStageUpdates()) {
4579         LOG("Downloader:onStopRequest - attempting to stage update: " +
4580             this._update.name);
4582         // Initiate the update in the background
4583         try {
4584           Cc["@mozilla.org/updates/update-processor;1"].
4585             createInstance(Ci.nsIUpdateProcessor).
4586             processUpdate(this._update);
4587         } catch (e) {
4588           // Fail gracefully in case the application does not support the update
4589           // processor service.
4590           LOG("Downloader:onStopRequest - failed to stage update. Exception: " +
4591               e);
4592           if (this.background) {
4593             shouldShowPrompt = true;
4594           }
4595         }
4596       }
4597     }
4599     // Do this after *everything* else, since it will likely cause the app
4600     // to shut down.
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);
4608     }
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();
4618       }
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);
4623     } else {
4624       // Prevent leaking the update object (bug 454964)
4625       this._update = null;
4626     }
4627   },
4629   /**
4630    * See nsIInterfaceRequestor.idl
4631    */
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"].
4637                    createInstance();
4638       return prompt.QueryInterface(iid);
4639     }
4640     throw Cr.NS_NOINTERFACE;
4641   },
4643   QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
4644                                          Ci.nsIProgressEventSink,
4645                                          Ci.nsIInterfaceRequestor])
4649  * UpdatePrompt
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.
4653  * @constructor
4654  */
4655 function UpdatePrompt() {
4657 UpdatePrompt.prototype = {
4658   /**
4659    * See nsIUpdateService.idl
4660    */
4661   checkForUpdates: function UP_checkForUpdates() {
4662     if (this._getAltUpdateWindow())
4663       return;
4665     this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
4666                  null, null);
4667   },
4669   /**
4670    * See nsIUpdateService.idl
4671    */
4672   showUpdateAvailable: function UP_showUpdateAvailable(update) {
4673     if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
4674         this._getUpdateWindow() || this._getAltUpdateWindow())
4675       return;
4677     var stringsPrefix = "updateAvailable_" + update.type + ".";
4678     var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title",
4679                                                    [update.name], 1);
4680     var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
4681     var imageUrl = "";
4682     this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
4683                            UPDATE_WINDOW_NAME, "updatesavailable", update,
4684                            title, text, imageUrl);
4685   },
4687   /**
4688    * See nsIUpdateService.idl
4689    */
4690   showUpdateDownloaded: function UP_showUpdateDownloaded(update, background) {
4691     if (this._getAltUpdateWindow())
4692       return;
4694     if (background) {
4695       if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false))
4696         return;
4698       var stringsPrefix = "updateDownloaded_" + update.type + ".";
4699       var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title",
4700                                                      [update.name], 1);
4701       var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
4702       var imageUrl = "";
4703       this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
4704                               UPDATE_WINDOW_NAME, "finishedBackground", update,
4705                               title, text, imageUrl);
4706     } else {
4707       this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
4708                    UPDATE_WINDOW_NAME, "finishedBackground", update);
4709     }
4710   },
4712   /**
4713    * See nsIUpdateService.idl
4714    */
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())
4719       return;
4721     var page = "installed";
4722     var win = this._getUpdateWindow();
4723     if (win) {
4724       if (page && "setCurrentPage" in win)
4725         win.setCurrentPage(page);
4726       win.focus();
4727     }
4728     else {
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);
4732       arg.data = page;
4733       Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, null, openFeatures, arg);
4734     }
4735   },
4737   /**
4738    * See nsIUpdateService.idl
4739    */
4740   showUpdateError: function UP_showUpdateError(update) {
4741     if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
4742         this._getAltUpdateWindow())
4743       return;
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);
4763       return;
4764     }
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);
4771       return;
4772     }
4774     this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
4775                  "errors", update);
4776   },
4778   /**
4779    * See nsIUpdateService.idl
4780    */
4781   showUpdateHistory: function UP_showUpdateHistory(parent) {
4782     this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal,dialog=yes",
4783                  "Update:History", null, null);
4784   },
4786   /**
4787    * Returns the update window if present.
4788    */
4789   _getUpdateWindow: function UP__getUpdateWindow() {
4790     return Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME);
4791   },
4793   /**
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.
4797    */
4798   _getAltUpdateWindow: function UP__getAltUpdateWindow() {
4799     let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
4800     if (!windowType)
4801       return null;
4802     return Services.wm.getMostRecentWindow(windowType);
4803   },
4805   /**
4806    * Initiate a less obtrusive UI, starting with a non-modal notification alert
4807    * @param   parent
4808    *          A parent window, can be null
4809    * @param   uri
4810    *          The URI string of the dialog to show
4811    * @param   name
4812    *          The Window Name of the dialog to show, in case it is already open
4813    *          and can merely be focused
4814    * @param   page
4815    *          The page of the wizard to be displayed, if one is already open.
4816    * @param   update
4817    *          An update to pass to the UI in the window arguments.
4818    *          Can be null
4819    * @param   title
4820    *          The title for the notification alert.
4821    * @param   text
4822    *          The contents of the notification alert.
4823    * @param   imageUrl
4824    *          A URL identifying the image to put in the notification alert.
4825    */
4826   _showUnobtrusiveUI: function UP__showUnobUI(parent, uri, features, name, page,
4827                                               update, title, text, imageUrl) {
4828     var observer = {
4829       updatePrompt: this,
4830       service: null,
4831       timer: null,
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())
4837           return;
4838         this.updatePrompt._showUIWhenIdle(parent, uri, features, name, page, update);
4839       },
4840       observe: function (aSubject, aTopic, aData) {
4841         switch (aTopic) {
4842           case "alertclickcallback":
4843             this.updatePrompt._showUI(parent, uri, features, name, page, update);
4844             // fall thru
4845           case "quit-application":
4846             if (this.timer)
4847               this.timer.cancel();
4848             this.service.removeObserver(this, "quit-application");
4849             break;
4850         }
4851       }
4852     };
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);
4864         return;
4865       }
4866     }
4868     try {
4869       var notifier = Cc["@mozilla.org/alerts-service;1"].
4870                      getService(Ci.nsIAlertsService);
4871       notifier.showAlertNotification(imageUrl, title, text, true, "", observer);
4872     }
4873     catch (e) {
4874       // Failed to retrieve alerts service, platform unsupported
4875       this._showUIWhenIdle(parent, uri, features, name, page, update);
4876       return;
4877     }
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);
4885       return;
4886     }
4888     // Give the user x seconds to react before prompting as defined by
4889     // promptWaitTime
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);
4894   },
4896   /**
4897    * Show the UI when the user was idle
4898    * @param   parent
4899    *          A parent window, can be null
4900    * @param   uri
4901    *          The URI string of the dialog to show
4902    * @param   name
4903    *          The Window Name of the dialog to show, in case it is already open
4904    *          and can merely be focused
4905    * @param   page
4906    *          The page of the wizard to be displayed, if one is already open.
4907    * @param   update
4908    *          An update to pass to the UI in the window arguments.
4909    *          Can be null
4910    */
4911   _showUIWhenIdle: function UP__showUIWhenIdle(parent, uri, features, name,
4912                                                page, update) {
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);
4919     } else {
4920       var observer = {
4921         updatePrompt: this,
4922         observe: function (aSubject, aTopic, aData) {
4923           switch (aTopic) {
4924             case "idle":
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);
4928               // fall thru
4929             case "quit-application":
4930               idleService.removeIdleObserver(this, IDLE_TIME);
4931               Services.obs.removeObserver(this, "quit-application");
4932               break;
4933           }
4934         }
4935       };
4936       idleService.addIdleObserver(observer, IDLE_TIME);
4937       Services.obs.addObserver(observer, "quit-application", false);
4938     }
4939   },
4941   /**
4942    * Show the Update Checking UI
4943    * @param   parent
4944    *          A parent window, can be null
4945    * @param   uri
4946    *          The URI string of the dialog to show
4947    * @param   name
4948    *          The Window Name of the dialog to show, in case it is already open
4949    *          and can merely be focused
4950    * @param   page
4951    *          The page of the wizard to be displayed, if one is already open.
4952    * @param   update
4953    *          An update to pass to the UI in the window arguments.
4954    *          Can be null
4955    */
4956   _showUI: function UP__showUI(parent, uri, features, name, page, update) {
4957     var ary = null;
4958     if (update) {
4959       ary = Cc["@mozilla.org/supports-array;1"].
4960             createInstance(Ci.nsISupportsArray);
4961       ary.AppendElement(update);
4962     }
4964     var win = this._getUpdateWindow();
4965     if (win) {
4966       if (page && "setCurrentPage" in win)
4967         win.setCurrentPage(page);
4968       win.focus();
4969     }
4970     else {
4971       var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
4972       if (features)
4973         openFeatures += "," + features;
4974       Services.ww.openWindow(parent, uri, "", openFeatures, ary);
4975     }
4976   },
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);
4987 #if 0
4989  * Logs a message and stack trace to the console.
4990  * @param   string
4991  *          The string to write to the console.
4992  */
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);
5001   if (!className)
5002     className = "<global>";
5003   var functionName = aFunctionName.substr(classDelimiter + 1, aFunctionName.length);
5004   if (!functionName)
5005     functionName = "<anonymous>";
5006   return className + "::" + functionName;
5009 function stackTraceArgumentsFormat(aArguments) {
5010   arglist = "";
5011   for (var i = 0; i < aArguments.length; i++) {
5012     arglist += aArguments[i];
5013     if (i < aArguments.length - 1)
5014       arglist += ", ";
5015   }
5016   return arglist;
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;
5024   var count = 0;
5025   while (temp) {
5026     dump("***     " + stackTraceFunctionFormat(temp.name) + "(" +
5027          stackTraceArgumentsFormat(temp.arguments) + ")\n");
5029     temp = temp.arguments.callee.caller;
5030     if (aMaxCount > 0 && ++count == aMaxCount)
5031       break;
5032   }
5033   dump("==================================================================\n");
5036 function dumpFile(file) {
5037   dump("*** file = " + file.path + ", exists = " + file.exists() + "\n");
5039 #endif