toolkit/mozapps/update/nsUpdateService.js

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

mercurial