!import
1 //@line 44 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
2
3 const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
4 const PREF_APP_UPDATE_AUTO = "app.update.auto";
5 const PREF_APP_UPDATE_MODE = "app.update.mode";
6 const PREF_APP_UPDATE_SILENT = "app.update.silent";
7 const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
8 const PREF_APP_UPDATE_TIMER = "app.update.timer";
9 const PREF_APP_UPDATE_IDLETIME = "app.update.idletime";
10 const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
11 const PREF_APP_UPDATE_LOG_BRANCH = "app.update.log.";
12 const PREF_APP_UPDATE_URL = "app.update.url";
13 const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override";
14 const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
15 const PREF_APP_UPDATE_CHANNEL = "app.update.channel";
16 const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI";
17 const PREF_APP_UPDATE_LASTUPDATETIME_FMT = "app.update.lastUpdateTime.%ID%";
18 const PREF_GENERAL_USERAGENT_LOCALE = "general.useragent.locale";
19 const PREF_APP_UPDATE_INCOMPATIBLE_MODE = "app.update.incompatible.mode";
20 const PREF_UPDATE_NEVER_BRANCH = "app.update.never.";
21 const PREF_PARTNER_BRANCH = "app.partner.";
22 const PREF_APP_DISTRIBUTION = "distribution.id";
23 const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
24
25 const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
26 const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
27 const URI_BRAND_PROPERTIES = "chrome://branding/locale/brand.properties";
28 const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
29 const URI_UPDATE_NS = "http://www.mozilla.org/2005/app-update";
30
31 const KEY_APPDIR = "XCurProcD";
32 //@line 78 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
33
34 const DIR_UPDATES = "updates";
35 const FILE_UPDATE_STATUS = "update.status";
36 const FILE_UPDATE_ARCHIVE = "update.mar";
37 const FILE_UPDATE_LOG = "update.log"
38 const FILE_UPDATES_DB = "updates.xml";
39 const FILE_UPDATE_ACTIVE = "active-update.xml";
40 const FILE_PERMS_TEST = "update.test";
41 const FILE_LAST_LOG = "last-update.log";
42
43 const MODE_RDONLY = 0x01;
44 const MODE_WRONLY = 0x02;
45 const MODE_CREATE = 0x08;
46 const MODE_APPEND = 0x10;
47 const MODE_TRUNCATE = 0x20;
48
49 const PERMS_FILE = 0644;
50 const PERMS_DIRECTORY = 0755;
51
52 const STATE_NONE = "null";
53 const STATE_DOWNLOADING = "downloading";
54 const STATE_PENDING = "pending";
55 const STATE_APPLYING = "applying";
56 const STATE_SUCCEEDED = "succeeded";
57 const STATE_DOWNLOAD_FAILED = "download-failed";
58 const STATE_FAILED = "failed";
59
60 // From updater/errors.h:
61 const WRITE_ERROR = 7;
62
63 const DOWNLOAD_CHUNK_SIZE = 300000; // bytes
64 const DOWNLOAD_BACKGROUND_INTERVAL = 600; // seconds
65 const DOWNLOAD_FOREGROUND_INTERVAL = 0;
66
67 const TOOLKIT_ID = "toolkit@mozilla.org";
68
69 const POST_UPDATE_CONTRACTID = "@mozilla.org/updates/post-update;1";
70
71 const nsIExtensionManager = Components.interfaces.nsIExtensionManager;
72 const nsILocalFile = Components.interfaces.nsILocalFile;
73 const nsIUpdateService = Components.interfaces.nsIUpdateService;
74 const nsIUpdateItem = Components.interfaces.nsIUpdateItem;
75 const nsIPrefLocalizedString = Components.interfaces.nsIPrefLocalizedString;
76 const nsIIncrementalDownload = Components.interfaces.nsIIncrementalDownload;
77 const nsIFileInputStream = Components.interfaces.nsIFileInputStream;
78 const nsIFileOutputStream = Components.interfaces.nsIFileOutputStream;
79 const nsICryptoHash = Components.interfaces.nsICryptoHash;
80
81 const Node = Components.interfaces.nsIDOMNode;
82
83 var gApp = null;
84 var gPref = null;
85 var gABI = null;
86 var gOSVersion = null;
87 var gConsole = null;
88 var gLogEnabled = { };
89
90 // shared code for suppressing bad cert dialogs
91 //@line 40 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/shared/src/badCertHandler.js"
92
93 /**
94 * Only allow built-in certs for HTTPS connections. See bug 340198.
95 */
checkCert
96 function checkCert(channel) {
97 if (!channel.originalURI.schemeIs("https")) // bypass
98 return;
99
100 const Ci = Components.interfaces;
101 var cert =
102 channel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider).
103 SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
104
105 var issuer = cert.issuer;
106 while (issuer && !cert.equals(issuer)) {
107 cert = issuer;
108 issuer = cert.issuer;
109 }
110
111 if (!issuer || issuer.tokenName != "Builtin Object Token")
112 throw "cert issuer is not built-in";
113 }
114
115 /**
116 * This class implements nsIBadCertListener. It's job is to prevent "bad cert"
117 * security dialogs from being shown to the user. It is better to simply fail
118 * if the certificate is bad. See bug 304286.
119 */
BadCertHandler
120 function BadCertHandler() {
121 }
122 BadCertHandler.prototype = {
123
124 // nsIChannelEventSink
onChannelRedirect
125 onChannelRedirect: function(oldChannel, newChannel, flags) {
126 // make sure the certificate of the old channel checks out before we follow
127 // a redirect from it. See bug 340198.
128 checkCert(oldChannel);
129 },
130
131 // Suppress any certificate errors
notifyCertProblem
132 notifyCertProblem: function(socketInfo, status, targetSite) {
133 return true;
134 },
135
136 // Suppress any ssl errors
notifySSLError
137 notifySSLError: function(socketInfo, error, targetSite) {
138 return true;
139 },
140
141 // nsIInterfaceRequestor
getInterface
142 getInterface: function(iid) {
143 return this.QueryInterface(iid);
144 },
145
146 // nsISupports
QueryInterface
147 QueryInterface: function(iid) {
148 if (!iid.equals(Components.interfaces.nsIChannelEventSink) &&
149 !iid.equals(Components.interfaces.nsIBadCertListener2) &&
150 !iid.equals(Components.interfaces.nsISSLErrorListener) &&
151 !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
152 !iid.equals(Components.interfaces.nsISupports))
153 throw Components.results.NS_ERROR_NO_INTERFACE;
154 return this;
155 }
156 };
157 //@line 137 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
158
159 /**
160 * Logs a string to the error console.
161 * @param string
162 * The string to write to the error console..
163 */
LOG
Called By: nsUpdateService.js:_postUpdateProcessing (4 calls, 54 v-uS)
nsUpdateService.js:_loadXMLFileIntoArray (1 calls, 9 v-uS)
nsUpdateService.js:readStatusFile (1 calls, 8 v-uS)
164 function LOG(module, string) {
165 if (module in gLogEnabled || "all" in gLogEnabled) {
166 dump("*** " + module + ": " + string + "\n");
167 gConsole.logStringMessage(string);
168 }
169 }
170
171 /**
172 * Convert a string containing binary values to hex.
173 */
binaryToHex
174 function binaryToHex(input) {
175 var result = "";
176 for (var i = 0; i < input.length; ++i) {
177 var hex = input.charCodeAt(i).toString(16);
178 if (hex.length == 1)
179 hex = "0" + hex;
180 result += hex;
181 }
182 return result;
183 }
184
185 /**
186 * Gets a File URL spec for a nsIFile
187 * @param file
188 * The file to get a file URL spec to
189 * @returns The file URL spec to the file
190 */
getURLSpecFromFile
191 function getURLSpecFromFile(file) {
192 var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
193 .getService(Components.interfaces.nsIIOService);
194 var fph = ioServ.getProtocolHandler("file")
195 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
196 return fph.getURLSpecFromFile(file);
197 }
198
199 /**
200 * Gets the specified directory at the specified hierarchy under a
201 * Directory Service key.
202 * @param key
203 * The Directory Service Key to start from
204 * @param pathArray
205 * An array of path components to locate beneath the directory
206 * specified by |key|
207 * @return nsIFile object for the location specified. If the directory
208 * requested does not exist, it is created, along with any
209 * parent directories that need to be created.
210 */
getDir
211 function getDir(key, pathArray) {
212 return getDirInternal(key, pathArray, true, false);
213 }
214
215 /**
216 * Gets the specified directory at the specified hierarchy under a
217 * Directory Service key.
218 * @param key
219 * The Directory Service Key to start from
220 * @param pathArray
221 * An array of path components to locate beneath the directory
222 * specified by |key|
223 * @return nsIFile object for the location specified. If the directory
224 * requested does not exist, it is NOT created.
225 */
getDirNoCreate
226 function getDirNoCreate(key, pathArray) {
227 return getDirInternal(key, pathArray, false, false);
228 }
229
230 /**
231 * Gets the specified directory at the specified hierarchy under the
232 * update root directory.
233 * @param pathArray
234 * An array of path components to locate beneath the directory
235 * specified by |key|
236 * @return nsIFile object for the location specified. If the directory
237 * requested does not exist, it is created, along with any
238 * parent directories that need to be created.
239 */
getUpdateDir
Called: nsUpdateService.js:getDirInternal (2 calls, 236 v-uS)
Called By: nsUpdateService.js:getUpdateFile (2 calls, 270 v-uS)
240 function getUpdateDir(pathArray) {
241 return getDirInternal(KEY_APPDIR, pathArray, true, true);
242 }
243
244 /**
245 * Gets the specified directory at the specified hierarchy under a
246 * Directory Service key.
247 * @param key
248 * The Directory Service Key to start from
249 * @param pathArray
250 * An array of path components to locate beneath the directory
251 * specified by |key|
252 * @param shouldCreate
253 * true if the directory hierarchy specified in |pathArray|
254 * should be created if it does not exist,
255 * false otherwise.
256 * @param update
257 * true if finding the update directory,
258 * false otherwise.
259 * @return nsIFile object for the location specified.
260 */
getDirInternal
Called: BackstagePass:get (2 calls, 47 v-uS)
BackstagePass:getService (2 calls, 37 v-uS)
Called By: nsUpdateService.js:getUpdateDir (2 calls, 236 v-uS)
261 function getDirInternal(key, pathArray, shouldCreate, update) {
262 var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
263 .getService(Components.interfaces.nsIProperties);
264 var dir = fileLocator.get(key, Components.interfaces.nsIFile);
265 //@line 252 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
266 for (var i = 0; i < pathArray.length; ++i) {
267 dir.append(pathArray[i]);
268 if (shouldCreate && !dir.exists())
269 dir.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
270 }
271 return dir;
272 }
273
274 /**
275 * Gets the file at the specified hierarchy under a Directory Service key.
276 * @param key
277 * The Directory Service Key to start from
278 * @param pathArray
279 * An array of path components to locate beneath the directory
280 * specified by |key|. The last item in this array must be the
281 * leaf name of a file.
282 * @return nsIFile object for the file specified. The file is NOT created
283 * if it does not exist, however all required directories along
284 * the way are.
285 */
getFile
286 function getFile(key, pathArray) {
287 var file = getDir(key, pathArray.slice(0, -1));
288 file.append(pathArray[pathArray.length - 1]);
289 return file;
290 }
291
292 /**
293 * Gets the file at the specified hierarchy under the update root directory.
294 * @param pathArray
295 * An array of path components to locate beneath the directory
296 * specified by |key|. The last item in this array must be the
297 * leaf name of a file.
298 * @return nsIFile object for the file specified. The file is NOT created
299 * if it does not exist, however all required directories along
300 * the way are.
301 */
getUpdateFile
Called: BackstagePass:append (2 calls, 151 v-uS)
BackstagePass:slice (2 calls, 38 v-uS)
nsUpdateService.js:getUpdateDir (2 calls, 270 v-uS)
Called By: Object:getService (1 calls, 225 v-uS)
nsUpdateService.js:_postUpdateProcessing (1 calls, 317 v-uS)
302 function getUpdateFile(pathArray) {
303 var file = getUpdateDir(pathArray.slice(0, -1));
304 file.append(pathArray[pathArray.length - 1]);
305 return file;
306 }
307
308 /**
309 * Closes a Safe Output Stream
310 * @param fos
311 * The Safe Output Stream to close
312 */
closeSafeOutputStream
313 function closeSafeOutputStream(fos) {
314 if (fos instanceof Components.interfaces.nsISafeOutputStream) {
315 try {
316 fos.finish();
317 }
318 catch (e) {
319 fos.close();
320 }
321 }
322 else
323 fos.close();
324 }
325
326 /**
327 * Returns human readable status text from the updates.properties bundle
328 * based on an error code
329 * @param code
330 * The error code to look up human readable status text for
331 * @param defaultCode
332 * The default code to look up should human readable status text
333 * not exist for |code|
334 * @returns A human readable status text string
335 */
getStatusTextFromCode
336 function getStatusTextFromCode(code, defaultCode) {
337 var sbs =
338 Components.classes["@mozilla.org/intl/stringbundle;1"].
339 getService(Components.interfaces.nsIStringBundleService);
340 var updateBundle = sbs.createBundle(URI_UPDATES_PROPERTIES);
341 var reason = updateBundle.GetStringFromName("checker_error-" + defaultCode);
342 try {
343 reason = updateBundle.GetStringFromName("checker_error-" + code);
344 LOG("General", "Transfer Error: " + reason + ", code: " + code);
345 }
346 catch (e) {
347 // Use the default reason
348 LOG("General", "Transfer Error: " + reason + ", code: " + defaultCode);
349 }
350 return reason;
351 }
352
353 /**
354 * Get the Active Updates directory
355 * @param key
356 * The Directory Service Key (optional).
357 * If used, don't search local appdata on Win32 and don't create dir.
358 * @returns The active updates directory, as a nsIFile object
359 */
getUpdatesDir
Called: BackstagePass:append (4 calls, 347 v-uS)
BackstagePass:exists (2 calls, 62 v-uS)
BackstagePass:get (2 calls, 61 v-uS)
BackstagePass:getService (2 calls, 49 v-uS)
Called By: nsUpdateService.js:_postUpdateProcessing (2 calls, 745 v-uS)
360 function getUpdatesDir(key) {
361 // Right now, we only support downloading one patch at a time, so we always
362 // use the same target directory.
363 var fileLocator =
364 Components.classes["@mozilla.org/file/directory_service;1"].
365 getService(Components.interfaces.nsIProperties);
366 var appDir;
367 if (key)
368 appDir = fileLocator.get(key, Components.interfaces.nsIFile);
369 else {
370 appDir = fileLocator.get(KEY_APPDIR, Components.interfaces.nsIFile);
371 //@line 363 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
372 }
373 appDir.append(DIR_UPDATES);
374 appDir.append("0");
375 if (!appDir.exists() && !key)
376 appDir.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
377 return appDir;
378 }
379
380 /**
381 * Reads the update state from the update.status file in the specified
382 * directory.
383 * @param dir
384 * The dir to look for an update.status file in
385 * @returns The status value of the update.
386 */
readStatusFile
Called: BackstagePass:append (1 calls, 52 v-uS)
BackstagePass:clone (1 calls, 17 v-uS)
nsUpdateService.js:LOG (1 calls, 8 v-uS)
nsUpdateService.js:readStringFromFile (1 calls, 1335 v-uS)
Called By: nsUpdateService.js:_postUpdateProcessing (1 calls, 1500 v-uS)
387 function readStatusFile(dir) {
388 var statusFile = dir.clone();
389 statusFile.append(FILE_UPDATE_STATUS);
390 LOG("General", "Reading Status File: " + statusFile.path);
391 return readStringFromFile(statusFile) || STATE_NONE;
392 }
393
394 /**
395 * Writes the current update operation/state to a file in the patch
396 * directory, indicating to the patching system that operations need
397 * to be performed.
398 * @param dir
399 * The patch directory where the update.status file should be
400 * written.
401 * @param state
402 * The state value to write.
403 */
writeStatusFile
404 function writeStatusFile(dir, state) {
405 var statusFile = dir.clone();
406 statusFile.append(FILE_UPDATE_STATUS);
407 writeStringToFile(statusFile, state);
408 }
409
410 /**
411 * Removes the Updates Directory
412 * @param key
413 * The Directory Service Key under which update directory resides
414 * (optional).
415 */
cleanUpUpdatesDir
416 function cleanUpUpdatesDir(key) {
417 // Bail out if we don't have appropriate permissions
418 var updateDir;
419 try {
420 updateDir = getUpdatesDir(key);
421 }
422 catch (e) {
423 return;
424 }
425
426 var e = updateDir.directoryEntries;
427 while (e.hasMoreElements()) {
428 var f = e.getNext().QueryInterface(Components.interfaces.nsIFile);
429 // Preserve the last update log file for debugging purposes
430 if (f.leafName == FILE_UPDATE_LOG) {
431 try {
432 var dir = f.parent.parent;
433 var logFile = dir.clone();
434 logFile.append(FILE_LAST_LOG);
435 if (logFile.exists())
436 logFile.remove(false);
437 f.copyTo(dir, FILE_LAST_LOG);
438 }
439 catch (e) {
440 LOG("General", "Failed to copy file: " + f.path);
441 }
442 }
443 // Now, recursively remove this file. The recusive removal is really
444 // only needed on Mac OSX because this directory will contain a copy of
445 // updater.app, which is itself a directory.
446 try {
447 f.remove(true);
448 }
449 catch (e) {
450 LOG("General", "Failed to remove file: " + f.path);
451 }
452 }
453 try {
454 updateDir.remove(false);
455 } catch (e) {
456 LOG("General", "Failed to remove update directory: " + updateDir.path +
457 " - This is almost always bad. Exception = " + e);
458 throw e;
459 }
460 }
461
462 /**
463 * Clean up updates list and the updates directory.
464 * @param key
465 * The Directory Service Key under which update directory resides
466 * (optional).
467 */
cleanupActiveUpdate
468 function cleanupActiveUpdate(key) {
469 // Move the update from the Active Update list into the Past Updates list.
470 var um =
471 Components.classes["@mozilla.org/updates/update-manager;1"].
472 getService(Components.interfaces.nsIUpdateManager);
473 um.activeUpdate = null;
474 um.saveUpdates();
475
476 // Now trash the updates directory, since we're done with it
477 cleanUpUpdatesDir(key);
478 }
479
480 /**
481 * Gets a preference value, handling the case where there is no default.
482 * @param func
483 * The name of the preference function to call, on nsIPrefBranch
484 * @param preference
485 * The name of the preference
486 * @param defaultValue
487 * The default value to return in the event the preference has
488 * no setting
489 * @returns The value of the preference, or undefined if there was no
490 * user or default value.
491 */
getPref
Called: BackstagePass:getIntPref (2 calls, 30 v-uS)
BackstagePass:getBoolPref (1 calls, 16 v-uS)
Called By: Object:getService (1 calls, 46 v-uS)
nsUpdateService.js:_postUpdateProcessing (1 calls, 46 v-uS)
nsUpdateService.js:_start (1 calls, 29 v-uS)
492 function getPref(func, preference, defaultValue) {
493 try {
494 return gPref[func](preference);
495 }
496 catch (e) {
497 }
498 return defaultValue;
499 }
500
501 /**
502 * Gets the current value of the locale. It's possible for this preference to
503 * be localized, so we have to do a little extra work here. Similar code
504 * exists in nsHttpHandler.cpp when building the UA string.
505 */
getLocale
506 function getLocale() {
507 try {
508 // Get the default branch
509 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
510 .getService(Components.interfaces.nsIPrefService);
511 var defaultPrefs = prefs.getDefaultBranch(null);
512 return defaultPrefs.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
513 } catch (e) {}
514
515 return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
516 }
517
518 /**
519 * Read the update channel from defaults only. We do this to ensure that
520 * the channel is tightly coupled with the application and does not apply
521 * to other instances of the application that may use the same profile.
522 */
getUpdateChannel
523 function getUpdateChannel() {
524 var channel = "default";
525 var prefName;
526 var prefValue;
527
528 var defaults =
529 gPref.QueryInterface(Components.interfaces.nsIPrefService).
530 getDefaultBranch(null);
531 try {
532 channel = defaults.getCharPref(PREF_APP_UPDATE_CHANNEL);
533 } catch (e) {
534 // use default when pref not found
535 }
536
537 try {
538 var partners = gPref.getChildList(PREF_PARTNER_BRANCH, { });
539 if (partners.length) {
540 channel += "-cck";
541 partners.sort();
542
543 for each (prefName in partners) {
544 prefValue = gPref.getCharPref(prefName);
545 channel += "-" + prefValue;
546 }
547 }
548 }
549 catch (e) {
550 Components.utils.reportError(e);
551 }
552
553 return channel;
554 }
555
556 /* Get the distribution pref values, from defaults only */
getDistributionPrefValue
557 function getDistributionPrefValue(aPrefName) {
558 var prefValue = "default";
559
560 var defaults =
561 gPref.QueryInterface(Components.interfaces.nsIPrefService).
562 getDefaultBranch(null);
563 try {
564 prefValue = defaults.getCharPref(aPrefName);
565 } catch (e) {
566 // use default when pref not found
567 }
568
569 return prefValue;
570 }
571
572 /**
573 * An enumeration of items in a JS array.
574 * @constructor
575 */
ArrayEnumerator
576 function ArrayEnumerator(aItems) {
577 this._index = 0;
578 if (aItems) {
579 for (var i = 0; i < aItems.length; ++i) {
580 if (!aItems[i])
581 aItems.splice(i, 1);
582 }
583 }
584 this._contents = aItems;
585 }
586
587 ArrayEnumerator.prototype = {
588 _index: 0,
589 _contents: [],
590
hasMoreElements
591 hasMoreElements: function() {
592 return this._index < this._contents.length;
593 },
594
getNext
595 getNext: function() {
596 return this._contents[this._index++];
597 }
598 };
599
600 /**
601 * Trims a prefix from a string.
602 * @param string
603 * The source string
604 * @param prefix
605 * The prefix to remove.
606 * @returns The suffix (string - prefix)
607 */
stripPrefix
608 function stripPrefix(string, prefix) {
609 return string.substr(prefix.length);
610 }
611
612 /**
613 * Writes a string of text to a file. A newline will be appended to the data
614 * written to the file. This function only works with ASCII text.
615 */
writeStringToFile
616 function writeStringToFile(file, text) {
617 var fos =
618 Components.classes["@mozilla.org/network/safe-file-output-stream;1"].
619 createInstance(nsIFileOutputStream);
620 var modeFlags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
621 if (!file.exists())
622 file.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
623 fos.init(file, modeFlags, PERMS_FILE, 0);
624 text += "\n";
625 fos.write(text, text.length);
626 closeSafeOutputStream(fos);
627 }
628
629 /**
630 * Reads a string of text from a file. A trailing newline will be removed
631 * before the result is returned. This function only works with ASCII text.
632 */
readStringFromFile
Called: BackstagePass:createInstance (1 calls, 1190 v-uS)
BackstagePass:exists (1 calls, 52 v-uS)
Called By: nsUpdateService.js:readStatusFile (1 calls, 1335 v-uS)
633 function readStringFromFile(file) {
634 var fis =
635 Components.classes["@mozilla.org/network/file-input-stream;1"].
636 createInstance(nsIFileInputStream);
637 var modeFlags = MODE_RDONLY;
638 if (!file.exists())
639 return null;
640 fis.init(file, modeFlags, PERMS_FILE, 0);
641 var sis =
642 Components.classes["@mozilla.org/scriptableinputstream;1"].
643 createInstance(Components.interfaces.nsIScriptableInputStream);
644 sis.init(fis);
645 var text = sis.read(sis.available());
646 sis.close();
647 if (text[text.length - 1] == "\n")
648 text = text.slice(0, -1);
649 return text;
650 }
651
getObserverService
Called: BackstagePass:getService (6 calls, 108 v-uS)
Called By: Object:getService (1 calls, 44 v-uS)
652 function getObserverService()
653 {
654 return Components.classes["@mozilla.org/observer-service;1"]
655 .getService(Components.interfaces.nsIObserverService);
656 }
657
658 /**
659 * Update Patch
660 * @param patch
661 * A <patch> element to initialize this object with
662 * @throws if patch has a size of 0
663 * @constructor
664 */
UpdatePatch
665 function UpdatePatch(patch) {
666 this._properties = {};
667 for (var i = 0; i < patch.attributes.length; ++i) {
668 var attr = patch.attributes.item(i);
669 attr.QueryInterface(Components.interfaces.nsIDOMAttr);
670 switch (attr.name) {
671 case "selected":
672 this.selected = attr.value == "true";
673 break;
674 case "size":
675 if (0 == parseInt(attr.value)) {
676 LOG("UpdatePatch", "0-sized patch!");
677 throw Components.results.NS_ERROR_ILLEGAL_VALUE;
678 }
679 // fall through
680 default:
681 this[attr.name] = attr.value;
682 break;
683 };
684 }
685 }
686 UpdatePatch.prototype = {
687 /**
688 * See nsIUpdateService.idl
689 */
serialize
690 serialize: function(updates) {
691 var patch = updates.createElementNS(URI_UPDATE_NS, "patch");
692 patch.setAttribute("type", this.type);
693 patch.setAttribute("URL", this.URL);
694 patch.setAttribute("hashFunction", this.hashFunction);
695 patch.setAttribute("hashValue", this.hashValue);
696 patch.setAttribute("size", this.size);
697 patch.setAttribute("selected", this.selected);
698 patch.setAttribute("state", this.state);
699
700 for (var p in this._properties) {
701 if (this._properties[p].present)
702 patch.setAttribute(p, this._properties[p].data);
703 }
704
705 return patch;
706 },
707
708 /**
709 * A hash of custom properties
710 */
711 _properties: null,
712
713 /**
714 * See nsIWritablePropertyBag.idl
715 */
setProperty
716 setProperty: function(name, value) {
717 this._properties[name] = { data: value, present: true };
718 },
719
720 /**
721 * See nsIWritablePropertyBag.idl
722 */
deleteProperty
723 deleteProperty: function(name) {
724 if (name in this._properties)
725 this._properties[name].present = false;
726 else
727 throw Components.results.NS_ERROR_FAILURE;
728 },
729
730 /**
731 * See nsIPropertyBag.idl
732 */
get_enumerator
733 get enumerator() {
734 var properties = [];
735 for (var p in this._properties)
736 properties.push(this._properties[p].data);
737 return new ArrayEnumerator(properties);
738 },
739
740 /**
741 * See nsIPropertyBag.idl
742 */
getProperty
743 getProperty: function(name) {
744 if (name in this._properties &&
745 this._properties[name].present)
746 return this._properties[name].data;
747 throw Components.results.NS_ERROR_FAILURE;
748 },
749
750 /**
751 * Returns whether or not the update.status file for this patch exists at the
752 * appropriate location.
753 */
get_statusFileExists
754 get statusFileExists() {
755 var statusFile = getUpdatesDir();
756 statusFile.append(FILE_UPDATE_STATUS);
757 return statusFile.exists();
758 },
759
760 /**
761 * See nsIUpdateService.idl
762 */
get_state
763 get state() {
764 if (!this.statusFileExists)
765 return STATE_NONE;
766 return this._properties.state;
767 },
set_state
768 set state(val) {
769 this._properties.state = val;
770 },
771
772 /**
773 * See nsISupports.idl
774 */
QueryInterface
775 QueryInterface: function(iid) {
776 if (!iid.equals(Components.interfaces.nsIUpdatePatch) &&
777 !iid.equals(Components.interfaces.nsIPropertyBag) &&
778 !iid.equals(Components.interfaces.nsIWritablePropertyBag) &&
779 !iid.equals(Components.interfaces.nsISupports))
780 throw Components.results.NS_ERROR_NO_INTERFACE;
781 return this;
782 }
783 };
784
785 /**
786 * Update
787 * Implements nsIUpdate
788 * @param update
789 * An <update> element to initialize this object with
790 * @throws if the update contains no patches
791 * @constructor
792 */
Update
793 function Update(update) {
794 this._properties = {};
795 this._patches = [];
796 this.installDate = 0;
797 this.isCompleteUpdate = false;
798 this.channel = "default"
799
800 // Null <update>, assume this is a message container and do no
801 // further initialization
802 if (!update)
803 return;
804
805 for (var i = 0; i < update.childNodes.length; ++i) {
806 var patchElement = update.childNodes.item(i);
807 if (patchElement.nodeType != Node.ELEMENT_NODE ||
808 patchElement.localName != "patch")
809 continue;
810
811 patchElement.QueryInterface(Components.interfaces.nsIDOMElement);
812 try {
813 var patch = new UpdatePatch(patchElement);
814 } catch (e) {
815 continue;
816 }
817 this._patches.push(patch);
818 }
819
820 if (0 == this._patches.length)
821 throw Components.results.NS_ERROR_ILLEGAL_VALUE;
822
823 for (var i = 0; i < update.attributes.length; ++i) {
824 var attr = update.attributes.item(i);
825 attr.QueryInterface(Components.interfaces.nsIDOMAttr);
826 if (attr.name == "installDate" && attr.value)
827 this.installDate = parseInt(attr.value);
828 else if (attr.name == "isCompleteUpdate")
829 this.isCompleteUpdate = attr.value == "true";
830 else if (attr.name == "isSecurityUpdate")
831 this.isSecurityUpdate = attr.value == "true";
832 else if (attr.name == "detailsURL")
833 this._detailsURL = attr.value;
834 else if (attr.name == "channel")
835 this.channel = attr.value;
836 else
837 this[attr.name] = attr.value;
838 }
839
840 // The Update Name is either the string provided by the <update> element, or
841 // the string: "<App Name> <Update App Version>"
842 var name = "";
843 if (update.hasAttribute("name"))
844 name = update.getAttribute("name");
845 else {
846 var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
847 .getService(Components.interfaces.nsIStringBundleService);
848 var brandBundle = sbs.createBundle(URI_BRAND_PROPERTIES);
849 var updateBundle = sbs.createBundle(URI_UPDATES_PROPERTIES);
850 var appName = brandBundle.GetStringFromName("brandShortName");
851 name = updateBundle.formatStringFromName("updateName",
852 [appName, this.version], 2);
853 }
854 this.name = name;
855 }
856 Update.prototype = {
857 /**
858 * See nsIUpdateService.idl
859 */
get_patchCount
860 get patchCount() {
861 return this._patches.length;
862 },
863
864 /**
865 * See nsIUpdateService.idl
866 */
getPatchAt
867 getPatchAt: function(index) {
868 return this._patches[index];
869 },
870
871 /**
872 * See nsIUpdateService.idl
873 *
874 * We use a copy of the state cached on this object in |_state| only when
875 * there is no selected patch, i.e. in the case when we could not load
876 * |.activeUpdate| from the update manager for some reason but still have
877 * the update.status file to work with.
878 */
879 _state: "",
set_state
880 set state(state) {
881 if (this.selectedPatch)
882 this.selectedPatch.state = state;
883 this._state = state;
884 return state;
885 },
get_state
886 get state() {
887 if (this.selectedPatch)
888 return this.selectedPatch.state;
889 return this._state;
890 },
891
892 /**
893 * See nsIUpdateService.idl
894 */
895 errorCode: 0,
896
897 /**
898 * See nsIUpdateService.idl
899 */
get_selectedPatch
900 get selectedPatch() {
901 for (var i = 0; i < this.patchCount; ++i) {
902 if (this._patches[i].selected)
903 return this._patches[i];
904 }
905 return null;
906 },
907
908 /**
909 * See nsIUpdateService.idl
910 */
get_detailsURL
911 get detailsURL() {
912 if (!this._detailsURL) {
913 try {
914 // Try using a default details URL supplied by the distribution
915 // if the update XML does not supply one.
916 var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
917 .getService(Components.interfaces.nsIURLFormatter);
918 return formatter.formatURLPref(PREF_APP_UPDATE_URL_DETAILS);
919 }
920 catch (e) {
921 }
922 }
923 return this._detailsURL || "";
924 },
925
926 /**
927 * See nsIUpdateService.idl
928 */
serialize
929 serialize: function(updates) {
930 var update = updates.createElementNS(URI_UPDATE_NS, "update");
931 update.setAttribute("type", this.type);
932 update.setAttribute("name", this.name);
933 update.setAttribute("version", this.version);
934 update.setAttribute("platformVersion", this.platformVersion);
935 update.setAttribute("extensionVersion", this.extensionVersion);
936 update.setAttribute("detailsURL", this.detailsURL);
937 update.setAttribute("licenseURL", this.licenseURL);
938 update.setAttribute("serviceURL", this.serviceURL);
939 update.setAttribute("installDate", this.installDate);
940 update.setAttribute("statusText", this.statusText);
941 update.setAttribute("buildID", this.buildID);
942 update.setAttribute("isCompleteUpdate", this.isCompleteUpdate);
943 update.setAttribute("channel", this.channel);
944 updates.documentElement.appendChild(update);
945
946 for (var p in this._properties) {
947 if (this._properties[p].present)
948 update.setAttribute(p, this._properties[p].data);
949 }
950
951 for (var i = 0; i < this.patchCount; ++i)
952 update.appendChild(this.getPatchAt(i).serialize(updates));
953
954 return update;
955 },
956
957 /**
958 * A hash of custom properties
959 */
960 _properties: null,
961
962 /**
963 * See nsIWritablePropertyBag.idl
964 */
setProperty
965 setProperty: function(name, value) {
966 this._properties[name] = { data: value, present: true };
967 },
968
969 /**
970 * See nsIWritablePropertyBag.idl
971 */
deleteProperty
972 deleteProperty: function(name) {
973 if (name in this._properties)
974 this._properties[name].present = false;
975 else
976 throw Components.results.NS_ERROR_FAILURE;
977 },
978
979 /**
980 * See nsIPropertyBag.idl
981 */
get_enumerator
982 get enumerator() {
983 var properties = [];
984 for (var p in this._properties)
985 properties.push(this._properties[p].data);
986 return new ArrayEnumerator(properties);
987 },
988
989 /**
990 * See nsIPropertyBag.idl
991 */
getProperty
992 getProperty: function(name) {
993 if (name in this._properties &&
994 this._properties[name].present)
995 return this._properties[name].data;
996 throw Components.results.NS_ERROR_FAILURE;
997 },
998
999 /**
1000 * See nsISupports.idl
1001 */
QueryInterface
1002 QueryInterface: function(iid) {
1003 if (!iid.equals(Components.interfaces.nsIUpdate) &&
1004 !iid.equals(Components.interfaces.nsIPropertyBag) &&
1005 !iid.equals(Components.interfaces.nsIWritablePropertyBag) &&
1006 !iid.equals(Components.interfaces.nsISupports))
1007 throw Components.results.NS_ERROR_NO_INTERFACE;
1008 return this;
1009 }
1010 };
1011
1012 /**
1013 * UpdateService
1014 * A Service for managing the discovery and installation of software updates.
1015 * @constructor
1016 */
UpdateService
1017 function UpdateService() {
1018 gApp = Components.classes["@mozilla.org/xre/app-info;1"]
1019 .getService(Components.interfaces.nsIXULAppInfo)
1020 .QueryInterface(Components.interfaces.nsIXULRuntime);
1021 gPref = Components.classes["@mozilla.org/preferences-service;1"]
1022 .getService(Components.interfaces.nsIPrefBranch2);
1023 gConsole = Components.classes["@mozilla.org/consoleservice;1"]
1024 .getService(Components.interfaces.nsIConsoleService);
1025
1026 // Not all builds have a known ABI
1027 try {
1028 gABI = gApp.XPCOMABI;
1029 }
1030 catch (e) {
1031 LOG("UpdateService", "XPCOM ABI unknown: updates are not possible.");
1032 }
1033
1034 var osVersion;
1035 var sysInfo = Components.classes["@mozilla.org/system-info;1"]
1036 .getService(Components.interfaces.nsIPropertyBag2);
1037 try {
1038 osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
1039 }
1040 catch (e) {
1041 LOG("UpdateService", "OS Version unknown: updates are not possible.");
1042 }
1043
1044 if (osVersion) {
1045 try {
1046 osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
1047 }
1048 catch (e) {
1049 // Not all platforms have a secondary widget library, so an error is nothing to worry about.
1050 }
1051 gOSVersion = encodeURIComponent(osVersion);
1052 }
1053
1054 //@line 1046 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
1055 // Mac universal build should report a different ABI than either macppc
1056 // or mactel.
1057 var macutils = Components.classes["@mozilla.org/xpcom/mac-utils;1"]
1058 .getService(Components.interfaces.nsIMacUtils);
1059
1060 if (macutils.isUniversalBinary)
1061 gABI = "Universal-gcc3";
1062 //@line 1054 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
1063
1064 // Start the update timer only after a profile has been selected so that the
1065 // appropriate values for the update check are read from the user's profile.
1066 var os = getObserverService();
1067
1068 os.addObserver(this, "profile-after-change", false);
1069
1070 // Observe xpcom-shutdown to unhook pref branch observers above to avoid
1071 // shutdown leaks.
1072 os.addObserver(this, "xpcom-shutdown", false);
1073 }
1074
1075 UpdateService.prototype = {
1076 /**
1077 * The downloader we are using to download updates. There is only ever one of
1078 * these.
1079 */
1080 _downloader: null,
1081
1082 /**
1083 * Handle Observer Service notifications
1084 * @param subject
1085 * The subject of the notification
1086 * @param topic
1087 * The notification name
1088 * @param data
1089 * Additional data
1090 */
observe
1091 observe: function(subject, topic, data) {
1092 var os = getObserverService();
1093
1094 switch (topic) {
1095 case "profile-after-change":
1096 os.removeObserver(this, "profile-after-change");
1097 this._start();
1098 break;
1099 case "xpcom-shutdown":
1100 os.removeObserver(this, "xpcom-shutdown");
1101
1102 // Release Services
1103 gApp = null;
1104 gPref = null;
1105 gConsole = null;
1106 break;
1107 }
1108 },
1109
1110 /**
1111 * Start the Update Service
1112 */
_start
Called: Object:getService (2 calls, 3712 v-uS)
Object:registerTimer (1 calls, 968 v-uS)
nsUpdateService.js:_initLoggingPrefs (1 calls, 472 v-uS)
nsUpdateService.js:_postUpdateProcessing (1 calls, 6147 v-uS)
nsUpdateService.js:getPref (1 calls, 29 v-uS)
1113 _start: function() {
1114 // Start logging
1115 this._initLoggingPrefs();
1116
1117 // Clean up any extant updates
1118 this._postUpdateProcessing();
1119
1120 // Register a background update check timer
1121 var tm =
1122 Components.classes["@mozilla.org/updates/timer-manager;1"]
1123 .getService(Components.interfaces.nsIUpdateTimerManager);
1124 var interval = getPref("getIntPref", PREF_APP_UPDATE_INTERVAL, 86400);
1125 tm.registerTimer("background-update-timer", this, interval);
1126
1127 // Resume fetching...
1128 var um = Components.classes["@mozilla.org/updates/update-manager;1"]
1129 .getService(Components.interfaces.nsIUpdateManager);
1130 var activeUpdate = um.activeUpdate;
1131 if (activeUpdate) {
1132 var status = this.downloadUpdate(activeUpdate, true);
1133 if (status == STATE_NONE)
1134 cleanupActiveUpdate();
1135 }
1136 },
1137
1138 /**
1139 * Perform post-processing on updates lingering in the updates directory
1140 * from a previous browser session - either report install failures (and
1141 * optionally attempt to fetch a different version if appropriate) or
1142 * notify the user of install success.
1143 */
_postUpdateProcessing
Called: nsUpdateService.js:LOG (4 calls, 54 v-uS)
Object:create (2 calls, 644 v-uS)
Object:exists (2 calls, 64 v-uS)
Object:remove (2 calls, 392 v-uS)
nsUpdateService.js:getUpdatesDir (2 calls, 745 v-uS)
Object:append (1 calls, 67 v-uS)
Object:clone (1 calls, 24 v-uS)
nsUpdateService.js:getPref (1 calls, 46 v-uS)
nsUpdateService.js:getUpdateFile (1 calls, 317 v-uS)
nsUpdateService.js:readStatusFile (1 calls, 1500 v-uS)
Called By: nsUpdateService.js:_start (1 calls, 6147 v-uS)
1144 _postUpdateProcessing: function() {
1145 // Detect installation failures and notify
1146
1147 // Bail out if we don't have appropriate permissions
1148 if (!this.canUpdate)
1149 return;
1150
1151 var status = readStatusFile(getUpdatesDir());
1152
1153 // Make sure to cleanup after an update that failed for an unknown reason
1154 if (status == "null")
1155 status = null;
1156
1157 var updRootKey = null;
1158 //@line 1171 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
1159
1160 if (status == STATE_DOWNLOADING) {
1161 LOG("UpdateService", "_postUpdateProcessing: Downloading patch, resuming...");
1162 }
1163 else if (status != null) {
1164 // null status means the update.status file is not present, because either:
1165 // 1) no update was performed, and so there's no UI to show
1166 // 2) an update was attempted but failed during checking, transfer or
1167 // verification, and was cleaned up at that point, and UI notifying of
1168 // that error was shown at that stage.
1169 var um =
1170 Components.classes["@mozilla.org/updates/update-manager;1"].
1171 getService(Components.interfaces.nsIUpdateManager);
1172 var prompter =
1173 Components.classes["@mozilla.org/updates/update-prompt;1"].
1174 createInstance(Components.interfaces.nsIUpdatePrompt);
1175
1176 var shouldCleanup = true;
1177 var update = um.activeUpdate;
1178 if (!update) {
1179 update = new Update(null);
1180 }
1181 update.state = status;
1182 var sbs =
1183 Components.classes["@mozilla.org/intl/stringbundle;1"].
1184 getService(Components.interfaces.nsIStringBundleService);
1185 var bundle = sbs.createBundle(URI_UPDATES_PROPERTIES);
1186 if (status == STATE_SUCCEEDED) {
1187 update.statusText = bundle.GetStringFromName("installSuccess");
1188
1189 // Dig through the update history to find the patch that was just
1190 // installed and update its metadata.
1191 for (var i = 0; i < um.updateCount; ++i) {
1192 var umUpdate = um.getUpdateAt(i);
1193 if (umUpdate && umUpdate.version == update.version &&
1194 umUpdate.buildID == update.buildID) {
1195 umUpdate.statusText = update.statusText;
1196 break;
1197 }
1198 }
1199
1200 LOG("UpdateService", "_postUpdateProcessing: Install Succeeded, Showing UI");
1201 prompter.showUpdateInstalled(update);
1202 //@line 1218 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
1203 // Perform platform-specific post-update processing.
1204 if (POST_UPDATE_CONTRACTID in Components.classes) {
1205 Components.classes[POST_UPDATE_CONTRACTID].
1206 createInstance(Components.interfaces.nsIRunnable).run();
1207 }
1208 //@line 1224 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
1209
1210 // Done with this update. Clean it up.
1211 cleanupActiveUpdate(updRootKey);
1212 }
1213 else {
1214 // If we hit an error, then the error code will be included in the
1215 // status string following a colon. If we had an I/O error, then we
1216 // assume that the patch is not invalid, and we restage the patch so
1217 // that it can be attempted again the next time we restart.
1218 var ary = status.split(": ");
1219 update.state = ary[0];
1220 if (update.state == STATE_FAILED && ary[1]) {
1221 update.errorCode = ary[1];
1222 if (update.errorCode == WRITE_ERROR) {
1223 prompter.showUpdateError(update);
1224 writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
1225 return;
1226 }
1227 }
1228
1229 // Something went wrong with the patch application process.
1230 cleanupActiveUpdate();
1231
1232 update.statusText = bundle.GetStringFromName("patchApplyFailure");
1233 var oldType = update.selectedPatch ? update.selectedPatch.type
1234 : "complete";
1235 if (update.selectedPatch && oldType == "partial") {
1236 // Partial patch application failed, try downloading the complete
1237 // update in the background instead.
1238 LOG("UpdateService", "_postUpdateProcessing: Install of Partial Patch " +
1239 "failed, downloading Complete Patch and maybe showing UI");
1240 var status = this.downloadUpdate(update, true);
1241 if (status == STATE_NONE)
1242 cleanupActiveUpdate();
1243 }
1244 else {
1245 LOG("UpdateService", "_postUpdateProcessing: Install of Complete or " +
1246 "only patch failed. Showing error.");
1247 }
1248 update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
1249 update.setProperty("patchingFailed", oldType);
1250 prompter.showUpdateError(update);
1251 }
1252 }
1253 else {
1254 LOG("UpdateService", "_postUpdateProcessing: No Status, No Update");
1255 }
1256 },
1257
1258 /**
1259 * Initialize Logging preferences, formatted like so:
1260 * app.update.log.<moduleName> = <true|false>
1261 */
_initLoggingPrefs
Called: Object:getBranch (1 calls, 41 v-uS)
Object:getChildList (1 calls, 305 v-uS)
Object:getService (1 calls, 30 v-uS)
Called By: nsUpdateService.js:_start (1 calls, 472 v-uS)
1262 _initLoggingPrefs: function() {
1263 try {
1264 var ps = Components.classes["@mozilla.org/preferences-service;1"]
1265 .getService(Components.interfaces.nsIPrefService);
1266 var logBranch = ps.getBranch(PREF_APP_UPDATE_LOG_BRANCH);
1267 var modules = logBranch.getChildList("", { value: 0 });
1268
1269 for (var i = 0; i < modules.length; ++i) {
1270 if (logBranch.prefHasUserValue(modules[i]))
1271 gLogEnabled[modules[i]] = logBranch.getBoolPref(modules[i]);
1272 }
1273 }
1274 catch (e) {
1275 }
1276 },
1277
1278 /**
1279 * Notified when a timer fires
1280 * @param timer
1281 * The timer that fired
1282 */
notify
1283 notify: function(timer) {
1284 // If a download is in progress, then do nothing.
1285 if (this.isDownloading || this._downloader && this._downloader.patchIsStaged)
1286 return;
1287
1288 var self = this;
1289 var listener = {
1290 /**
1291 * See nsIUpdateService.idl
1292 */
onProgress
1293 onProgress: function(request, position, totalSize) {
1294 },
1295
1296 /**
1297 * See nsIUpdateService.idl
1298 */
onCheckComplete
1299 onCheckComplete: function(request, updates, updateCount) {
1300 self._selectAndInstallUpdate(updates);
1301 },
1302
1303 /**
1304 * See nsIUpdateService.idl
1305 */
onError
1306 onError: function(request, update) {
1307 LOG("Checker", "Error during background update: " + update.statusText);
1308 },
1309 }
1310 this.backgroundChecker.checkForUpdates(listener, false);
1311 },
1312
1313 /**
1314 * Determine whether or not an update requires user confirmation before it
1315 * can be installed.
1316 * @param update
1317 * The update to be installed
1318 * @returns true if a prompt UI should be shown asking the user if they want
1319 * to install the update, false if the update should just be
1320 * silently downloaded and installed.
1321 */
_shouldPrompt
1322 _shouldPrompt: function(update) {
1323 // There are two possible outcomes here:
1324 // 1. download and install the update automatically
1325 // 2. alert the user about the presence of an update before doing anything
1326 //
1327 // The outcome we follow is determined as follows:
1328 //
1329 // Note: all Major updates require notification and confirmation
1330 //
1331 // Update Type Mode Incompatible Outcome
1332 // Major 0 Yes or No Notify and Confirm
1333 // Major 1 No Notify and Confirm
1334 // Major 1 Yes Notify and Confirm
1335 // Major 2 Yes or No Notify and Confirm
1336 // Minor 0 Yes or No Auto Install
1337 // Minor 1 No Auto Install
1338 // Minor 1 Yes Notify and Confirm
1339 // Minor 2 No Auto Install
1340 // Minor 2 Yes Notify and Confirm
1341 //
1342 // In addition, if there is a license associated with an update, regardless
1343 // of type it must be agreed to.
1344 //
1345 // If app.update.enabled is set to false, an update check is not performed
1346 // at all, and so none of the decision making above is entered into.
1347 //
1348 if (update.type == "major") {
1349 LOG("Checker", "_shouldPrompt: Prompting because it is a major update");
1350 return true;
1351 }
1352
1353 update.QueryInterface(Components.interfaces.nsIPropertyBag);
1354 try {
1355 var licenseAccepted = update.getProperty("licenseAccepted") == "true";
1356 }
1357 catch (e) {
1358 licenseAccepted = false;
1359 }
1360
1361 var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
1362 if (!updateEnabled) {
1363 LOG("Checker", "_shouldPrompt: Not prompting because update is " +
1364 "disabled");
1365 return false;
1366 }
1367
1368 // User has turned off automatic download and install
1369 var autoEnabled = getPref("getBoolPref", PREF_APP_UPDATE_AUTO, true);
1370 if (!autoEnabled) {
1371 LOG("Checker", "_shouldPrompt: Prompting because auto install is disabled");
1372 return true;
1373 }
1374
1375 switch (getPref("getIntPref", PREF_APP_UPDATE_MODE, 1)) {
1376 case 1:
1377 // Mode 1 is do not prompt only if there are no incompatibilities
1378 // releases
1379 LOG("Checker", "_shouldPrompt: Prompting if there are incompatibilities");
1380 return !isCompatible(update);
1381 case 2:
1382 // Mode 2 is do not prompt only if there are no incompatibilities
1383 LOG("Checker", "_shouldPrompt: Prompting if there are incompatibilities");
1384 return !isCompatible(update);
1385 }
1386 // Mode 0 is do not prompt regardless of incompatibilities
1387 LOG("Checker", "_shouldPrompt: Not prompting the user - they choose to " +
1388 "ignore incompatibilities");
1389 return false;
1390 },
1391
1392 /**
1393 * Determine which of the specified updates should be installed.
1394 * @param updates
1395 * An array of available updates
1396 */
selectUpdate
1397 selectUpdate: function(updates) {
1398 if (updates.length == 0)
1399 return null;
1400
1401 // Choose the newest of the available minor and major updates.
1402 var majorUpdate = null, minorUpdate = null;
1403 var newestMinor = updates[0], newestMajor = updates[0];
1404
1405 var vc = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
1406 .getService(Components.interfaces.nsIVersionComparator);
1407 for (var i = 0; i < updates.length; ++i) {
1408 if (updates[i].type == "major" &&
1409 vc.compare(newestMajor.version, updates[i].version) <= 0)
1410 majorUpdate = newestMajor = updates[i];
1411 if (updates[i].type == "minor" &&
1412 vc.compare(newestMinor.version, updates[i].version) <= 0)
1413 minorUpdate = newestMinor = updates[i];
1414 }
1415
1416 // IMPORTANT
1417 // If there's a minor update, always try and fetch that one first,
1418 // otherwise use the newest major update.
1419 // selectUpdate() only returns one update.
1420 // if major were to trump minor, and we said "never" to the major
1421 // we'd never get the minor update, since selectUpdate()
1422 // would return the major update that the user said "never" to
1423 // (shadowing the important minor update with security fixes)
1424 return minorUpdate || majorUpdate;
1425 },
1426
1427 /**
1428 * Determine which of the specified updates should be installed and
1429 * begin the download/installation process, optionally prompting the
1430 * user for permission if required.
1431 * @param updates
1432 * An array of available updates
1433 */
_selectAndInstallUpdate
1434 _selectAndInstallUpdate: function(updates) {
1435 // Don't prompt if there's an active update - the user is already
1436 // aware and is downloading, or performed some user action to prevent
1437 // notification.
1438 var um = Components.classes["@mozilla.org/updates/update-manager;1"]
1439 .getService(Components.interfaces.nsIUpdateManager);
1440 if (um.activeUpdate)
1441 return;
1442
1443 var update = this.selectUpdate(updates, updates.length);
1444 if (!update)
1445 return;
1446
1447 // check if the user said "never" to this version
1448 // this check is done here, and not in selectUpdate() so that
1449 // the user can get an upgrade they said "never" to if they
1450 // manually do "Check for Updates..."
1451 // note, selectUpdate() only returns one update.
1452 // but in selectUpdate(), minor updates trump major updates
1453 // if major trumps minor, and we said "never" to the major
1454 // we'd never see the minor update.
1455 //
1456 // note, the never decision should only apply to major updates
1457 // see bug #350636 for a scenario where this could potentially
1458 // be an issue
1459 //
1460 // fix for bug #359093
1461 // version might one day come back from AUS as an
1462 // arbitrary (and possibly non ascii) string, so we need to encode it
1463 var neverPrefName = PREF_UPDATE_NEVER_BRANCH + encodeURIComponent(update.version);
1464 var never = getPref("getBoolPref", neverPrefName, false);
1465 if (never && update.type == "major")
1466 return;
1467
1468 if (this._shouldPrompt(update))
1469 showPromptIfNoIncompatibilities(update);
1470 else {
1471 LOG("UpdateService", "_selectAndInstallUpdate: No need to show prompt, just download update");
1472 var status = this.downloadUpdate(update, true);
1473 if (status == STATE_NONE)
1474 cleanupActiveUpdate();
1475 }
1476 },
1477
1478 /**
1479 * The Checker used for background update checks.
1480 */
1481 _backgroundChecker: null,
1482
1483 /**
1484 * See nsIUpdateService.idl
1485 */
get_backgroundChecker
1486 get backgroundChecker() {
1487 if (!this._backgroundChecker)
1488 this._backgroundChecker = new Checker();
1489 return this._backgroundChecker;
1490 },
1491
1492 /**
1493 * See nsIUpdateService.idl
1494 */
get_canUpdate
1495 get canUpdate() {
1496 try {
1497 var appDirFile = getUpdateFile([FILE_PERMS_TEST]);
1498 LOG("UpdateService", "canUpdate? testing " + appDirFile.path);
1499 if (!appDirFile.exists()) {
1500 appDirFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
1501 appDirFile.remove(false);
1502 }
1503 var updateDir = getUpdatesDir();
1504 var upDirFile = updateDir.clone();
1505 upDirFile.append(FILE_PERMS_TEST);
1506 LOG("UpdateService", "canUpdate? testing " + upDirFile.path);
1507 if (!upDirFile.exists()) {
1508 upDirFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
1509 upDirFile.remove(false);
1510 }
1511 //@line 1605 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
1512 }
1513 catch (e) {
1514 LOG("UpdateService", "can't update, no privileges: " + e);
1515 // No write privileges to install directory
1516 return false;
1517 }
1518 // If the administrator has locked the app update functionality
1519 // OFF - this is not just a user setting, so disable the manual
1520 // UI too.
1521 var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
1522 if (!enabled && gPref.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
1523 LOG("UpdateService", "can't update, disabled by pref");
1524 return false;
1525 }
1526
1527 // If we don't know the binary platform we're updating, we can't update.
1528 if (!gABI) {
1529 LOG("UpdateService", "can't update, unknown ABI");
1530 return false;
1531 }
1532
1533 // If we don't know the OS version we're updating, we can't update.
1534 if (!gOSVersion) {
1535 LOG("UpdateService", "can't update, unknown OS version");
1536 return false;
1537 }
1538
1539 LOG("UpdateService", "can update");
1540 return true;
1541 },
1542
1543 /**
1544 * See nsIUpdateService.idl
1545 */
addDownloadListener
1546 addDownloadListener: function(listener) {
1547 if (!this._downloader) {
1548 LOG("UpdateService", "addDownloadListener: no downloader!\n");
1549 return;
1550 }
1551 this._downloader.addDownloadListener(listener);
1552 },
1553
1554 /**
1555 * See nsIUpdateService.idl
1556 */
removeDownloadListener
1557 removeDownloadListener: function(listener) {
1558 if (!this._downloader) {
1559 LOG("UpdateService", "removeDownloadListener: no downloader!\n");
1560 return;
1561 }
1562 this._downloader.removeDownloadListener(listener);
1563 },
1564
1565 /**
1566 * See nsIUpdateService.idl
1567 */
downloadUpdate
1568 downloadUpdate: function(update, background) {
1569 if (!update)
1570 throw Components.results.NS_ERROR_NULL_POINTER;
1571 if (this.isDownloading) {
1572 if (update.isCompleteUpdate == this._downloader.isCompleteUpdate &&
1573 background == this._downloader.background) {
1574 LOG("UpdateService", "no support for downloading more than one update at a time");
1575 return readStatusFile(getUpdatesDir());
1576 }
1577 this._downloader.cancel();
1578 }
1579 this._downloader = new Downloader(background);
1580 return this._downloader.downloadUpdate(update);
1581 },
1582
1583 /**
1584 * See nsIUpdateService.idl
1585 */
pauseDownload
1586 pauseDownload: function() {
1587 if (this.isDownloading)
1588 this._downloader.cancel();
1589 },
1590
1591 /**
1592 * See nsIUpdateService.idl
1593 */
get_isDownloading
1594 get isDownloading() {
1595 return this._downloader && this._downloader.isBusy;
1596 },
1597
1598 /**
1599 * See nsISupports.idl
1600 */
QueryInterface
Called: Object:equals (4 calls, 39 v-uS)
1601 QueryInterface: function(iid) {
1602 if (!iid.equals(Components.interfaces.nsIApplicationUpdateService) &&
1603 !iid.equals(Components.interfaces.nsITimerCallback) &&
1604 !iid.equals(Components.interfaces.nsIObserver) &&
1605 !iid.equals(Components.interfaces.nsISupports))
1606 throw Components.results.NS_ERROR_NO_INTERFACE;
1607 return this;
1608 }
1609 };
1610
1611 /**
1612 * A service to manage active and past updates.
1613 * @constructor
1614 */
UpdateManager
1615 function UpdateManager() {
1616 // Ensure the Active Update file is loaded
1617 var updates = this._loadXMLFileIntoArray(getUpdateFile([FILE_UPDATE_ACTIVE]));
1618 if (updates.length > 0)
1619 this._activeUpdate = updates[0];
1620 }
1621 UpdateManager.prototype = {
1622 /**
1623 * All previously downloaded and installed updates, as an array of nsIUpdate
1624 * objects.
1625 */
1626 _updates: null,
1627
1628 /**
1629 * The current actively downloading/installing update, as a nsIUpdate object.
1630 */
1631 _activeUpdate: null,
1632
1633 /**
1634 * Loads an updates.xml formatted file into an array of nsIUpdate items.
1635 * @param file
1636 * A nsIFile for the updates.xml file
1637 * @returns The array of nsIUpdate items held in the file.
1638 */
_loadXMLFileIntoArray
Called: Object:exists (1 calls, 24 v-uS)
nsUpdateService.js:LOG (1 calls, 9 v-uS)
Called By: Object:getService (1 calls, 67 v-uS)
1639 _loadXMLFileIntoArray: function(file) {
1640 if (!file.exists()) {
1641 LOG("UpdateManager", "_loadXMLFileIntoArray: XML File does not exist");
1642 return [];
1643 }
1644
1645 var result = [];
1646 var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
1647 .createInstance(Components.interfaces.nsIFileInputStream);
1648 fileStream.init(file, MODE_RDONLY, PERMS_FILE, 0);
1649 try {
1650 var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
1651 .createInstance(Components.interfaces.nsIDOMParser);
1652 var doc = parser.parseFromStream(fileStream, "UTF-8", fileStream.available(), "text/xml");
1653
1654 var updateCount = doc.documentElement.childNodes.length;
1655 for (var i = 0; i < updateCount; ++i) {
1656 var updateElement = doc.documentElement.childNodes.item(i);
1657 if (updateElement.nodeType != Node.ELEMENT_NODE ||
1658 updateElement.localName != "update")
1659 continue;
1660
1661 updateElement.QueryInterface(Components.interfaces.nsIDOMElement);
1662 try {
1663 var update = new Update(updateElement);
1664 } catch (e) {
1665 LOG("UpdateManager", "_loadXMLFileIntoArray: invalid update");
1666 continue;
1667 }
1668 result.push(new Update(updateElement));
1669 }
1670 }
1671 catch (e) {
1672 LOG("UpdateManager", "_loadXMLFileIntoArray: Error constructing update list " +
1673 e);
1674 }
1675 fileStream.close();
1676 return result;
1677 },
1678
1679 /**
1680 * Load the update manager, initializing state from state files.
1681 */
_ensureUpdates
1682 _ensureUpdates: function() {
1683 if (!this._updates) {
1684 this._updates = this._loadXMLFileIntoArray(getUpdateFile(
1685 [FILE_UPDATES_DB]));
1686
1687 // Make sure that any active update is part of our updates list
1688 var active = this.activeUpdate;
1689 if (active)
1690 this._addUpdate(active);
1691 }
1692 },
1693
1694 /**
1695 * See nsIUpdateService.idl
1696 */
getUpdateAt
1697 getUpdateAt: function(index) {
1698 this._ensureUpdates();
1699 return this._updates[index];
1700 },
1701
1702 /**
1703 * See nsIUpdateService.idl
1704 */
get_updateCount
1705 get updateCount() {
1706 this._ensureUpdates();
1707 return this._updates.length;
1708 },
1709
1710 /**
1711 * See nsIUpdateService.idl
1712 */
get_activeUpdate
1713 get activeUpdate() {
1714 if (this._activeUpdate &&
1715 this._activeUpdate.channel != getUpdateChannel()) {
1716 // User switched channels, clear out any old active updates and remove
1717 // partial downloads
1718 this._activeUpdate = null;
1719
1720 // Destroy the updates directory, since we're done with it.
1721 cleanUpUpdatesDir();
1722 }
1723 return this._activeUpdate;
1724 },
set_activeUpdate
1725 set activeUpdate(activeUpdate) {
1726 this._addUpdate(activeUpdate);
1727 this._activeUpdate = activeUpdate;
1728 if (!activeUpdate) {
1729 // If |activeUpdate| is null, we have updated both lists - the active list
1730 // and the history list, so we want to write both files.
1731 this.saveUpdates();
1732 }
1733 else
1734 this._writeUpdatesToXMLFile([this._activeUpdate],
1735 getUpdateFile([FILE_UPDATE_ACTIVE]));
1736 return activeUpdate;
1737 },
1738
1739 /**
1740 * Add an update to the Updates list. If the item already exists in the list,
1741 * replace the existing value with the new value.
1742 * @param update
1743 * The nsIUpdate object to add.
1744 */
_addUpdate
1745 _addUpdate: function(update) {
1746 if (!update)
1747 return;
1748 this._ensureUpdates();
1749 if (this._updates) {
1750 for (var i = 0; i < this._updates.length; ++i) {
1751 if (this._updates[i] &&
1752 this._updates[i].version == update.version &&
1753 this._updates[i].buildID == update.buildID) {
1754 // Replace the existing entry with the new value, updating
1755 // all metadata.
1756 this._updates[i] = update;
1757 return;
1758 }
1759 }
1760 }
1761 // Otherwise add it to the front of the list.
1762 if (update)
1763 this._updates = [update].concat(this._updates);
1764 },
1765
1766 /**
1767 * Serializes an array of updates to an XML file
1768 * @param updates
1769 * An array of nsIUpdate objects
1770 * @param file
1771 * The nsIFile object to serialize to
1772 */
_writeUpdatesToXMLFile
1773 _writeUpdatesToXMLFile: function(updates, file) {
1774 var fos = Components.classes["@mozilla.org/network/safe-file-output-stream;1"]
1775 .createInstance(Components.interfaces.nsIFileOutputStream);
1776 var modeFlags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
1777 if (!file.exists())
1778 file.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
1779 fos.init(file, modeFlags, PERMS_FILE, 0);
1780
1781 try {
1782 var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
1783 .createInstance(Components.interfaces.nsIDOMParser);
1784 const EMPTY_UPDATES_DOCUMENT = "<?xml version=\"1.0\"?><updates xmlns=\"http://www.mozilla.org/2005/app-update\"></updates>";
1785 var doc = parser.parseFromString(EMPTY_UPDATES_DOCUMENT, "text/xml");
1786
1787 for (var i = 0; i < updates.length; ++i) {
1788 if (updates[i])
1789 doc.documentElement.appendChild(updates[i].serialize(doc));
1790 }
1791
1792 var serializer = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"]
1793 .createInstance(Components.interfaces.nsIDOMSerializer);
1794 serializer.serializeToStream(doc.documentElement, fos, null);
1795 }
1796 catch (e) {
1797 }
1798
1799 closeSafeOutputStream(fos);
1800 },
1801
1802 /**
1803 * See nsIUpdateService.idl
1804 */
saveUpdates
1805 saveUpdates: function() {
1806 this._writeUpdatesToXMLFile([this._activeUpdate],
1807 getUpdateFile([FILE_UPDATE_ACTIVE]));
1808 if (this._updates) {
1809 this._writeUpdatesToXMLFile(this._updates.slice(0, 10),
1810 getUpdateFile([FILE_UPDATES_DB]));
1811 }
1812 },
1813
1814 /**
1815 * See nsISupports.idl
1816 */
QueryInterface
Called: Object:equals (1 calls, 10 v-uS)
Called By: Object:getService (1 calls, 38 v-uS)
1817 QueryInterface: function(iid) {
1818 if (!iid.equals(Components.interfaces.nsIUpdateManager) &&
1819 !iid.equals(Components.interfaces.nsISupports))
1820 throw Components.results.NS_ERROR_NO_INTERFACE;
1821 return this;
1822 }
1823 };
1824
1825
1826 /**
1827 * Checker
1828 * Checks for new Updates
1829 * @constructor
1830 */
Checker
1831 function Checker() {
1832 }
1833 Checker.prototype = {
1834 /**
1835 * The XMLHttpRequest object that performs the connection.
1836 */
1837 _request : null,
1838
1839 /**
1840 * The nsIUpdateCheckListener callback
1841 */
1842 _callback : null,
1843
1844 /**
1845 * The URL of the update service XML file to connect to that contains details
1846 * about available updates.
1847 */
getUpdateURL
1848 getUpdateURL: function(force) {
1849 this._forced = force;
1850
1851 var defaults =
1852 gPref.QueryInterface(Components.interfaces.nsIPrefService).
1853 getDefaultBranch(null);
1854
1855 // Use the override URL if specified.
1856 var url = getPref("getCharPref", PREF_APP_UPDATE_URL_OVERRIDE, null);
1857
1858 // Otherwise, construct the update URL from component parts.
1859 if (!url) {
1860 try {
1861 url = defaults.getCharPref(PREF_APP_UPDATE_URL);
1862 } catch (e) {
1863 }
1864 }
1865
1866 if (!url || url == "") {
1867 LOG("Checker", "Update URL not defined");
1868 return null;
1869 }
1870
1871 url = url.replace(/%PRODUCT%/g, gApp.name);
1872 url = url.replace(/%VERSION%/g, gApp.version);
1873 url = url.replace(/%BUILD_ID%/g, gApp.appBuildID);
1874 url = url.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI);
1875 url = url.replace(/%OS_VERSION%/g, gOSVersion);
1876 url = url.replace(/%LOCALE%/g, getLocale());
1877 url = url.replace(/%CHANNEL%/g, getUpdateChannel());
1878 url = url.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion);
1879 url = url.replace(/%DISTRIBUTION%/g,
1880 getDistributionPrefValue(PREF_APP_DISTRIBUTION));
1881 url = url.replace(/%DISTRIBUTION_VERSION%/g,
1882 getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
1883 url = url.replace(/\+/g, "%2B");
1884
1885 if (force)
1886 url += "?force=1"
1887
1888 LOG("Checker", "update url: " + url);
1889 return url;
1890 },
1891
1892 /**
1893 * See nsIUpdateService.idl
1894 */
checkForUpdates
1895 checkForUpdates: function(listener, force) {
1896 if (!listener)
1897 throw Components.results.NS_ERROR_NULL_POINTER;
1898
1899 if (!this.getUpdateURL(force) || (!this.enabled && !force))
1900 return;
1901
1902 this._request =
1903 Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
1904 createInstance(Components.interfaces.nsIXMLHttpRequest);
1905 this._request.open("GET", this.getUpdateURL(force), true);
1906 this._request.channel.notificationCallbacks = new BadCertHandler();
1907 this._request.overrideMimeType("text/xml");
1908 this._request.setRequestHeader("Cache-Control", "no-cache");
1909
1910 var self = this;
anon:1911:32
1911 this._request.onerror = function(event) { self.onError(event); };
anon:1912:32
1912 this._request.onload = function(event) { self.onLoad(event); };
anon:1913:32
1913 this._request.onprogress = function(event) { self.onProgress(event); };
1914
1915 LOG("Checker", "checkForUpdates: sending request to " + this.getUpdateURL(force));
1916 this._request.send(null);
1917
1918 this._callback = listener;
1919 },
1920
1921 /**
1922 * When progress associated with the XMLHttpRequest is received.
1923 * @param event
1924 * The nsIDOMLSProgressEvent for the load.
1925 */
onProgress
1926 onProgress: function(event) {
1927 LOG("Checker", "onProgress: " + event.position + "/" + event.totalSize);
1928 this._callback.onProgress(event.target, event.position, event.totalSize);
1929 },
1930
1931 /**
1932 * Returns an array of nsIUpdate objects discovered by the update check.
1933 */
get__updates
1934 get _updates() {
1935 var updatesElement = this._request.responseXML.documentElement;
1936 if (!updatesElement) {
1937 LOG("Checker", "get_updates: empty updates document?!");
1938 return [];
1939 }
1940
1941 if (updatesElement.nodeName != "updates") {
1942 LOG("Checker", "get_updates: unexpected node name!");
1943 throw "";
1944 }
1945
1946 var updates = [];
1947 for (var i = 0; i < updatesElement.childNodes.length; ++i) {
1948 var updateElement = updatesElement.childNodes.item(i);
1949 if (updateElement.nodeType != Node.ELEMENT_NODE ||
1950 updateElement.localName != "update")
1951 continue;
1952
1953 updateElement.QueryInterface(Components.interfaces.nsIDOMElement);
1954 try {
1955 var update = new Update(updateElement);
1956 } catch (e) {
1957 LOG("Checker", "Invalid <update/>, ignoring...");
1958 continue;
1959 }
1960 update.serviceURL = this.getUpdateURL(this._forced);
1961 update.channel = getUpdateChannel();
1962 updates.push(update);
1963 }
1964
1965 return updates;
1966 },
1967
1968 /**
1969 * The XMLHttpRequest succeeded and the document was loaded.
1970 * @param event
1971 * The nsIDOMLSEvent for the load
1972 */
onLoad
1973 onLoad: function(event) {
1974 LOG("Checker", "onLoad: request completed downloading document");
1975
1976 try {
1977 checkCert(this._request.channel);
1978
1979 // Analyze the resulting DOM and determine the set of updates to install
1980 var updates = this._updates;
1981
1982 LOG("Checker", "Updates available: " + updates.length);
1983
1984 // ... and tell the Update Service about what we discovered.
1985 this._callback.onCheckComplete(event.target, updates, updates.length);
1986 }
1987 catch (e) {
1988 LOG("Checker", "There was a problem with the update service URL specified, " +
1989 "either the XML file was malformed or it does not exist at the location " +
1990 "specified. Exception: " + e);
1991 var update = new Update(null);
1992 update.statusText = getStatusTextFromCode(404, 404);
1993 this._callback.onError(event.target, update);
1994 }
1995
1996 this._request = null;
1997 },
1998
1999 /**
2000 * There was an error of some kind during the XMLHttpRequest
2001 * @param event
2002 * The nsIDOMLSEvent for the load
2003 */
onError
2004 onError: function(event) {
2005 LOG("Checker", "onError: error during load");
2006
2007 var request = event.target;
2008 try {
2009 var status = request.status;
2010 }
2011 catch (e) {
2012 var req = request.channel.QueryInterface(Components.interfaces.nsIRequest);
2013 status = req.status;
2014 }
2015
2016 // If we can't find an error string specific to this status code,
2017 // just use the 200 message from above, which means everything
2018 // "looks" fine but there was probably an XML error or a bogus file.
2019 var update = new Update(null);
2020 update.statusText = getStatusTextFromCode(status, 200);
2021 this._callback.onError(request, update);
2022
2023 this._request = null;
2024 },
2025
2026 /**
2027 * Whether or not we are allowed to do update checking.
2028 */
2029 _enabled: true,
2030
2031 /**
2032 * See nsIUpdateService.idl
2033 */
get_enabled
2034 get enabled() {
2035 var aus =
2036 Components.classes["@mozilla.org/updates/update-service;1"].
2037 getService(Components.interfaces.nsIApplicationUpdateService);
2038 var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) &&
2039 aus.canUpdate && this._enabled;
2040 return enabled;
2041 },
2042
2043 /**
2044 * See nsIUpdateService.idl
2045 */
stopChecking
2046 stopChecking: function(duration) {
2047 // Always stop the current check
2048 if (this._request)
2049 this._request.abort();
2050
2051 const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker;
2052 switch (duration) {
2053 case nsIUpdateChecker.CURRENT_SESSION:
2054 this._enabled = false;
2055 break;
2056 case nsIUpdateChecker.ANY_CHECKS:
2057 this._enabled = false;
2058 gPref.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled);
2059 break;
2060 }
2061 },
2062
2063 /**
2064 * See nsISupports.idl
2065 */
QueryInterface
2066 QueryInterface: function(iid) {
2067 if (!iid.equals(Components.interfaces.nsIUpdateChecker) &&
2068 !iid.equals(Components.interfaces.nsISupports))
2069 throw Components.results.NS_ERROR_NO_INTERFACE;
2070 return this;
2071 }
2072 };
2073
2074 /**
2075 * Manages the download of updates
2076 * @param background
2077 * Whether or not this downloader is operating in background
2078 * update mode.
2079 * @constructor
2080 */
Downloader
2081 function Downloader(background) {
2082 this.background = background;
2083 }
2084 Downloader.prototype = {
2085 /**
2086 * The nsIUpdatePatch that we are downloading
2087 */
2088 _patch: null,
2089
2090 /**
2091 * The nsIUpdate that we are downloading
2092 */
2093 _update: null,
2094
2095 /**
2096 * The nsIIncrementalDownload object handling the download
2097 */
2098 _request: null,
2099
2100 /**
2101 * Whether or not the update being downloaded is a complete replacement of
2102 * the user's existing installation or a patch representing the difference
2103 * between the new version and the previous version.
2104 */
2105 isCompleteUpdate: null,
2106
2107 /**
2108 * Cancels the active download.
2109 */
cancel
2110 cancel: function() {
2111 if (this._request &&
2112 this._request instanceof Components.interfaces.nsIRequest) {
2113 const NS_BINDING_ABORTED = 0x804b0002;
2114 this._request.cancel(NS_BINDING_ABORTED);
2115 }
2116 },
2117
2118 /**
2119 * Whether or not a patch has been downloaded and staged for installation.
2120 */
get_patchIsStaged
2121 get patchIsStaged() {
2122 return readStatusFile(getUpdatesDir()) == STATE_PENDING;
2123 },
2124
2125 /**
2126 * Verify the downloaded file. We assume that the download is complete at
2127 * this point.
2128 */
_verifyDownload
2129 _verifyDownload: function() {
2130 if (!this._request)
2131 return false;
2132
2133 var destination = this._request.destination;
2134
2135 // Ensure that the file size matches the expected file size.
2136 if (destination.fileSize != this._patch.size)
2137 return false;
2138
2139 var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
2140 createInstance(nsIFileInputStream);
2141 fileStream.init(destination, MODE_RDONLY, PERMS_FILE, 0);
2142
2143 try {
2144 var hash = Components.classes["@mozilla.org/security/hash;1"].
2145 createInstance(nsICryptoHash);
2146 var hashFunction = nsICryptoHash[this._patch.hashFunction.toUpperCase()];
2147 if (hashFunction == undefined)
2148 throw Components.results.NS_ERROR_UNEXPECTED;
2149 hash.init(hashFunction);
2150 hash.updateFromStream(fileStream, -1);
2151 // NOTE: For now, we assume that the format of _patch.hashValue is hex
2152 // encoded binary (such as what is typically output by programs like
2153 // sha1sum). In the future, this may change to base64 depending on how
2154 // we choose to compute these hashes.
2155 digest = binaryToHex(hash.finish(false));
2156 } catch (e) {
2157 LOG("Downloader", "failed to compute hash of downloaded update archive");
2158 digest = "";
2159 }
2160
2161 fileStream.close();
2162
2163 return digest == this._patch.hashValue.toLowerCase();
2164 },
2165
2166 /**
2167 * Select the patch to use given the current state of updateDir and the given
2168 * set of update patches.
2169 * @param update
2170 * A nsIUpdate object to select a patch from
2171 * @param updateDir
2172 * A nsIFile representing the update directory
2173 * @returns A nsIUpdatePatch object to download
2174 */
_selectPatch
2175 _selectPatch: function(update, updateDir) {
2176 // Given an update to download, we will always try to download the patch
2177 // for a partial update over the patch for a full update.
2178
2179 /**
2180 * Return the first UpdatePatch with the given type.
2181 * @param type
2182 * The type of the patch ("complete" or "partial")
2183 * @returns A nsIUpdatePatch object matching the type specified
2184 */
getPatchOfType
2185 function getPatchOfType(type) {
2186 for (var i = 0; i < update.patchCount; ++i) {
2187 var patch = update.getPatchAt(i);
2188 if (patch && patch.type == type)
2189 return patch;
2190 }
2191 return null;
2192 }
2193
2194 // Look to see if any of the patches in the Update object has been
2195 // pre-selected for download, otherwise we must figure out which one
2196 // to select ourselves.
2197 var selectedPatch = update.selectedPatch;
2198
2199 var state = readStatusFile(updateDir);
2200
2201 // If this is a patch that we know about, then select it. If it is a patch
2202 // that we do not know about, then remove it and use our default logic.
2203 var useComplete = false;
2204 if (selectedPatch) {
2205 LOG("Downloader", "found existing patch [state="+state+"]");
2206 switch (state) {
2207 case STATE_DOWNLOADING:
2208 LOG("Downloader", "resuming download");
2209 return selectedPatch;
2210 case STATE_PENDING:
2211 LOG("Downloader", "already downloaded and staged");
2212 return null;
2213 default:
2214 // Something went wrong when we tried to apply the previous patch.
2215 // Try the complete patch next time.
2216 if (update && selectedPatch.type == "partial") {
2217 useComplete = true;
2218 } else {
2219 // This is a pretty fatal error. Just bail.
2220 LOG("Downloader", "failed to apply complete patch!");
2221 writeStatusFile(updateDir, STATE_NONE);
2222 return null;
2223 }
2224 }
2225
2226 selectedPatch = null;
2227 }
2228
2229 // If we were not able to discover an update from a previous download, we
2230 // select the best patch from the given set.
2231 var partialPatch = getPatchOfType("partial");
2232 if (!useComplete)
2233 selectedPatch = partialPatch;
2234 if (!selectedPatch) {
2235 if (partialPatch)
2236 partialPatch.selected = false;
2237 selectedPatch = getPatchOfType("complete");
2238 }
2239
2240 // Restore the updateDir since we may have deleted it.
2241 updateDir = getUpdatesDir();
2242
2243 // if update only contains a partial patch, selectedPatch == null here if
2244 // the partial patch has been attempted and fails and we're trying to get a
2245 // complete patch
2246 if (selectedPatch)
2247 selectedPatch.selected = true;
2248
2249 update.isCompleteUpdate = useComplete;
2250
2251 // Reset the Active Update object on the Update Manager and flush the
2252 // Active Update DB.
2253 var um = Components.classes["@mozilla.org/updates/update-manager;1"]
2254 .getService(Components.interfaces.nsIUpdateManager);
2255 um.activeUpdate = update;
2256
2257 return selectedPatch;
2258 },
2259
2260 /**
2261 * Whether or not we are currently downloading something.
2262 */
get_isBusy
2263 get isBusy() {
2264 return this._request != null;
2265 },
2266
2267 /**
2268 * Download and stage the given update.
2269 * @param update
2270 * A nsIUpdate object to download a patch for. Cannot be null.
2271 */
downloadUpdate
2272 downloadUpdate: function(update) {
2273 if (!update)
2274 throw Components.results.NS_ERROR_NULL_POINTER;
2275
2276 var updateDir = getUpdatesDir();
2277
2278 this._update = update;
2279
2280 // This function may return null, which indicates that there are no patches
2281 // to download.
2282 this._patch = this._selectPatch(update, updateDir);
2283 if (!this._patch) {
2284 LOG("Downloader", "no patch to download");
2285 return readStatusFile(updateDir);
2286 }
2287 this.isCompleteUpdate = this._patch.type == "complete";
2288
2289 var patchFile = updateDir.clone();
2290 patchFile.append(FILE_UPDATE_ARCHIVE);
2291
2292 var ios = Components.classes["@mozilla.org/network/io-service;1"].
2293 getService(Components.interfaces.nsIIOService);
2294 var uri = ios.newURI(this._patch.URL, null, null);
2295
2296 this._request =
2297 Components.classes["@mozilla.org/network/incremental-download;1"].
2298 createInstance(nsIIncrementalDownload);
2299
2300 LOG("Downloader", "downloadUpdate: Downloading from " + uri.spec + " to " +
2301 patchFile.path);
2302
2303 var interval = this.background ? DOWNLOAD_BACKGROUND_INTERVAL
2304 : DOWNLOAD_FOREGROUND_INTERVAL;
2305 this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval);
2306 this._request.start(this, null);
2307
2308 writeStatusFile(updateDir, STATE_DOWNLOADING);
2309 this._patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
2310 this._patch.state = STATE_DOWNLOADING;
2311 var um = Components.classes["@mozilla.org/updates/update-manager;1"]
2312 .getService(Components.interfaces.nsIUpdateManager);
2313 um.saveUpdates();
2314 return STATE_DOWNLOADING;
2315 },
2316
2317 /**
2318 * An array of download listeners to notify when we receive
2319 * nsIRequestObserver or nsIProgressEventSink method calls.
2320 */
2321 _listeners: [],
2322
2323 /**
2324 * Adds a listener to the download process
2325 * @param listener
2326 * A download listener, implementing nsIRequestObserver and
2327 * nsIProgressEventSink
2328 */
addDownloadListener
2329 addDownloadListener: function(listener) {
2330 for (var i = 0; i < this._listeners.length; ++i) {
2331 if (this._listeners[i] == listener)
2332 return;
2333 }
2334 this._listeners.push(listener);
2335 },
2336
2337 /**
2338 * Removes a download listener
2339 * @param listener
2340 * The listener to remove.
2341 */
removeDownloadListener
2342 removeDownloadListener: function(listener) {
2343 for (var i = 0; i < this._listeners.length; ++i) {
2344 if (this._listeners[i] == listener) {
2345 this._listeners.splice(i, 1);
2346 return;
2347 }
2348 }
2349 },
2350
2351 /**
2352 * When the async request begins
2353 * @param request
2354 * The nsIRequest object for the transfer
2355 * @param context
2356 * Additional data
2357 */
onStartRequest
2358 onStartRequest: function(request, context) {
2359 request.QueryInterface(nsIIncrementalDownload);
2360 LOG("Downloader", "onStartRequest: " + request.URI.spec);
2361
2362 var listenerCount = this._listeners.length;
2363 for (var i = 0; i < listenerCount; ++i)
2364 this._listeners[i].onStartRequest(request, context);
2365 },
2366
2367 /**
2368 * When new data has been downloaded
2369 * @param request
2370 * The nsIRequest object for the transfer
2371 * @param context
2372 * Additional data
2373 * @param progress
2374 * The current number of bytes transferred
2375 * @param maxProgress
2376 * The total number of bytes that must be transferred
2377 */
onProgress
2378 onProgress: function(request, context, progress, maxProgress) {
2379 request.QueryInterface(nsIIncrementalDownload);
2380 LOG("Downloader.onProgress", "onProgress: " + request.URI.spec + ", " + progress + "/" + maxProgress);
2381
2382 var listenerCount = this._listeners.length;
2383 for (var i = 0; i < listenerCount; ++i) {
2384 var listener = this._listeners[i];
2385 if (listener instanceof Components.interfaces.nsIProgressEventSink)
2386 listener.onProgress(request, context, progress, maxProgress);
2387 }
2388 },
2389
2390 /**
2391 * When we have new status text
2392 * @param request
2393 * The nsIRequest object for the transfer
2394 * @param context
2395 * Additional data
2396 * @param status
2397 * A status code
2398 * @param statusText
2399 * Human readable version of |status|
2400 */
onStatus
2401 onStatus: function(request, context, status, statusText) {
2402 request.QueryInterface(nsIIncrementalDownload);
2403 LOG("Downloader", "onStatus: " + request.URI.spec + " status = " + status + ", text = " + statusText);
2404 var listenerCount = this._listeners.length;
2405 for (var i = 0; i < listenerCount; ++i) {
2406 var listener = this._listeners[i];
2407 if (listener instanceof Components.interfaces.nsIProgressEventSink)
2408 listener.onStatus(request, context, status, statusText);
2409 }
2410 },
2411
2412 /**
2413 * When data transfer ceases
2414 * @param request
2415 * The nsIRequest object for the transfer
2416 * @param context
2417 * Additional data
2418 * @param status
2419 * Status code containing the reason for the cessation.
2420 */
onStopRequest
2421 onStopRequest: function(request, context, status) {
2422 request.QueryInterface(nsIIncrementalDownload);
2423 LOG("Downloader", "onStopRequest: " + request.URI.spec + ", status = " + status);
2424
2425 var state = this._patch.state;
2426 var shouldShowPrompt = false;
2427 var deleteActiveUpdate = false;
2428 const NS_BINDING_ABORTED = 0x804b0002;
2429 const NS_ERROR_ABORT = 0x80004004;
2430 if (Components.isSuccessCode(status)) {
2431 var sbs =
2432 Components.classes["@mozilla.org/intl/stringbundle;1"].
2433 getService(Components.interfaces.nsIStringBundleService);
2434 var updateStrings = sbs.createBundle(URI_UPDATES_PROPERTIES);
2435 if (this._verifyDownload()) {
2436 state = STATE_PENDING;
2437
2438 // We only need to explicitly show the prompt if this is a backround
2439 // download, since otherwise some kind of UI is already visible and
2440 // that UI will notify.
2441 if (this.background)
2442 shouldShowPrompt = true;
2443
2444 // Tell the updater.exe we're ready to apply.
2445 writeStatusFile(getUpdatesDir(), state);
2446 this._update.installDate = (new Date()).getTime();
2447 this._update.statusText = updateStrings.
2448 GetStringFromName("installPending");
2449 } else {
2450 LOG("Downloader", "onStopRequest: download verification failed");
2451 state = STATE_DOWNLOAD_FAILED;
2452
2453 var brandStrings = sbs.createBundle(URI_BRAND_PROPERTIES);
2454 var brandShortName = brandStrings.GetStringFromName("brandShortName");
2455 this._update.statusText = updateStrings.
2456 formatStringFromName("verificationError", [brandShortName], 1);
2457
2458 // TODO: use more informative error code here
2459 status = Components.results.NS_ERROR_UNEXPECTED;
2460
2461 var message = getStatusTextFromCode("verification_failed",
2462 "verification_failed");
2463 this._update.statusText = message;
2464
2465 if (this._update.isCompleteUpdate)
2466 deleteActiveUpdate = true;
2467
2468 // Destroy the updates directory, since we're done with it.
2469 cleanUpUpdatesDir();
2470 }
2471 }
2472 else if (status != NS_BINDING_ABORTED &&
2473 status != NS_ERROR_ABORT) {
2474 LOG("Downloader", "onStopRequest: Non-verification failure");
2475 // Some sort of other failure, log this in the |statusText| property
2476 state = STATE_DOWNLOAD_FAILED;
2477
2478 // XXXben - if |request| (The Incremental Download) provided a means
2479 // for accessing the http channel we could do more here.
2480
2481 const NS_BINDING_FAILED = 2152398849;
2482 this._update.statusText = getStatusTextFromCode(status,
2483 NS_BINDING_FAILED);
2484
2485 // Destroy the updates directory, since we're done with it.
2486 cleanUpUpdatesDir();
2487
2488 deleteActiveUpdate = true;
2489 }
2490 LOG("Downloader", "Setting state to: " + state);
2491 this._patch.state = state;
2492 var um =
2493 Components.classes["@mozilla.org/updates/update-manager;1"].
2494 getService(Components.interfaces.nsIUpdateManager);
2495 if (deleteActiveUpdate) {
2496 this._update.installDate = (new Date()).getTime();
2497 um.activeUpdate = null;
2498 }
2499 um.saveUpdates();
2500
2501 var listenerCount = this._listeners.length;
2502 for (var i = 0; i < listenerCount; ++i)
2503 this._listeners[i].onStopRequest(request, context, status);
2504
2505 this._request = null;
2506
2507 if (state == STATE_DOWNLOAD_FAILED) {
2508 if (!this._update.isCompleteUpdate) {
2509 var allFailed = true;
2510
2511 // If we were downloading a patch and the patch verification phase
2512 // failed, log this and then commence downloading the complete update.
2513 LOG("Downloader", "onStopRequest: Verification of patch failed, downloading complete update");
2514 this._update.isCompleteUpdate = true;
2515 var status = this.downloadUpdate(this._update);
2516
2517 if (status == STATE_NONE) {
2518 cleanupActiveUpdate();
2519 } else {
2520 allFailed = false;
2521 }
2522 // This will reset the |.state| property on this._update if a new
2523 // download initiates.
2524 }
2525
2526 // if we still fail after trying a complete download, give up completely
2527 if (allFailed) {
2528 // In all other failure cases, i.e. we're S.O.L. - no more failing over
2529 // ...
2530
2531 // If this was ever a foreground download, and now there is no UI active
2532 // (e.g. because the user closed the download window) and there was an
2533 // error, we must notify now. Otherwise we can keep the failure to
2534 // ourselves since the user won't be expecting it.
2535 try {
2536 this._update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
2537 var fgdl = this._update.getProperty("foregroundDownload");
2538 }
2539 catch (e) {
2540 }
2541
2542 if (fgdl == "true") {
2543 var prompter =
2544 Components.classes["@mozilla.org/updates/update-prompt;1"].
2545 createInstance(Components.interfaces.nsIUpdatePrompt);
2546 this._update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
2547 this._update.setProperty("downloadFailed", "true");
2548 prompter.showUpdateError(this._update);
2549 }
2550 }
2551
2552 // the complete download succeeded or total failure was handled, so exit
2553 return;
2554 }
2555
2556 // Do this after *everything* else, since it will likely cause the app
2557 // to shut down.
2558 if (shouldShowPrompt) {
2559 // Notify the user that an update has been downloaded and is ready for
2560 // installation (i.e. that they should restart the application). We do
2561 // not notify on failed update attempts.
2562 var prompter =
2563 Components.classes["@mozilla.org/updates/update-prompt;1"].
2564 createInstance(Components.interfaces.nsIUpdatePrompt);
2565 prompter.showUpdateDownloaded(this._update, true);
2566 }
2567 },
2568
2569 /**
2570 * See nsIInterfaceRequestor.idl
2571 */
getInterface
2572 getInterface: function(iid) {
2573 // The network request may require proxy authentication, so provide the
2574 // default nsIAuthPrompt if requested.
2575 if (iid.equals(Components.interfaces.nsIAuthPrompt)) {
2576 var prompt =
2577 Components.classes["@mozilla.org/network/default-auth-prompt;1"].
2578 createInstance();
2579 return prompt.QueryInterface(iid);
2580 }
2581 throw Components.results.NS_ERROR_NO_INTERFACE;
2582 },
2583
2584 /**
2585 * See nsISupports.idl
2586 */
QueryInterface
2587 QueryInterface: function(iid) {
2588 if (!iid.equals(Components.interfaces.nsIRequestObserver) &&
2589 !iid.equals(Components.interfaces.nsIProgressEventSink) &&
2590 !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
2591 !iid.equals(Components.interfaces.nsISupports))
2592 throw Components.results.NS_ERROR_NO_INTERFACE;
2593 return this;
2594 }
2595 };
2596
2597 /**
2598 * A manager for update check timers. Manages timers that fire over long
2599 * periods of time (e.g. days, weeks).
2600 * @constructor
2601 */
TimerManager
2602 function TimerManager() {
2603 getObserverService().addObserver(this, "xpcom-shutdown", false);
2604
2605 const nsITimer = Components.interfaces.nsITimer;
2606 this._timer = Components.classes["@mozilla.org/timer;1"]
2607 .createInstance(nsITimer);
2608 var timerInterval = getPref("getIntPref", PREF_APP_UPDATE_TIMER, 600000);
2609 this._timer.initWithCallback(this, timerInterval,
2610 nsITimer.TYPE_REPEATING_SLACK);
2611 }
2612 TimerManager.prototype = {
2613 /**
2614 * See nsIObserver.idl
2615 */
observe
2616 observe: function(subject, topic, data) {
2617 if (topic == "xpcom-shutdown") {
2618 getObserverService().removeObserver(this, "xpcom-shutdown");
2619
2620 // Release everything we hold onto.
2621 for (var timerID in this._timers)
2622 delete this._timers[timerID];
2623 this._timer = null;
2624 this._timers = null;
2625 }
2626 },
2627
2628 /**
2629 * The Checker Timer
2630 */
2631 _timer: null,
2632
2633 /**
2634 * The set of registered timers.
2635 */
2636 _timers: { },
2637
2638 /**
2639 * Called when the checking timer fires.
2640 * @param timer
2641 * The checking timer that fired.
2642 */
notify
2643 notify: function(timer) {
2644 for (var timerID in this._timers) {
2645 var timerData = this._timers[timerID];
2646 var lastUpdateTime = timerData.lastUpdateTime;
2647 var now = Math.round(Date.now() / 1000);
2648
2649 // Fudge the lastUpdateTime by some random increment of the update
2650 // check interval (e.g. some random slice of 10 minutes) so that when
2651 // the time comes to check, we offset each client request by a random
2652 // amount so they don't all hit at once. app.update.timer is in milliseconds,
2653 // whereas app.update.lastUpdateTime is in seconds
2654 var timerInterval = getPref("getIntPref", PREF_APP_UPDATE_TIMER, 600000);
2655 lastUpdateTime += Math.round(Math.random() * timerInterval / 1000);
2656
2657 if ((now - lastUpdateTime) > timerData.interval &&
2658 timerData.callback instanceof Components.interfaces.nsITimerCallback) {
2659 timerData.callback.notify(timer);
2660 timerData.lastUpdateTime = now;
2661 var preference = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
2662 gPref.setIntPref(preference, now);
2663 }
2664 }
2665 },
2666
2667 /**
2668 * See nsIUpdateService.idl
2669 */
registerTimer
2670 registerTimer: function(id, callback, interval) {
2671 var preference = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
2672 var now = Math.round(Date.now() / 1000);
2673 var lastUpdateTime = null;
2674 if (gPref.prefHasUserValue(preference)) {
2675 lastUpdateTime = gPref.getIntPref(preference);
2676 } else {
2677 gPref.setIntPref(preference, now);
2678 lastUpdateTime = now;
2679 }
2680 this._timers[id] = { callback : callback,
2681 interval : interval,
2682 lastUpdateTime : lastUpdateTime };
2683 },
2684
2685 /**
2686 * See nsISupports.idl
2687 */
QueryInterface
Called: Object:equals (1 calls, 10 v-uS)
Called By: Object:getService (1 calls, 35 v-uS)
2688 QueryInterface: function(iid) {
2689 if (!iid.equals(Components.interfaces.nsIUpdateTimerManager) &&
2690 !iid.equals(Components.interfaces.nsITimerCallback) &&
2691 !iid.equals(Components.interfaces.nsIObserver) &&
2692 !iid.equals(Components.interfaces.nsISupports))
2693 throw Components.results.NS_ERROR_NO_INTERFACE;
2694 return this;
2695 }
2696 };
2697
2698 //@line 2792 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
2699 /**
2700 * UpdatePrompt
2701 * An object which can prompt the user with information about updates, request
2702 * action, etc. Embedding clients can override this component with one that
2703 * invokes a native front end.
2704 * @constructor
2705 */
UpdatePrompt
2706 function UpdatePrompt() {
2707 }
2708 UpdatePrompt.prototype = {
2709 /**
2710 * See nsIUpdateService.idl
2711 */
checkForUpdates
2712 checkForUpdates: function() {
2713 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, "Update:Wizard",
2714 null, null);
2715 },
2716
2717 /**
2718 * See nsIUpdateService.idl
2719 */
showUpdateAvailable
2720 showUpdateAvailable: function(update) {
2721 if (!this._enabled)
2722 return;
2723 var bundle = this._updateBundle;
2724 var stringsPrefix = "updateAvailable_" + update.type + ".";
2725 var title = bundle.formatStringFromName(stringsPrefix + "title", [update.name], 1);
2726 var text = bundle.GetStringFromName(stringsPrefix + "text");
2727 var imageUrl = "";
2728 this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
2729 "Update:Wizard", "updatesavailable", update,
2730 title, text, imageUrl);
2731 },
2732
2733 /**
2734 * See nsIUpdateService.idl
2735 */
showUpdateDownloaded
2736 showUpdateDownloaded: function(update, background) {
2737 if (background) {
2738 if (!this._enabled)
2739 return;
2740 var bundle = this._updateBundle;
2741 var stringsPrefix = "updateDownloaded_" + update.type + ".";
2742 var title = bundle.formatStringFromName(stringsPrefix + "title", [update.name], 1);
2743 var text = bundle.GetStringFromName(stringsPrefix + "text");
2744 var imageUrl = "";
2745 this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
2746 "Update:Wizard", "finishedBackground", update,
2747 title, text, imageUrl);
2748 } else {
2749 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
2750 "Update:Wizard", "finishedBackground", update);
2751 }
2752 },
2753
2754 /**
2755 * See nsIUpdateService.idl
2756 */
showUpdateInstalled
2757 showUpdateInstalled: function(update) {
2758 var showUpdateInstalledUI = getPref("getBoolPref",
2759 PREF_APP_UPDATE_SHOW_INSTALLED_UI, true);
2760 if (this._enabled && showUpdateInstalledUI) {
2761 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, "Update:Wizard",
2762 "installed", update);
2763 }
2764 },
2765
2766 /**
2767 * See nsIUpdateService.idl
2768 */
showUpdateError
2769 showUpdateError: function(update) {
2770 if (this._enabled) {
2771 // In some cases, we want to just show a simple alert dialog:
2772 if (update.state == STATE_FAILED && update.errorCode == WRITE_ERROR) {
2773 var updateBundle = this._updateBundle;
2774 var title = updateBundle.GetStringFromName("updaterIOErrorTitle");
2775 var text = updateBundle.formatStringFromName("updaterIOErrorMsg",
2776 [gApp.name, gApp.name], 2);
2777 var ww =
2778 Components.classes["@mozilla.org/embedcomp/window-watcher;1"].
2779 getService(Components.interfaces.nsIWindowWatcher);
2780 ww.getNewPrompter(null).alert(title, text);
2781 } else {
2782 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, "Update:Wizard",
2783 "errors", update);
2784 }
2785 }
2786 },
2787
2788 /**
2789 * See nsIUpdateService.idl
2790 */
showUpdateHistory
2791 showUpdateHistory: function(parent) {
2792 this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal,dialog=yes", "Update:History",
2793 null, null);
2794 },
2795
2796 /**
2797 * Whether or not we are enabled (i.e. not in Silent mode)
2798 */
get__enabled
2799 get _enabled() {
2800 return !getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false);
2801 },
2802
get__updateBundle
2803 get _updateBundle() {
2804 return Components.classes["@mozilla.org/intl/stringbundle;1"]
2805 .getService(Components.interfaces.nsIStringBundleService)
2806 .createBundle(URI_UPDATES_PROPERTIES);
2807 },
2808
2809 /**
2810 * Initiate a less obtrusive UI, starting with a non-modal notification alert
2811 * @param parent
2812 * A parent window, can be null
2813 * @param uri
2814 * The URI string of the dialog to show
2815 * @param name
2816 * The Window Name of the dialog to show, in case it is already open
2817 * and can merely be focused
2818 * @param page
2819 * The page of the wizard to be displayed, if one is already open.
2820 * @param update
2821 * An update to pass to the UI in the window arguments.
2822 * Can be null
2823 * @param title
2824 * The title for the notification alert.
2825 * @param text
2826 * The contents of the notification alert.
2827 * @param imageUrl
2828 * A URL identifying the image to put in the notification alert.
2829 */
_showUnobtrusiveUI
2830 _showUnobtrusiveUI: function(parent, uri, features, name, page, update,
2831 title, text, imageUrl) {
2832 var observer = {
2833 updatePrompt: this,
2834 service: null,
2835 timer: null,
notify
2836 notify: function () {
2837 // the user hasn't restarted yet => prompt when idle
2838 this.service.removeObserver(this, "quit-application");
2839 this.updatePrompt._showUIWhenIdle(parent, uri, features, name, page, update);
2840 },
observe
2841 observe: function (aSubject, aTopic, aData) {
2842 switch (aTopic) {
2843 case "alertclickcallback":
2844 this.updatePrompt._showUI(parent, uri, features, name, page, update);
2845 // fall thru
2846 case "quit-application":
2847 this.timer.cancel();
2848 this.service.removeObserver(this, "quit-application");
2849 break;
2850 }
2851 }
2852 };
2853
2854 try {
2855 var notifier = Components.classes["@mozilla.org/alerts-service;1"]
2856 .getService(Components.interfaces.nsIAlertsService);
2857 notifier.showAlertNotification(imageUrl, title, text, true, "", observer);
2858 }
2859 catch (e) {
2860 // Failed to retrieve alerts service, platform unsupported
2861 this._showUIWhenIdle(parent, uri, features, name, page, update);
2862 return;
2863 }
2864
2865 observer.service =
2866 Components.classes["@mozilla.org/observer-service;1"]
2867 .getService(Components.interfaces.nsIObserverService);
2868 observer.service.addObserver(observer, "quit-application", false);
2869
2870 // Give the user x seconds to react before showing the big UI
2871 var promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
2872 observer.timer =
2873 Components.classes["@mozilla.org/timer;1"]
2874 .createInstance(Components.interfaces.nsITimer);
2875 observer.timer.initWithCallback(observer, promptWaitTime * 1000,
2876 observer.timer.TYPE_ONE_SHOT);
2877 },
2878
2879 /**
2880 * Show the UI when the user was idle
2881 * @param parent
2882 * A parent window, can be null
2883 * @param uri
2884 * The URI string of the dialog to show
2885 * @param name
2886 * The Window Name of the dialog to show, in case it is already open
2887 * and can merely be focused
2888 * @param page
2889 * The page of the wizard to be displayed, if one is already open.
2890 * @param update
2891 * An update to pass to the UI in the window arguments.
2892 * Can be null
2893 */
_showUIWhenIdle
2894 _showUIWhenIdle: function(parent, uri, features, name, page, update) {
2895 var idleService =
2896 Components.classes["@mozilla.org/widget/idleservice;1"]
2897 .getService(Components.interfaces.nsIIdleService);
2898
2899 const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
2900 if (idleService.idleTime / 1000 >= IDLE_TIME) {
2901 this._showUI(parent, uri, features, name, page, update);
2902 } else {
2903 var observerService =
2904 Components.classes["@mozilla.org/observer-service;1"]
2905 .getService(Components.interfaces.nsIObserverService);
2906 var observer = {
2907 updatePrompt: this,
observe
2908 observe: function (aSubject, aTopic, aData) {
2909 switch (aTopic) {
2910 case "idle":
2911 this.updatePrompt._showUI(parent, uri, features, name, page, update);
2912 // fall thru
2913 case "quit-application":
2914 idleService.removeIdleObserver(this, IDLE_TIME);
2915 observerService.removeObserver(this, "quit-application");
2916 break;
2917 }
2918 }
2919 };
2920 idleService.addIdleObserver(observer, IDLE_TIME);
2921 observerService.addObserver(observer, "quit-application", false);
2922 }
2923 },
2924
2925 /**
2926 * Show the Update Checking UI
2927 * @param parent
2928 * A parent window, can be null
2929 * @param uri
2930 * The URI string of the dialog to show
2931 * @param name
2932 * The Window Name of the dialog to show, in case it is already open
2933 * and can merely be focused
2934 * @param page
2935 * The page of the wizard to be displayed, if one is already open.
2936 * @param update
2937 * An update to pass to the UI in the window arguments.
2938 * Can be null
2939 */
_showUI
2940 _showUI: function(parent, uri, features, name, page, update) {
2941 var ary = null;
2942 if (update) {
2943 ary = Components.classes["@mozilla.org/supports-array;1"]
2944 .createInstance(Components.interfaces.nsISupportsArray);
2945 ary.AppendElement(update);
2946 }
2947
2948 var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
2949 .getService(Components.interfaces.nsIWindowMediator);
2950 var win = wm.getMostRecentWindow(name);
2951 if (win) {
2952 if (page && "setCurrentPage" in win)
2953 win.setCurrentPage(page);
2954 win.focus();
2955 }
2956 else {
2957 var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
2958 if (features)
2959 openFeatures += "," + features;
2960 var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
2961 .getService(Components.interfaces.nsIWindowWatcher);
2962 ww.openWindow(parent, uri, "", openFeatures, ary);
2963 }
2964 },
2965
2966 /**
2967 * See nsISupports.idl
2968 */
QueryInterface
2969 QueryInterface: function(iid) {
2970 if (!iid.equals(Components.interfaces.nsIUpdatePrompt) &&
2971 !iid.equals(Components.interfaces.nsISupports))
2972 throw Components.results.NS_ERROR_NO_INTERFACE;
2973 return this;
2974 }
2975 };
2976 //@line 3070 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
2977
2978 var gModule = {
registerSelf
2979 registerSelf: function(componentManager, fileSpec, location, type) {
2980 componentManager = componentManager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
2981
2982 for (var key in this._objects) {
2983 var obj = this._objects[key];
2984 componentManager.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
2985 fileSpec, location, type);
2986 }
2987
2988 // Make the Update Service a startup observer
2989 var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
2990 .getService(Components.interfaces.nsICategoryManager);
2991 categoryManager.addCategoryEntry("app-startup", this._objects.service.className,
2992 "service," + this._objects.service.contractID,
2993 true, true);
2994 },
2995
getClassObject
2996 getClassObject: function(componentManager, cid, iid) {
2997 if (!iid.equals(Components.interfaces.nsIFactory))
2998 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
2999
3000 for (var key in this._objects) {
3001 if (cid.equals(this._objects[key].CID))
3002 return this._objects[key].factory;
3003 }
3004
3005 throw Components.results.NS_ERROR_NO_INTERFACE;
3006 },
3007
3008 _objects: {
3009 service: { CID : Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}"),
3010 contractID : "@mozilla.org/updates/update-service;1",
3011 className : "Update Service",
3012 factory : makeFactory(UpdateService)
3013 },
3014 checker: { CID : Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"),
3015 contractID : "@mozilla.org/updates/update-checker;1",
3016 className : "Update Checker",
3017 factory : makeFactory(Checker)
3018 },
3019 //@line 3113 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
3020 prompt: { CID : Components.ID("{27ABA825-35B5-4018-9FDD-F99250A0E722}"),
3021 contractID : "@mozilla.org/updates/update-prompt;1",
3022 className : "Update Prompt",
3023 factory : makeFactory(UpdatePrompt)
3024 },
3025 //@line 3119 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
3026 timers: { CID : Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"),
3027 contractID : "@mozilla.org/updates/timer-manager;1",
3028 className : "Timer Manager",
3029 factory : makeFactory(TimerManager)
3030 },
3031 manager: { CID : Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
3032 contractID : "@mozilla.org/updates/update-manager;1",
3033 className : "Update Manager",
3034 factory : makeFactory(UpdateManager)
3035 },
3036 },
3037
canUnload
3038 canUnload: function(componentManager) {
3039 return true;
3040 }
3041 };
3042
3043 /**
3044 * Creates a factory for instances of an object created using the passed-in
3045 * constructor.
3046 */
makeFactory
3047 function makeFactory(ctor) {
ci
3048 function ci(outer, iid) {
3049 if (outer != null)
3050 throw Components.results.NS_ERROR_NO_AGGREGATION;
3051 return (new ctor()).QueryInterface(iid);
3052 }
3053 return { createInstance: ci };
3054 }
3055
NSGetModule
3056 function NSGetModule(compMgr, fileSpec) {
3057 return gModule;
3058 }
3059
3060 /**
3061 * Determines whether or there are installed addons which are incompatible
3062 * with this update.
3063 * @param update
3064 * The update to check compatibility against
3065 * @returns true if there are no addons installed that are incompatible with
3066 * the specified update, false otherwise.
3067 */
isCompatible
3068 function isCompatible(update) {
3069 //@line 3163 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
3070 var em =
3071 Components.classes["@mozilla.org/extensions/manager;1"].
3072 getService(nsIExtensionManager);
3073 var items = em.getIncompatibleItemList("", update.extensionVersion,
3074 update.platformVersion, nsIUpdateItem.TYPE_ANY, false, { });
3075 return items.length == 0;
3076 //@line 3172 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
3077 }
3078
3079 /**
3080 * Shows a prompt for an update, provided there are no incompatible addons.
3081 * If there are, kick off an update check and see if updates are available
3082 * that will resolve the incompatibilities.
3083 * @param update
3084 * The available update to show
3085 */
showPromptIfNoIncompatibilities
3086 function showPromptIfNoIncompatibilities(update) {
showPrompt
3087 function showPrompt(update) {
3088 LOG("UpdateService", "_selectAndInstallUpdate: need to prompt user before continuing...");
3089 var prompter =
3090 Components.classes["@mozilla.org/updates/update-prompt;1"].
3091 createInstance(Components.interfaces.nsIUpdatePrompt);
3092 prompter.showUpdateAvailable(update);
3093 }
3094
3095 //@line 3191 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
3096 /**
3097 * Determines if an addon is compatible with a particular update.
3098 * @param addon
3099 * The addon to check
3100 * @param version
3101 * The extensionVersion of the update to check for compatibility
3102 * against.
3103 * @returns true if the addon is compatible, false otherwise
3104 */
addonIsCompatible
3105 function addonIsCompatible(addon, version) {
3106 var vc =
3107 Components.classes["@mozilla.org/xpcom/version-comparator;1"].
3108 getService(Components.interfaces.nsIVersionComparator);
3109 return (vc.compare(version, addon.minAppVersion) >= 0) &&
3110 (vc.compare(version, addon.maxAppVersion) <= 0);
3111 }
3112
3113 /**
3114 * An object implementing nsIAddonUpdateCheckListener that looks for
3115 * available updates to addons and if updates are found that will make the
3116 * user's installed addon set compatible with the update, suppresses the
3117 * prompt that would otherwise be shown.
3118 * @param addons
3119 * An array of incompatible addons that are installed.
3120 * @constructor
3121 */
Listener
3122 function Listener(addons) {
3123 this._addons = addons;
3124 }
3125 Listener.prototype = {
3126 _addons: null,
3127
3128 /**
3129 * See nsIUpdateService.idl
3130 */
onUpdateStarted
3131 onUpdateStarted: function() {
3132 },
onUpdateEnded
3133 onUpdateEnded: function() {
3134 // There are still incompatibilities, even after an extension update
3135 // check to see if there were newer, compatible versions available, so
3136 // we have to prompt.
3137 //
3138 // PREF_APP_UPDATE_INCOMPATIBLE_MODE controls the mode in which we
3139 // handle incompatibilities:
3140 // UPDATE_CHECK_NEWVERSION We count both VersionInfo updates _and_
3141 // NewerVersion updates against the list of incompatible addons
3142 // installed - i.e. if Foo 1.2 is installed and it is incompatible
3143 // with the update, and we find Foo 2.0 which is but which is not
3144 // yet downloaded or installed, then we do NOT prompt because the
3145 // user can download Foo 2.0 when they restart after the update
3146 // during the mismatch checking UI. This is the default, since it
3147 // suppresses most prompt dialogs.
3148 // UPDATE_CHECK_COMPATIBILITY We count only VersionInfo updates
3149 // against the list of incompatible addons installed - i.e. if the
3150 // situation above with Foo 1.2 and available update to 2.0
3151 // applies, we DO show the prompt since a download operation will
3152 // be required after the update. This is not the default and is
3153 // supplied only as a hidden option for those that want it.
3154 var mode = getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE,
3155 nsIExtensionManager.UPDATE_CHECK_NEWVERSION);
3156 if ((mode == nsIExtensionManager.UPDATE_CHECK_NEWVERSION
3157 && this._addons.length) || !isCompatible(update))
3158 showPrompt(update);
3159 },
onAddonUpdateStarted
3160 onAddonUpdateStarted: function(addon) {
3161 },
onAddonUpdateEnded
3162 onAddonUpdateEnded: function(addon, status) {
3163 const Ci = Components.interfaces;
3164 if (status != Ci.nsIAddonUpdateCheckListener.STATUS_UPDATE)
3165 return;
3166
3167 var reqVersion = addon.targetAppID == TOOLKIT_ID ?
3168 update.platformVersion :
3169 update.extensionVersion;
3170 if (!addonIsCompatible(addon, reqVersion))
3171 return;
3172
3173 for (var i = 0; i < this._addons.length; ++i) {
3174 if (this._addons[i] == addon) {
3175 this._addons.splice(i, 1);
3176 break;
3177 }
3178 }
3179 },
3180 /**
3181 * See nsISupports.idl
3182 */
QueryInterface
3183 QueryInterface: function(iid) {
3184 if (!iid.equals(Components.interfaces.nsIAddonUpdateCheckListener) &&
3185 !iid.equals(Components.interfaces.nsISupports))
3186 throw Components.results.NS_ERROR_NO_INTERFACE;
3187 return this;
3188 }
3189 };
3190
3191 if (!isCompatible(update)) {
3192 var em =
3193 Components.classes["@mozilla.org/extensions/manager;1"].
3194 getService(nsIExtensionManager);
3195 var items = em.getIncompatibleItemList("", update.extensionVersion,
3196 update.platformVersion, nsIUpdateItem.TYPE_ANY, false, { });
3197 var listener = new Listener(items);
3198 // See documentation on |mode| above.
3199 var mode = getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE,
3200 nsIExtensionManager.UPDATE_CHECK_NEWVERSION);
3201 em.update([], 0, mode, listener);
3202 }
3203 else
3204 //@line 3300 "/Users/sombrero/rev_control/hg/mozilla/toolkit/mozapps/update/src/nsUpdateService.js.in"
3205 showPrompt(update);
3206 }