|
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 |