Bug 485624 - Downloads in progress for previous releases should be canceled on startu...
[mozilla-1.9.git] / toolkit / mozapps / update / src / nsUpdateService.js.in
blobf05aa39d6e800ef7860737eb8dfd27b2668b5588
1 #if 0
2 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is the Update Service.
18 * The Initial Developer of the Original Code is Ben Goodger.
19 * Portions created by the Initial Developer are Copyright (C) 2004
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Ben Goodger <ben@mozilla.org> (Original Author)
24 * Darin Fisher <darin@meer.net>
25 * Ben Turner <bent.mozilla@gmail.com>
26 * Jeff Walden <jwalden+code@mit.edu>
27 * Alexander J. Vincent <ajvincent@gmail.com>
28 * Dão Gottwald <dao@mozilla.com>
30 * Alternatively, the contents of this file may be used under the terms of
31 * either the GNU General Public License Version 2 or later (the "GPL"), or
32 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
33 * in which case the provisions of the GPL or the LGPL are applicable instead
34 * of those above. If you wish to allow use of your version of this file only
35 * under the terms of either the GPL or the LGPL, and not to allow others to
36 * use your version of this file under the terms of the MPL, indicate your
37 * decision by deleting the provisions above and replace them with the notice
38 * and other provisions required by the GPL or the LGPL. If you do not delete
39 * the provisions above, a recipient may use your version of this file under
40 * the terms of any one of the MPL, the GPL or the LGPL.
42 * ***** END LICENSE BLOCK ***** */
43 #endif
45 const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
46 const PREF_APP_UPDATE_AUTO = "app.update.auto";
47 const PREF_APP_UPDATE_MODE = "app.update.mode";
48 const PREF_APP_UPDATE_SILENT = "app.update.silent";
49 const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
50 const PREF_APP_UPDATE_TIMER = "app.update.timer";
51 const PREF_APP_UPDATE_IDLETIME = "app.update.idletime";
52 const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
53 const PREF_APP_UPDATE_LOG_BRANCH = "app.update.log.";
54 const PREF_APP_UPDATE_URL = "app.update.url";
55 const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override";
56 const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
57 const PREF_APP_UPDATE_CHANNEL = "app.update.channel";
58 const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI";
59 const PREF_APP_UPDATE_LASTUPDATETIME_FMT = "app.update.lastUpdateTime.%ID%";
60 const PREF_GENERAL_USERAGENT_LOCALE = "general.useragent.locale";
61 const PREF_APP_UPDATE_INCOMPATIBLE_MODE = "app.update.incompatible.mode";
62 const PREF_UPDATE_NEVER_BRANCH = "app.update.never.";
63 const PREF_PARTNER_BRANCH = "app.partner.";
64 const PREF_APP_DISTRIBUTION = "distribution.id";
65 const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
67 const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
68 const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
69 const URI_BRAND_PROPERTIES = "chrome://branding/locale/brand.properties";
70 const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
71 const URI_UPDATE_NS = "http://www.mozilla.org/2005/app-update";
73 const KEY_GREDIR = "GreD";
74 const KEY_APPDIR = "XCurProcD";
75 #ifdef XP_WIN
76 const KEY_UPDROOT = "UpdRootD";
77 const KEY_UAPPDATA = "UAppData";
78 #endif
80 const DIR_UPDATES = "updates";
81 const FILE_UPDATE_STATUS = "update.status";
82 const FILE_UPDATE_VERSION = "update.version";
83 const FILE_UPDATE_ARCHIVE = "update.mar";
84 const FILE_UPDATE_LOG = "update.log"
85 const FILE_UPDATES_DB = "updates.xml";
86 const FILE_UPDATE_ACTIVE = "active-update.xml";
87 const FILE_PERMS_TEST = "update.test";
88 const FILE_LAST_LOG = "last-update.log";
89 const FILE_UPDATER_INI = "updater.ini";
91 const MODE_RDONLY = 0x01;
92 const MODE_WRONLY = 0x02;
93 const MODE_CREATE = 0x08;
94 const MODE_APPEND = 0x10;
95 const MODE_TRUNCATE = 0x20;
97 const PERMS_FILE = 0644;
98 const PERMS_DIRECTORY = 0755;
100 const STATE_NONE = "null";
101 const STATE_DOWNLOADING = "downloading";
102 const STATE_PENDING = "pending";
103 const STATE_APPLYING = "applying";
104 const STATE_SUCCEEDED = "succeeded";
105 const STATE_DOWNLOAD_FAILED = "download-failed";
106 const STATE_FAILED = "failed";
108 // From updater/errors.h:
109 const WRITE_ERROR = 7;
111 const DOWNLOAD_CHUNK_SIZE = 300000; // bytes
112 const DOWNLOAD_BACKGROUND_INTERVAL = 600; // seconds
113 const DOWNLOAD_FOREGROUND_INTERVAL = 0;
115 const TOOLKIT_ID = "toolkit@mozilla.org";
117 const POST_UPDATE_CONTRACTID = "@mozilla.org/updates/post-update;1";
119 const nsIExtensionManager = Components.interfaces.nsIExtensionManager;
120 const nsILocalFile = Components.interfaces.nsILocalFile;
121 const nsIUpdateService = Components.interfaces.nsIUpdateService;
122 const nsIUpdateItem = Components.interfaces.nsIUpdateItem;
123 const nsIPrefLocalizedString = Components.interfaces.nsIPrefLocalizedString;
124 const nsIIncrementalDownload = Components.interfaces.nsIIncrementalDownload;
125 const nsIFileInputStream = Components.interfaces.nsIFileInputStream;
126 const nsIFileOutputStream = Components.interfaces.nsIFileOutputStream;
127 const nsICryptoHash = Components.interfaces.nsICryptoHash;
128 const nsIINIParserFactory = Components.interfaces.nsIINIParserFactory;
130 const Node = Components.interfaces.nsIDOMNode;
132 var gApp = null;
133 var gPref = null;
134 var gABI = null;
135 var gOSVersion = null;
136 var gLocale = null;
137 var gConsole = null;
138 var gLogEnabled = { };
140 // shared code for suppressing bad cert dialogs
141 #include ../../shared/src/badCertHandler.js
144 * Logs a string to the error console.
145 * @param string
146 * The string to write to the error console..
148 function LOG(module, string) {
149 if (module in gLogEnabled || "all" in gLogEnabled) {
150 dump("*** " + module + ": " + string + "\n");
151 gConsole.logStringMessage(string);
156 * Convert a string containing binary values to hex.
158 function binaryToHex(input) {
159 var result = "";
160 for (var i = 0; i < input.length; ++i) {
161 var hex = input.charCodeAt(i).toString(16);
162 if (hex.length == 1)
163 hex = "0" + hex;
164 result += hex;
166 return result;
170 * Gets a File URL spec for a nsIFile
171 * @param file
172 * The file to get a file URL spec to
173 * @returns The file URL spec to the file
175 function getURLSpecFromFile(file) {
176 var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
177 .getService(Components.interfaces.nsIIOService);
178 var fph = ioServ.getProtocolHandler("file")
179 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
180 return fph.getURLSpecFromFile(file);
184 * Gets the specified directory at the specified hierarchy under a
185 * Directory Service key.
186 * @param key
187 * The Directory Service Key to start from
188 * @param pathArray
189 * An array of path components to locate beneath the directory
190 * specified by |key|
191 * @return nsIFile object for the location specified. If the directory
192 * requested does not exist, it is created, along with any
193 * parent directories that need to be created.
195 function getDir(key, pathArray) {
196 return getDirInternal(key, pathArray, true, false);
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 NOT created.
210 function getDirNoCreate(key, pathArray) {
211 return getDirInternal(key, pathArray, false, false);
215 * Gets the specified directory at the specified hierarchy under the
216 * update root directory.
217 * @param pathArray
218 * An array of path components to locate beneath the directory
219 * specified by |key|
220 * @return nsIFile object for the location specified. If the directory
221 * requested does not exist, it is created, along with any
222 * parent directories that need to be created.
224 function getUpdateDir(pathArray) {
225 return getDirInternal(KEY_APPDIR, pathArray, true, true);
229 * Gets the specified directory at the specified hierarchy under a
230 * Directory Service key.
231 * @param key
232 * The Directory Service Key to start from
233 * @param pathArray
234 * An array of path components to locate beneath the directory
235 * specified by |key|
236 * @param shouldCreate
237 * true if the directory hierarchy specified in |pathArray|
238 * should be created if it does not exist,
239 * false otherwise.
240 * @param update
241 * true if finding the update directory,
242 * false otherwise.
243 * @return nsIFile object for the location specified.
245 function getDirInternal(key, pathArray, shouldCreate, update) {
246 var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
247 .getService(Components.interfaces.nsIProperties);
248 var dir = fileLocator.get(key, Components.interfaces.nsIFile);
249 #ifdef XP_WIN
250 if (update) {
251 try {
252 dir = fileLocator.get(KEY_UPDROOT, Components.interfaces.nsIFile);
253 } catch (e) {
256 #endif
257 for (var i = 0; i < pathArray.length; ++i) {
258 dir.append(pathArray[i]);
259 if (shouldCreate && !dir.exists())
260 dir.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
262 return dir;
266 * Gets the file at the specified hierarchy under a Directory Service key.
267 * @param key
268 * The Directory Service Key to start from
269 * @param pathArray
270 * An array of path components to locate beneath the directory
271 * specified by |key|. The last item in this array must be the
272 * leaf name of a file.
273 * @return nsIFile object for the file specified. The file is NOT created
274 * if it does not exist, however all required directories along
275 * the way are.
277 function getFile(key, pathArray) {
278 var file = getDir(key, pathArray.slice(0, -1));
279 file.append(pathArray[pathArray.length - 1]);
280 return file;
284 * Gets the file at the specified hierarchy under the update root directory.
285 * @param pathArray
286 * An array of path components to locate beneath the directory
287 * specified by |key|. The last item in this array must be the
288 * leaf name of a file.
289 * @return nsIFile object for the file specified. The file is NOT created
290 * if it does not exist, however all required directories along
291 * the way are.
293 function getUpdateFile(pathArray) {
294 var file = getUpdateDir(pathArray.slice(0, -1));
295 file.append(pathArray[pathArray.length - 1]);
296 return file;
300 * Closes a Safe Output Stream
301 * @param fos
302 * The Safe Output Stream to close
304 function closeSafeOutputStream(fos) {
305 if (fos instanceof Components.interfaces.nsISafeOutputStream) {
306 try {
307 fos.finish();
309 catch (e) {
310 fos.close();
313 else
314 fos.close();
318 * Returns human readable status text from the updates.properties bundle
319 * based on an error code
320 * @param code
321 * The error code to look up human readable status text for
322 * @param defaultCode
323 * The default code to look up should human readable status text
324 * not exist for |code|
325 * @returns A human readable status text string
327 function getStatusTextFromCode(code, defaultCode) {
328 var sbs =
329 Components.classes["@mozilla.org/intl/stringbundle;1"].
330 getService(Components.interfaces.nsIStringBundleService);
331 var updateBundle = sbs.createBundle(URI_UPDATES_PROPERTIES);
332 var reason = updateBundle.GetStringFromName("checker_error-" + defaultCode);
333 try {
334 reason = updateBundle.GetStringFromName("checker_error-" + code);
335 LOG("General", "Transfer Error: " + reason + ", code: " + code);
337 catch (e) {
338 // Use the default reason
339 LOG("General", "Transfer Error: " + reason + ", code: " + defaultCode);
341 return reason;
345 * Get the Active Updates directory
346 * @param key
347 * The Directory Service Key (optional).
348 * If used, don't search local appdata on Win32 and don't create dir.
349 * @returns The active updates directory, as a nsIFile object
351 function getUpdatesDir(key) {
352 // Right now, we only support downloading one patch at a time, so we always
353 // use the same target directory.
354 var fileLocator =
355 Components.classes["@mozilla.org/file/directory_service;1"].
356 getService(Components.interfaces.nsIProperties);
357 var appDir;
358 if (key)
359 appDir = fileLocator.get(key, Components.interfaces.nsIFile);
360 else {
361 appDir = fileLocator.get(KEY_APPDIR, Components.interfaces.nsIFile);
362 #ifdef XP_WIN
363 try {
364 appDir = fileLocator.get(KEY_UPDROOT, Components.interfaces.nsIFile);
365 } catch (e) {
367 #endif
369 appDir.append(DIR_UPDATES);
370 appDir.append("0");
371 if (!appDir.exists() && !key)
372 appDir.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
373 return appDir;
377 * Reads the update state from the update.status file in the specified
378 * directory.
379 * @param dir
380 * The dir to look for an update.status file in
381 * @returns The status value of the update.
383 function readStatusFile(dir) {
384 var statusFile = dir.clone();
385 statusFile.append(FILE_UPDATE_STATUS);
386 LOG("General", "Reading Status File: " + statusFile.path);
387 return readStringFromFile(statusFile) || STATE_NONE;
391 * Writes the current update operation/state to a file in the patch
392 * directory, indicating to the patching system that operations need
393 * to be performed.
394 * @param dir
395 * The patch directory where the update.status file should be
396 * written.
397 * @param state
398 * The state value to write.
400 function writeStatusFile(dir, state) {
401 var statusFile = dir.clone();
402 statusFile.append(FILE_UPDATE_STATUS);
403 writeStringToFile(statusFile, state);
407 # Writes the update's application version to a file in the patch directory. If
408 # the update doesn't provide application version information via the
409 # extensionVersion attribute the string "null" will be written to the file.
410 # This value is compared during startup (in nsUpdateDriver.cpp) to determine if
411 # the update should be applied. Note that this won't provide protection from
412 # downgrade of the application for the nightly user case where the application
413 # version doesn't change.
414 # @param dir
415 # The patch directory where the update.version file should be
416 # written.
417 # @param version
418 # The version value to write. Will be the string "null" when the
419 # update doesn't provide the extensionVersion attribute in the update
420 # xml.
422 function writeVersionFile(dir, version) {
423 var versionFile = dir.clone();
424 versionFile.append(FILE_UPDATE_VERSION);
425 writeStringToFile(versionFile, version);
429 * Removes the Updates Directory
430 * @param key
431 * The Directory Service Key under which update directory resides
432 * (optional).
434 function cleanUpUpdatesDir(key) {
435 // Bail out if we don't have appropriate permissions
436 var updateDir;
437 try {
438 updateDir = getUpdatesDir(key);
440 catch (e) {
441 return;
444 var e = updateDir.directoryEntries;
445 while (e.hasMoreElements()) {
446 var f = e.getNext().QueryInterface(Components.interfaces.nsIFile);
447 // Preserve the last update log file for debugging purposes
448 if (f.leafName == FILE_UPDATE_LOG) {
449 try {
450 var dir = f.parent.parent;
451 var logFile = dir.clone();
452 logFile.append(FILE_LAST_LOG);
453 if (logFile.exists())
454 logFile.remove(false);
455 f.copyTo(dir, FILE_LAST_LOG);
457 catch (e) {
458 LOG("General", "Failed to copy file: " + f.path);
461 // Now, recursively remove this file. The recusive removal is really
462 // only needed on Mac OSX because this directory will contain a copy of
463 // updater.app, which is itself a directory.
464 try {
465 f.remove(true);
467 catch (e) {
468 LOG("General", "Failed to remove file: " + f.path);
471 try {
472 updateDir.remove(false);
473 } catch (e) {
474 LOG("General", "Failed to remove update directory: " + updateDir.path +
475 " - This is almost always bad. Exception = " + e);
476 throw e;
481 * Clean up updates list and the updates directory.
482 * @param key
483 * The Directory Service Key under which update directory resides
484 * (optional).
486 function cleanupActiveUpdate(key) {
487 // Move the update from the Active Update list into the Past Updates list.
488 var um =
489 Components.classes["@mozilla.org/updates/update-manager;1"].
490 getService(Components.interfaces.nsIUpdateManager);
491 um.activeUpdate = null;
492 um.saveUpdates();
494 // Now trash the updates directory, since we're done with it
495 cleanUpUpdatesDir(key);
499 * Gets a preference value, handling the case where there is no default.
500 * @param func
501 * The name of the preference function to call, on nsIPrefBranch
502 * @param preference
503 * The name of the preference
504 * @param defaultValue
505 * The default value to return in the event the preference has
506 * no setting
507 * @returns The value of the preference, or undefined if there was no
508 * user or default value.
510 function getPref(func, preference, defaultValue) {
511 try {
512 return gPref[func](preference);
514 catch (e) {
516 return defaultValue;
520 * Gets the locale specified by the 'Locale' key in the 'Installation' section
521 * of updater.ini if it is available. Otherwise the general.useragent.locale
522 * preference is used to get the locale. It's possible for this preference to
523 * be localized, so we have to do a little extra work here. Similar code
524 * exists in nsHttpHandler.cpp when building the UA string.
526 function getLocale() {
527 if (gLocale)
528 return gLocale;
530 try {
531 #ifdef XP_MACOSX
532 var updaterIni = getFile(KEY_GREDIR, ["updater.app", "Contents", "MacOS",
533 FILE_UPDATER_INI]);
534 #else
535 var updaterIni = getFile(KEY_GREDIR, [FILE_UPDATER_INI]);
536 #endif
537 var iniParser = Components.classes["@mozilla.org/xpcom/ini-parser-factory;1"]
538 .getService(nsIINIParserFactory).createINIParser(updaterIni);
539 gLocale = iniParser.getString("Installation", "Locale");
540 LOG("General", "Getting Locale from File: " + updaterIni.path + " Locale: " + gLocale);
541 return gLocale;
542 } catch (e) {}
544 try {
545 // Get the default branch
546 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
547 .getService(Components.interfaces.nsIPrefService);
548 var defaultPrefs = prefs.getDefaultBranch(null);
549 gLocale = defaultPrefs.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
550 } catch (e) {
551 gLocale = gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
554 return gLocale;
558 * Read the update channel from defaults only. We do this to ensure that
559 * the channel is tightly coupled with the application and does not apply
560 * to other instances of the application that may use the same profile.
562 function getUpdateChannel() {
563 var channel = "default";
564 var prefName;
565 var prefValue;
567 var defaults =
568 gPref.QueryInterface(Components.interfaces.nsIPrefService).
569 getDefaultBranch(null);
570 try {
571 channel = defaults.getCharPref(PREF_APP_UPDATE_CHANNEL);
572 } catch (e) {
573 // use default when pref not found
576 try {
577 var partners = gPref.getChildList(PREF_PARTNER_BRANCH, { });
578 if (partners.length) {
579 channel += "-cck";
580 partners.sort();
582 for each (prefName in partners) {
583 prefValue = gPref.getCharPref(prefName);
584 channel += "-" + prefValue;
588 catch (e) {
589 Components.utils.reportError(e);
592 return channel;
595 /* Get the distribution pref values, from defaults only */
596 function getDistributionPrefValue(aPrefName) {
597 var prefValue = "default";
599 var defaults =
600 gPref.QueryInterface(Components.interfaces.nsIPrefService).
601 getDefaultBranch(null);
602 try {
603 prefValue = defaults.getCharPref(aPrefName);
604 } catch (e) {
605 // use default when pref not found
608 return prefValue;
612 * An enumeration of items in a JS array.
613 * @constructor
615 function ArrayEnumerator(aItems) {
616 this._index = 0;
617 if (aItems) {
618 for (var i = 0; i < aItems.length; ++i) {
619 if (!aItems[i])
620 aItems.splice(i, 1);
623 this._contents = aItems;
626 ArrayEnumerator.prototype = {
627 _index: 0,
628 _contents: [],
630 hasMoreElements: function() {
631 return this._index < this._contents.length;
634 getNext: function() {
635 return this._contents[this._index++];
640 * Trims a prefix from a string.
641 * @param string
642 * The source string
643 * @param prefix
644 * The prefix to remove.
645 * @returns The suffix (string - prefix)
647 function stripPrefix(string, prefix) {
648 return string.substr(prefix.length);
652 * Writes a string of text to a file. A newline will be appended to the data
653 * written to the file. This function only works with ASCII text.
655 function writeStringToFile(file, text) {
656 var fos =
657 Components.classes["@mozilla.org/network/safe-file-output-stream;1"].
658 createInstance(nsIFileOutputStream);
659 var modeFlags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
660 if (!file.exists())
661 file.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
662 fos.init(file, modeFlags, PERMS_FILE, 0);
663 text += "\n";
664 fos.write(text, text.length);
665 closeSafeOutputStream(fos);
669 * Reads a string of text from a file. A trailing newline will be removed
670 * before the result is returned. This function only works with ASCII text.
672 function readStringFromFile(file) {
673 var fis =
674 Components.classes["@mozilla.org/network/file-input-stream;1"].
675 createInstance(nsIFileInputStream);
676 var modeFlags = MODE_RDONLY;
677 if (!file.exists())
678 return null;
679 fis.init(file, modeFlags, PERMS_FILE, 0);
680 var sis =
681 Components.classes["@mozilla.org/scriptableinputstream;1"].
682 createInstance(Components.interfaces.nsIScriptableInputStream);
683 sis.init(fis);
684 var text = sis.read(sis.available());
685 sis.close();
686 if (text[text.length - 1] == "\n")
687 text = text.slice(0, -1);
688 return text;
691 function getObserverService()
693 return Components.classes["@mozilla.org/observer-service;1"]
694 .getService(Components.interfaces.nsIObserverService);
698 * Update Patch
699 * @param patch
700 * A <patch> element to initialize this object with
701 * @throws if patch has a size of 0
702 * @constructor
704 function UpdatePatch(patch) {
705 this._properties = {};
706 for (var i = 0; i < patch.attributes.length; ++i) {
707 var attr = patch.attributes.item(i);
708 attr.QueryInterface(Components.interfaces.nsIDOMAttr);
709 switch (attr.name) {
710 case "selected":
711 this.selected = attr.value == "true";
712 break;
713 case "size":
714 if (0 == parseInt(attr.value)) {
715 LOG("UpdatePatch", "0-sized patch!");
716 throw Components.results.NS_ERROR_ILLEGAL_VALUE;
718 // fall through
719 default:
720 this[attr.name] = attr.value;
721 break;
725 UpdatePatch.prototype = {
727 * See nsIUpdateService.idl
729 serialize: function(updates) {
730 var patch = updates.createElementNS(URI_UPDATE_NS, "patch");
731 patch.setAttribute("type", this.type);
732 patch.setAttribute("URL", this.URL);
733 patch.setAttribute("hashFunction", this.hashFunction);
734 patch.setAttribute("hashValue", this.hashValue);
735 patch.setAttribute("size", this.size);
736 patch.setAttribute("selected", this.selected);
737 patch.setAttribute("state", this.state);
739 for (var p in this._properties) {
740 if (this._properties[p].present)
741 patch.setAttribute(p, this._properties[p].data);
744 return patch;
748 * A hash of custom properties
750 _properties: null,
753 * See nsIWritablePropertyBag.idl
755 setProperty: function(name, value) {
756 this._properties[name] = { data: value, present: true };
760 * See nsIWritablePropertyBag.idl
762 deleteProperty: function(name) {
763 if (name in this._properties)
764 this._properties[name].present = false;
765 else
766 throw Components.results.NS_ERROR_FAILURE;
770 * See nsIPropertyBag.idl
772 get enumerator() {
773 var properties = [];
774 for (var p in this._properties)
775 properties.push(this._properties[p].data);
776 return new ArrayEnumerator(properties);
780 * See nsIPropertyBag.idl
782 getProperty: function(name) {
783 if (name in this._properties &&
784 this._properties[name].present)
785 return this._properties[name].data;
786 throw Components.results.NS_ERROR_FAILURE;
790 * Returns whether or not the update.status file for this patch exists at the
791 * appropriate location.
793 get statusFileExists() {
794 var statusFile = getUpdatesDir();
795 statusFile.append(FILE_UPDATE_STATUS);
796 return statusFile.exists();
800 * See nsIUpdateService.idl
802 get state() {
803 if (this._properties.state)
804 return this._properties.state;
805 return STATE_NONE;
807 set state(val) {
808 this._properties.state = val;
812 * See nsISupports.idl
814 QueryInterface: function(iid) {
815 if (!iid.equals(Components.interfaces.nsIUpdatePatch) &&
816 !iid.equals(Components.interfaces.nsIPropertyBag) &&
817 !iid.equals(Components.interfaces.nsIWritablePropertyBag) &&
818 !iid.equals(Components.interfaces.nsISupports))
819 throw Components.results.NS_ERROR_NO_INTERFACE;
820 return this;
825 * Update
826 * Implements nsIUpdate
827 * @param update
828 * An <update> element to initialize this object with
829 * @throws if the update contains no patches
830 * @constructor
832 function Update(update) {
833 this._properties = {};
834 this._patches = [];
835 this.installDate = 0;
836 this.isCompleteUpdate = false;
837 this.channel = "default"
839 // Null <update>, assume this is a message container and do no
840 // further initialization
841 if (!update)
842 return;
844 for (var i = 0; i < update.childNodes.length; ++i) {
845 var patchElement = update.childNodes.item(i);
846 if (patchElement.nodeType != Node.ELEMENT_NODE ||
847 patchElement.localName != "patch")
848 continue;
850 patchElement.QueryInterface(Components.interfaces.nsIDOMElement);
851 try {
852 var patch = new UpdatePatch(patchElement);
853 } catch (e) {
854 continue;
856 this._patches.push(patch);
859 if (0 == this._patches.length)
860 throw Components.results.NS_ERROR_ILLEGAL_VALUE;
862 for (var i = 0; i < update.attributes.length; ++i) {
863 var attr = update.attributes.item(i);
864 attr.QueryInterface(Components.interfaces.nsIDOMAttr);
865 if (attr.name == "installDate" && attr.value)
866 this.installDate = parseInt(attr.value);
867 else if (attr.name == "isCompleteUpdate")
868 this.isCompleteUpdate = attr.value == "true";
869 else if (attr.name == "isSecurityUpdate")
870 this.isSecurityUpdate = attr.value == "true";
871 else if (attr.name == "detailsURL")
872 this._detailsURL = attr.value;
873 else if (attr.name == "channel")
874 this.channel = attr.value;
875 else
876 this[attr.name] = attr.value;
879 // The Update Name is either the string provided by the <update> element, or
880 // the string: "<App Name> <Update App Version>"
881 var name = "";
882 if (update.hasAttribute("name"))
883 name = update.getAttribute("name");
884 else {
885 var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
886 .getService(Components.interfaces.nsIStringBundleService);
887 var brandBundle = sbs.createBundle(URI_BRAND_PROPERTIES);
888 var updateBundle = sbs.createBundle(URI_UPDATES_PROPERTIES);
889 var appName = brandBundle.GetStringFromName("brandShortName");
890 name = updateBundle.formatStringFromName("updateName",
891 [appName, this.version], 2);
893 this.name = name;
895 Update.prototype = {
897 * See nsIUpdateService.idl
899 get patchCount() {
900 return this._patches.length;
904 * See nsIUpdateService.idl
906 getPatchAt: function(index) {
907 return this._patches[index];
911 * See nsIUpdateService.idl
913 * We use a copy of the state cached on this object in |_state| only when
914 * there is no selected patch, i.e. in the case when we could not load
915 * |.activeUpdate| from the update manager for some reason but still have
916 * the update.status file to work with.
918 _state: "",
919 set state(state) {
920 if (this.selectedPatch)
921 this.selectedPatch.state = state;
922 this._state = state;
923 return state;
925 get state() {
926 if (this.selectedPatch)
927 return this.selectedPatch.state;
928 return this._state;
932 * See nsIUpdateService.idl
934 errorCode: 0,
937 * See nsIUpdateService.idl
939 get selectedPatch() {
940 for (var i = 0; i < this.patchCount; ++i) {
941 if (this._patches[i].selected)
942 return this._patches[i];
944 return null;
948 * See nsIUpdateService.idl
950 get detailsURL() {
951 if (!this._detailsURL) {
952 try {
953 // Try using a default details URL supplied by the distribution
954 // if the update XML does not supply one.
955 var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
956 .getService(Components.interfaces.nsIURLFormatter);
957 return formatter.formatURLPref(PREF_APP_UPDATE_URL_DETAILS);
959 catch (e) {
962 return this._detailsURL || "";
966 * See nsIUpdateService.idl
968 serialize: function(updates) {
969 var update = updates.createElementNS(URI_UPDATE_NS, "update");
970 update.setAttribute("type", this.type);
971 update.setAttribute("name", this.name);
972 update.setAttribute("version", this.version);
973 update.setAttribute("platformVersion", this.platformVersion);
974 update.setAttribute("extensionVersion", this.extensionVersion);
975 update.setAttribute("detailsURL", this.detailsURL);
976 update.setAttribute("licenseURL", this.licenseURL);
977 update.setAttribute("serviceURL", this.serviceURL);
978 update.setAttribute("installDate", this.installDate);
979 update.setAttribute("statusText", this.statusText);
980 update.setAttribute("buildID", this.buildID);
981 update.setAttribute("isCompleteUpdate", this.isCompleteUpdate);
982 update.setAttribute("channel", this.channel);
983 updates.documentElement.appendChild(update);
985 for (var p in this._properties) {
986 if (this._properties[p].present)
987 update.setAttribute(p, this._properties[p].data);
990 for (var i = 0; i < this.patchCount; ++i)
991 update.appendChild(this.getPatchAt(i).serialize(updates));
993 return update;
997 * A hash of custom properties
999 _properties: null,
1002 * See nsIWritablePropertyBag.idl
1004 setProperty: function(name, value) {
1005 this._properties[name] = { data: value, present: true };
1009 * See nsIWritablePropertyBag.idl
1011 deleteProperty: function(name) {
1012 if (name in this._properties)
1013 this._properties[name].present = false;
1014 else
1015 throw Components.results.NS_ERROR_FAILURE;
1019 * See nsIPropertyBag.idl
1021 get enumerator() {
1022 var properties = [];
1023 for (var p in this._properties)
1024 properties.push(this._properties[p].data);
1025 return new ArrayEnumerator(properties);
1029 * See nsIPropertyBag.idl
1031 getProperty: function(name) {
1032 if (name in this._properties &&
1033 this._properties[name].present)
1034 return this._properties[name].data;
1035 throw Components.results.NS_ERROR_FAILURE;
1039 * See nsISupports.idl
1041 QueryInterface: function(iid) {
1042 if (!iid.equals(Components.interfaces.nsIUpdate) &&
1043 !iid.equals(Components.interfaces.nsIPropertyBag) &&
1044 !iid.equals(Components.interfaces.nsIWritablePropertyBag) &&
1045 !iid.equals(Components.interfaces.nsISupports))
1046 throw Components.results.NS_ERROR_NO_INTERFACE;
1047 return this;
1052 * UpdateService
1053 * A Service for managing the discovery and installation of software updates.
1054 * @constructor
1056 function UpdateService() {
1057 gApp = Components.classes["@mozilla.org/xre/app-info;1"]
1058 .getService(Components.interfaces.nsIXULAppInfo)
1059 .QueryInterface(Components.interfaces.nsIXULRuntime);
1060 gPref = Components.classes["@mozilla.org/preferences-service;1"]
1061 .getService(Components.interfaces.nsIPrefBranch2);
1062 gConsole = Components.classes["@mozilla.org/consoleservice;1"]
1063 .getService(Components.interfaces.nsIConsoleService);
1065 // Not all builds have a known ABI
1066 try {
1067 gABI = gApp.XPCOMABI;
1069 catch (e) {
1070 LOG("UpdateService", "XPCOM ABI unknown: updates are not possible.");
1073 var osVersion;
1074 var sysInfo = Components.classes["@mozilla.org/system-info;1"]
1075 .getService(Components.interfaces.nsIPropertyBag2);
1076 try {
1077 osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
1079 catch (e) {
1080 LOG("UpdateService", "OS Version unknown: updates are not possible.");
1083 if (osVersion) {
1084 try {
1085 osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
1087 catch (e) {
1088 // Not all platforms have a secondary widget library, so an error is nothing to worry about.
1090 gOSVersion = encodeURIComponent(osVersion);
1093 #ifdef XP_MACOSX
1094 // Mac universal build should report a different ABI than either macppc
1095 // or mactel.
1096 var macutils = Components.classes["@mozilla.org/xpcom/mac-utils;1"]
1097 .getService(Components.interfaces.nsIMacUtils);
1099 if (macutils.isUniversalBinary)
1100 gABI = "Universal-gcc3";
1101 #endif
1103 // Start the update timer only after a profile has been selected so that the
1104 // appropriate values for the update check are read from the user's profile.
1105 var os = getObserverService();
1107 os.addObserver(this, "profile-after-change", false);
1109 // Observe xpcom-shutdown to unhook pref branch observers above to avoid
1110 // shutdown leaks.
1111 os.addObserver(this, "xpcom-shutdown", false);
1114 UpdateService.prototype = {
1116 * The downloader we are using to download updates. There is only ever one of
1117 * these.
1119 _downloader: null,
1122 * Handle Observer Service notifications
1123 * @param subject
1124 * The subject of the notification
1125 * @param topic
1126 * The notification name
1127 * @param data
1128 * Additional data
1130 observe: function(subject, topic, data) {
1131 var os = getObserverService();
1133 switch (topic) {
1134 case "profile-after-change":
1135 os.removeObserver(this, "profile-after-change");
1136 this._start();
1137 break;
1138 case "xpcom-shutdown":
1139 os.removeObserver(this, "xpcom-shutdown");
1141 // Release Services
1142 gApp = null;
1143 gPref = null;
1144 gConsole = null;
1145 break;
1150 * Start the Update Service
1152 _start: function() {
1153 // Start logging
1154 this._initLoggingPrefs();
1156 // Clean up any extant updates
1157 this._postUpdateProcessing();
1159 // Register a background update check timer
1160 var tm =
1161 Components.classes["@mozilla.org/updates/timer-manager;1"]
1162 .getService(Components.interfaces.nsIUpdateTimerManager);
1163 var interval = getPref("getIntPref", PREF_APP_UPDATE_INTERVAL, 86400);
1164 tm.registerTimer("background-update-timer", this, interval);
1166 // Resume fetching...
1167 var um = Components.classes["@mozilla.org/updates/update-manager;1"]
1168 .getService(Components.interfaces.nsIUpdateManager);
1169 var activeUpdate = um.activeUpdate;
1170 if (activeUpdate) {
1171 var status = this.downloadUpdate(activeUpdate, true);
1172 if (status == STATE_NONE)
1173 cleanupActiveUpdate();
1178 * Perform post-processing on updates lingering in the updates directory
1179 * from a previous browser session - either report install failures (and
1180 * optionally attempt to fetch a different version if appropriate) or
1181 * notify the user of install success.
1183 _postUpdateProcessing: function() {
1184 // Detect installation failures and notify
1186 // Bail out if we don't have appropriate permissions
1187 if (!this.canUpdate)
1188 return;
1190 var status = readStatusFile(getUpdatesDir());
1192 // Make sure to cleanup after an update that failed for an unknown reason
1193 if (status == "null")
1194 status = null;
1196 var updRootKey = null;
1197 #ifdef XP_WIN
1198 function findPreviousUpdate(key) {
1199 var updateDir = getUpdatesDir(key);
1200 if (updateDir.exists()) {
1201 status = readStatusFile(updateDir);
1202 // Previous download should succeed. Otherwise, we will not be here!
1203 if (status == STATE_SUCCEEDED)
1204 updRootKey = key;
1205 else
1206 status = null;
1210 // required when updating from Fx 2.0.0.1 to 2.0.0.3 (or later)
1211 // on Windows Vista.
1212 if (status == null)
1213 findPreviousUpdate(KEY_UAPPDATA);
1215 // required to migrate from older versions.
1216 if (status == null)
1217 findPreviousUpdate(KEY_APPDIR);
1218 #endif
1220 if (status == STATE_DOWNLOADING) {
1221 LOG("UpdateService", "_postUpdateProcessing: patch found in " +
1222 "downloading state");
1224 else if (status != null) {
1225 // null status means the update.status file is not present, because either:
1226 // 1) no update was performed, and so there's no UI to show
1227 // 2) an update was attempted but failed during checking, transfer or
1228 // verification, and was cleaned up at that point, and UI notifying of
1229 // that error was shown at that stage.
1230 var um =
1231 Components.classes["@mozilla.org/updates/update-manager;1"].
1232 getService(Components.interfaces.nsIUpdateManager);
1233 var prompter =
1234 Components.classes["@mozilla.org/updates/update-prompt;1"].
1235 createInstance(Components.interfaces.nsIUpdatePrompt);
1237 var shouldCleanup = true;
1238 var update = um.activeUpdate;
1239 if (!update) {
1240 update = new Update(null);
1242 update.state = status;
1243 var sbs =
1244 Components.classes["@mozilla.org/intl/stringbundle;1"].
1245 getService(Components.interfaces.nsIStringBundleService);
1246 var bundle = sbs.createBundle(URI_UPDATES_PROPERTIES);
1247 if (status == STATE_SUCCEEDED) {
1248 update.statusText = bundle.GetStringFromName("installSuccess");
1250 // Update the patch's metadata.
1251 um.activeUpdate = update;
1253 LOG("UpdateService", "_postUpdateProcessing: Install Succeeded, Showing UI");
1254 prompter.showUpdateInstalled(update);
1255 #ifdef MOZ_SUNBIRD
1256 // we need to fix both nsPostUpdateWin.js and
1257 // the uninstaller to work for sunbird
1258 #else
1259 // Perform platform-specific post-update processing.
1260 if (POST_UPDATE_CONTRACTID in Components.classes) {
1261 Components.classes[POST_UPDATE_CONTRACTID].
1262 createInstance(Components.interfaces.nsIRunnable).run();
1264 #endif
1266 // Done with this update. Clean it up.
1267 cleanupActiveUpdate(updRootKey);
1269 else {
1270 // If we hit an error, then the error code will be included in the
1271 // status string following a colon. If we had an I/O error, then we
1272 // assume that the patch is not invalid, and we restage the patch so
1273 // that it can be attempted again the next time we restart.
1274 var ary = status.split(": ");
1275 update.state = ary[0];
1276 if (update.state == STATE_FAILED && ary[1]) {
1277 update.errorCode = ary[1];
1278 if (update.errorCode == WRITE_ERROR) {
1279 prompter.showUpdateError(update);
1280 writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
1281 writeVersionFile(getUpdatesDir(), update.extensionVersion);
1282 return;
1286 // Something went wrong with the patch application process.
1287 cleanupActiveUpdate();
1289 update.statusText = bundle.GetStringFromName("patchApplyFailure");
1290 var oldType = update.selectedPatch ? update.selectedPatch.type
1291 : "complete";
1292 if (update.selectedPatch && oldType == "partial") {
1293 // Partial patch application failed, try downloading the complete
1294 // update in the background instead.
1295 LOG("UpdateService", "_postUpdateProcessing: Install of Partial Patch " +
1296 "failed, downloading Complete Patch and maybe showing UI");
1297 var status = this.downloadUpdate(update, true);
1298 if (status == STATE_NONE)
1299 cleanupActiveUpdate();
1301 else {
1302 LOG("UpdateService", "_postUpdateProcessing: Install of Complete or " +
1303 "only patch failed. Showing error.");
1305 update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
1306 update.setProperty("patchingFailed", oldType);
1307 prompter.showUpdateError(update);
1310 else {
1311 LOG("UpdateService", "_postUpdateProcessing: No Status, No Update");
1316 * Initialize Logging preferences, formatted like so:
1317 * app.update.log.<moduleName> = <true|false>
1319 _initLoggingPrefs: function() {
1320 try {
1321 var ps = Components.classes["@mozilla.org/preferences-service;1"]
1322 .getService(Components.interfaces.nsIPrefService);
1323 var logBranch = ps.getBranch(PREF_APP_UPDATE_LOG_BRANCH);
1324 var modules = logBranch.getChildList("", { value: 0 });
1326 for (var i = 0; i < modules.length; ++i) {
1327 if (logBranch.prefHasUserValue(modules[i]))
1328 gLogEnabled[modules[i]] = logBranch.getBoolPref(modules[i]);
1331 catch (e) {
1336 * Notified when a timer fires
1337 * @param timer
1338 * The timer that fired
1340 notify: function(timer) {
1341 // If a download is in progress, then do nothing.
1342 if (this.isDownloading || this._downloader && this._downloader.patchIsStaged)
1343 return;
1345 var self = this;
1346 var listener = {
1348 * See nsIUpdateService.idl
1350 onProgress: function(request, position, totalSize) {
1354 * See nsIUpdateService.idl
1356 onCheckComplete: function(request, updates, updateCount) {
1357 self._selectAndInstallUpdate(updates);
1361 * See nsIUpdateService.idl
1363 onError: function(request, update) {
1364 LOG("Checker", "Error during background update: " + update.statusText);
1367 this.backgroundChecker.checkForUpdates(listener, false);
1371 * Determine whether or not an update requires user confirmation before it
1372 * can be installed.
1373 * @param update
1374 * The update to be installed
1375 * @returns true if a prompt UI should be shown asking the user if they want
1376 * to install the update, false if the update should just be
1377 * silently downloaded and installed.
1379 _shouldPrompt: function(update) {
1380 // There are two possible outcomes here:
1381 // 1. download and install the update automatically
1382 // 2. alert the user about the presence of an update before doing anything
1384 // The outcome we follow is determined as follows:
1386 // Note: all Major updates require notification and confirmation
1388 // Update Type Mode Incompatible Outcome
1389 // Major 0 Yes or No Notify and Confirm
1390 // Major 1 No Notify and Confirm
1391 // Major 1 Yes Notify and Confirm
1392 // Major 2 Yes or No Notify and Confirm
1393 // Minor 0 Yes or No Auto Install
1394 // Minor 1 No Auto Install
1395 // Minor 1 Yes Notify and Confirm
1396 // Minor 2 No Auto Install
1397 // Minor 2 Yes Notify and Confirm
1399 // In addition, if there is a license associated with an update, regardless
1400 // of type it must be agreed to.
1402 // If app.update.enabled is set to false, an update check is not performed
1403 // at all, and so none of the decision making above is entered into.
1405 if (update.type == "major") {
1406 LOG("Checker", "_shouldPrompt: Prompting because it is a major update");
1407 return true;
1410 update.QueryInterface(Components.interfaces.nsIPropertyBag);
1411 try {
1412 var licenseAccepted = update.getProperty("licenseAccepted") == "true";
1414 catch (e) {
1415 licenseAccepted = false;
1418 var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
1419 if (!updateEnabled) {
1420 LOG("Checker", "_shouldPrompt: Not prompting because update is " +
1421 "disabled");
1422 return false;
1425 // User has turned off automatic download and install
1426 var autoEnabled = getPref("getBoolPref", PREF_APP_UPDATE_AUTO, true);
1427 if (!autoEnabled) {
1428 LOG("Checker", "_shouldPrompt: Prompting because auto install is disabled");
1429 return true;
1432 switch (getPref("getIntPref", PREF_APP_UPDATE_MODE, 1)) {
1433 case 1:
1434 // Mode 1 is do not prompt only if there are no incompatibilities
1435 // releases
1436 LOG("Checker", "_shouldPrompt: Prompting if there are incompatibilities");
1437 return !isCompatible(update);
1438 case 2:
1439 // Mode 2 is do not prompt only if there are no incompatibilities
1440 LOG("Checker", "_shouldPrompt: Prompting if there are incompatibilities");
1441 return !isCompatible(update);
1443 // Mode 0 is do not prompt regardless of incompatibilities
1444 LOG("Checker", "_shouldPrompt: Not prompting the user - they choose to " +
1445 "ignore incompatibilities");
1446 return false;
1450 * Determine which of the specified updates should be installed.
1451 * @param updates
1452 * An array of available updates
1454 selectUpdate: function(updates) {
1455 if (updates.length == 0)
1456 return null;
1458 // Choose the newest of the available minor and major updates.
1459 var majorUpdate = null, minorUpdate = null;
1460 var newestMinor = updates[0], newestMajor = updates[0];
1462 var vc = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
1463 .getService(Components.interfaces.nsIVersionComparator);
1464 for (var i = 0; i < updates.length; ++i) {
1465 if (updates[i].type == "major" &&
1466 vc.compare(newestMajor.version, updates[i].version) <= 0)
1467 majorUpdate = newestMajor = updates[i];
1468 if (updates[i].type == "minor" &&
1469 vc.compare(newestMinor.version, updates[i].version) <= 0)
1470 minorUpdate = newestMinor = updates[i];
1473 // IMPORTANT
1474 // If there's a minor update, always try and fetch that one first,
1475 // otherwise use the newest major update.
1476 // selectUpdate() only returns one update.
1477 // if major were to trump minor, and we said "never" to the major
1478 // we'd never get the minor update, since selectUpdate()
1479 // would return the major update that the user said "never" to
1480 // (shadowing the important minor update with security fixes)
1481 return minorUpdate || majorUpdate;
1485 * Determine which of the specified updates should be installed and
1486 * begin the download/installation process, optionally prompting the
1487 * user for permission if required.
1488 * @param updates
1489 * An array of available updates
1491 _selectAndInstallUpdate: function(updates) {
1492 // Don't prompt if there's an active update - the user is already
1493 // aware and is downloading, or performed some user action to prevent
1494 // notification.
1495 var um = Components.classes["@mozilla.org/updates/update-manager;1"]
1496 .getService(Components.interfaces.nsIUpdateManager);
1497 if (um.activeUpdate)
1498 return;
1500 var update = this.selectUpdate(updates, updates.length);
1501 if (!update)
1502 return;
1504 // check if the user said "never" to this version
1505 // this check is done here, and not in selectUpdate() so that
1506 // the user can get an upgrade they said "never" to if they
1507 // manually do "Check for Updates..."
1508 // note, selectUpdate() only returns one update.
1509 // but in selectUpdate(), minor updates trump major updates
1510 // if major trumps minor, and we said "never" to the major
1511 // we'd never see the minor update.
1513 // note, the never decision should only apply to major updates
1514 // see bug #350636 for a scenario where this could potentially
1515 // be an issue
1517 // fix for bug #359093
1518 // version might one day come back from AUS as an
1519 // arbitrary (and possibly non ascii) string, so we need to encode it
1520 var neverPrefName = PREF_UPDATE_NEVER_BRANCH + encodeURIComponent(update.version);
1521 var never = getPref("getBoolPref", neverPrefName, false);
1522 if (never && update.type == "major")
1523 return;
1525 if (this._shouldPrompt(update))
1526 showPromptIfNoIncompatibilities(update);
1527 else {
1528 LOG("UpdateService", "_selectAndInstallUpdate: No need to show prompt, just download update");
1529 var status = this.downloadUpdate(update, true);
1530 if (status == STATE_NONE)
1531 cleanupActiveUpdate();
1536 * The Checker used for background update checks.
1538 _backgroundChecker: null,
1541 * See nsIUpdateService.idl
1543 get backgroundChecker() {
1544 if (!this._backgroundChecker)
1545 this._backgroundChecker = new Checker();
1546 return this._backgroundChecker;
1550 * See nsIUpdateService.idl
1552 get canUpdate() {
1553 try {
1554 var appDirFile = getUpdateFile([FILE_PERMS_TEST]);
1555 LOG("UpdateService", "canUpdate? testing " + appDirFile.path);
1556 if (!appDirFile.exists()) {
1557 appDirFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
1558 appDirFile.remove(false);
1560 var updateDir = getUpdatesDir();
1561 var upDirFile = updateDir.clone();
1562 upDirFile.append(FILE_PERMS_TEST);
1563 LOG("UpdateService", "canUpdate? testing " + upDirFile.path);
1564 if (!upDirFile.exists()) {
1565 upDirFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
1566 upDirFile.remove(false);
1568 #ifdef XP_WIN
1569 var sysInfo =
1570 Components.classes["@mozilla.org/system-info;1"]
1571 .getService(Components.interfaces.nsIPropertyBag2);
1573 // Example windowsVersion: Windows XP == 5.1
1574 var windowsVersion = sysInfo.getProperty("version");
1575 LOG("UpdateService", "canUpdate? windowsVersion = " + windowsVersion);
1577 // For Vista, updates can be performed to a location requiring
1578 // admin privileges by requesting elevation via the UAC prompt when
1579 // launching updater.exe if the appDir is under the Program Files
1580 // directory (e.g. C:\Program Files\) and UAC is turned on and
1581 // we can elevate (e.g. user has a split token)
1583 // Note: this does note attempt to handle the case where UAC is
1584 // turned on and the installation directory is in a restricted
1585 // location that requires admin privileges to update other than
1586 // Program Files.
1588 var userCanElevate = false;
1590 if (parseFloat(windowsVersion) >= 6) {
1591 try {
1592 var fileLocator =
1593 Components.classes["@mozilla.org/file/directory_service;1"]
1594 .getService(Components.interfaces.nsIProperties);
1595 // KEY_UPDROOT will fail and throw an exception if
1596 // appDir is not under the Program Files, so we rely on that
1597 var dir = fileLocator.get(KEY_UPDROOT, Components.interfaces.nsIFile);
1598 // appDir is under Program Files, so check if the user can elevate
1599 userCanElevate =
1600 gApp.QueryInterface(Components.interfaces.nsIWinAppHelper)
1601 .userCanElevate;
1602 LOG("UpdateService",
1603 "canUpdate? on Vista, userCanElevate = " + userCanElevate);
1605 catch (ex) {
1606 // When the installation directory is not under Program Files,
1607 // fall through to checking if write access to the
1608 // installation directory is available.
1609 LOG("UpdateService", "canUpdate: on Vista, appDir is not under " +
1610 "Program Files");
1614 // On Windows, we no longer store the update under the app dir
1615 // if the app dir is under C:\Program Files.
1617 // If we are on Windows (including Vista, if we can't elevate)
1618 // we need to check that
1619 // we can create and remove files from the actual app directory
1620 // (like C:\Program Files\Mozilla Firefox). If we can't
1621 // (because this user is not an adminstrator, for example)
1622 // canUpdate() should return false.
1624 // For Vista, we perform this check to enable updating the
1625 // application when the user has write access to the installation
1626 // directory under the following scenarios:
1627 // 1) the installation directory is not under Program Files
1628 // (e.g. C:\Program Files)
1629 // 2) UAC is turned off
1630 // 3) UAC is turned on and the user is not an admin
1631 // (e.g. the user does not have a split token)
1632 // 4) UAC is turned on and the user is already elevated,
1633 // so they can't be elevated again.
1634 if (!userCanElevate) {
1635 // if we're unable to create the test file
1636 // the code below will throw an exception
1637 var actualAppDir = getDir(KEY_APPDIR, []);
1638 var actualAppDirFile = actualAppDir.clone();
1639 actualAppDirFile.append(FILE_PERMS_TEST);
1640 LOG("UpdateService", "canUpdate? testing " + actualAppDirFile.path);
1641 if (!actualAppDirFile.exists()) {
1642 actualAppDirFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
1643 actualAppDirFile.remove(false);
1646 #endif
1648 catch (e) {
1649 LOG("UpdateService", "can't update, no privileges: " + e);
1650 // No write privileges to install directory
1651 return false;
1653 // If the administrator has locked the app update functionality
1654 // OFF - this is not just a user setting, so disable the manual
1655 // UI too.
1656 var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
1657 if (!enabled && gPref.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
1658 LOG("UpdateService", "can't update, disabled by pref");
1659 return false;
1662 // If we don't know the binary platform we're updating, we can't update.
1663 if (!gABI) {
1664 LOG("UpdateService", "can't update, unknown ABI");
1665 return false;
1668 // If we don't know the OS version we're updating, we can't update.
1669 if (!gOSVersion) {
1670 LOG("UpdateService", "can't update, unknown OS version");
1671 return false;
1674 LOG("UpdateService", "can update");
1675 return true;
1679 * See nsIUpdateService.idl
1681 addDownloadListener: function(listener) {
1682 if (!this._downloader) {
1683 LOG("UpdateService", "addDownloadListener: no downloader!\n");
1684 return;
1686 this._downloader.addDownloadListener(listener);
1690 * See nsIUpdateService.idl
1692 removeDownloadListener: function(listener) {
1693 if (!this._downloader) {
1694 LOG("UpdateService", "removeDownloadListener: no downloader!\n");
1695 return;
1697 this._downloader.removeDownloadListener(listener);
1701 * See nsIUpdateService.idl
1703 downloadUpdate: function(update, background) {
1704 if (!update)
1705 throw Components.results.NS_ERROR_NULL_POINTER;
1707 var ai = Components.classes["@mozilla.org/xre/app-info;1"].
1708 getService(Components.interfaces.nsIXULAppInfo);
1709 var vc = Components.classes["@mozilla.org/xpcom/version-comparator;1"].
1710 getService(Components.interfaces.nsIVersionComparator);
1711 // Don't download the update if the update's version is less than the
1712 // current application's version.
1713 if (update.version && vc.compare(update.version, ai.version) < 0) {
1714 LOG("UpdateService", "downloadUpdate: removing update for previous " +
1715 "application version " + update.version);
1716 cleanupActiveUpdate();
1717 return STATE_NONE;
1720 if (this.isDownloading) {
1721 if (update.isCompleteUpdate == this._downloader.isCompleteUpdate &&
1722 background == this._downloader.background) {
1723 LOG("UpdateService", "no support for downloading more than one update at a time");
1724 return readStatusFile(getUpdatesDir());
1726 this._downloader.cancel();
1728 this._downloader = new Downloader(background);
1729 return this._downloader.downloadUpdate(update);
1733 * See nsIUpdateService.idl
1735 pauseDownload: function() {
1736 if (this.isDownloading)
1737 this._downloader.cancel();
1741 * See nsIUpdateService.idl
1743 get isDownloading() {
1744 return this._downloader && this._downloader.isBusy;
1748 * See nsISupports.idl
1750 QueryInterface: function(iid) {
1751 if (!iid.equals(Components.interfaces.nsIApplicationUpdateService) &&
1752 !iid.equals(Components.interfaces.nsITimerCallback) &&
1753 !iid.equals(Components.interfaces.nsIObserver) &&
1754 !iid.equals(Components.interfaces.nsISupports))
1755 throw Components.results.NS_ERROR_NO_INTERFACE;
1756 return this;
1761 * A service to manage active and past updates.
1762 * @constructor
1764 function UpdateManager() {
1765 // Ensure the Active Update file is loaded
1766 var updates = this._loadXMLFileIntoArray(getUpdateFile([FILE_UPDATE_ACTIVE]));
1767 if (updates.length > 0)
1768 this._activeUpdate = updates[0];
1770 UpdateManager.prototype = {
1772 * All previously downloaded and installed updates, as an array of nsIUpdate
1773 * objects.
1775 _updates: null,
1778 * The current actively downloading/installing update, as a nsIUpdate object.
1780 _activeUpdate: null,
1783 * Loads an updates.xml formatted file into an array of nsIUpdate items.
1784 * @param file
1785 * A nsIFile for the updates.xml file
1786 * @returns The array of nsIUpdate items held in the file.
1788 _loadXMLFileIntoArray: function(file) {
1789 if (!file.exists()) {
1790 LOG("UpdateManager", "_loadXMLFileIntoArray: XML File does not exist");
1791 return [];
1794 var result = [];
1795 var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
1796 .createInstance(Components.interfaces.nsIFileInputStream);
1797 fileStream.init(file, MODE_RDONLY, PERMS_FILE, 0);
1798 try {
1799 var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
1800 .createInstance(Components.interfaces.nsIDOMParser);
1801 var doc = parser.parseFromStream(fileStream, "UTF-8", fileStream.available(), "text/xml");
1803 var updateCount = doc.documentElement.childNodes.length;
1804 for (var i = 0; i < updateCount; ++i) {
1805 var updateElement = doc.documentElement.childNodes.item(i);
1806 if (updateElement.nodeType != Node.ELEMENT_NODE ||
1807 updateElement.localName != "update")
1808 continue;
1810 updateElement.QueryInterface(Components.interfaces.nsIDOMElement);
1811 try {
1812 var update = new Update(updateElement);
1813 } catch (e) {
1814 LOG("UpdateManager", "_loadXMLFileIntoArray: invalid update");
1815 continue;
1817 result.push(update);
1820 catch (e) {
1821 LOG("UpdateManager", "_loadXMLFileIntoArray: Error constructing update list " +
1824 fileStream.close();
1825 return result;
1829 * Load the update manager, initializing state from state files.
1831 _ensureUpdates: function() {
1832 if (!this._updates) {
1833 this._updates = this._loadXMLFileIntoArray(getUpdateFile(
1834 [FILE_UPDATES_DB]));
1836 var activeUpdates = this._loadXMLFileIntoArray(getUpdateFile(
1837 [FILE_UPDATE_ACTIVE]));
1838 if (activeUpdates.length > 0)
1839 this._activeUpdate = activeUpdates[0];
1844 * See nsIUpdateService.idl
1846 getUpdateAt: function(index) {
1847 this._ensureUpdates();
1848 return this._updates[index];
1852 * See nsIUpdateService.idl
1854 get updateCount() {
1855 this._ensureUpdates();
1856 return this._updates.length;
1860 * See nsIUpdateService.idl
1862 get activeUpdate() {
1863 if (this._activeUpdate &&
1864 this._activeUpdate.channel != getUpdateChannel()) {
1865 // User switched channels, clear out any old active updates and remove
1866 // partial downloads
1867 this._activeUpdate = null;
1869 // Destroy the updates directory, since we're done with it.
1870 cleanUpUpdatesDir();
1872 return this._activeUpdate;
1874 set activeUpdate(activeUpdate) {
1875 this._addUpdate(activeUpdate);
1876 this._activeUpdate = activeUpdate;
1877 if (!activeUpdate) {
1878 // If |activeUpdate| is null, we have updated both lists - the active list
1879 // and the history list, so we want to write both files.
1880 this.saveUpdates();
1882 else
1883 this._writeUpdatesToXMLFile([this._activeUpdate],
1884 getUpdateFile([FILE_UPDATE_ACTIVE]));
1885 return activeUpdate;
1889 * Add an update to the Updates list. If the item already exists in the list,
1890 * replace the existing value with the new value.
1891 * @param update
1892 * The nsIUpdate object to add.
1894 _addUpdate: function(update) {
1895 if (!update)
1896 return;
1897 this._ensureUpdates();
1898 if (this._updates) {
1899 for (var i = 0; i < this._updates.length; ++i) {
1900 if (this._updates[i] &&
1901 this._updates[i].version == update.version &&
1902 this._updates[i].buildID == update.buildID) {
1903 // Replace the existing entry with the new value, updating
1904 // all metadata.
1905 this._updates[i] = update;
1906 return;
1910 // Otherwise add it to the front of the list.
1911 this._updates.unshift(update);
1915 * Serializes an array of updates to an XML file
1916 * @param updates
1917 * An array of nsIUpdate objects
1918 * @param file
1919 * The nsIFile object to serialize to
1921 _writeUpdatesToXMLFile: function(updates, file) {
1922 var fos = Components.classes["@mozilla.org/network/safe-file-output-stream;1"]
1923 .createInstance(Components.interfaces.nsIFileOutputStream);
1924 var modeFlags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
1925 if (!file.exists())
1926 file.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
1927 fos.init(file, modeFlags, PERMS_FILE, 0);
1929 try {
1930 var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
1931 .createInstance(Components.interfaces.nsIDOMParser);
1932 const EMPTY_UPDATES_DOCUMENT = "<?xml version=\"1.0\"?><updates xmlns=\"http://www.mozilla.org/2005/app-update\"></updates>";
1933 var doc = parser.parseFromString(EMPTY_UPDATES_DOCUMENT, "text/xml");
1935 for (var i = 0; i < updates.length; ++i) {
1936 if (updates[i])
1937 doc.documentElement.appendChild(updates[i].serialize(doc));
1940 var serializer = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"]
1941 .createInstance(Components.interfaces.nsIDOMSerializer);
1942 serializer.serializeToStream(doc.documentElement, fos, null);
1944 catch (e) {
1947 closeSafeOutputStream(fos);
1951 * See nsIUpdateService.idl
1953 saveUpdates: function() {
1954 this._writeUpdatesToXMLFile([this._activeUpdate],
1955 getUpdateFile([FILE_UPDATE_ACTIVE]));
1956 if (this._activeUpdate)
1957 this._addUpdate(this._activeUpdate);
1959 // Don't write updates that have a temporary state to the updates.xml file.
1960 if (this._updates) {
1961 var updates = this._updates.slice();
1962 for (var i = updates.length - 1; i >= 0; --i) {
1963 var state = updates[i].state;
1964 if (state == STATE_NONE || state == STATE_DOWNLOADING ||
1965 state == STATE_PENDING) {
1966 updates.splice(i, 1);
1970 this._writeUpdatesToXMLFile(updates.slice(0, 10),
1971 getUpdateFile([FILE_UPDATES_DB]));
1976 * See nsISupports.idl
1978 QueryInterface: function(iid) {
1979 if (!iid.equals(Components.interfaces.nsIUpdateManager) &&
1980 !iid.equals(Components.interfaces.nsISupports))
1981 throw Components.results.NS_ERROR_NO_INTERFACE;
1982 return this;
1988 * Checker
1989 * Checks for new Updates
1990 * @constructor
1992 function Checker() {
1994 Checker.prototype = {
1996 * The XMLHttpRequest object that performs the connection.
1998 _request : null,
2001 * The nsIUpdateCheckListener callback
2003 _callback : null,
2006 * The URL of the update service XML file to connect to that contains details
2007 * about available updates.
2009 getUpdateURL: function(force) {
2010 this._forced = force;
2012 var defaults =
2013 gPref.QueryInterface(Components.interfaces.nsIPrefService).
2014 getDefaultBranch(null);
2016 // Use the override URL if specified.
2017 var url = getPref("getCharPref", PREF_APP_UPDATE_URL_OVERRIDE, null);
2019 // Otherwise, construct the update URL from component parts.
2020 if (!url) {
2021 try {
2022 url = defaults.getCharPref(PREF_APP_UPDATE_URL);
2023 } catch (e) {
2027 if (!url || url == "") {
2028 LOG("Checker", "Update URL not defined");
2029 return null;
2032 url = url.replace(/%PRODUCT%/g, gApp.name);
2033 url = url.replace(/%VERSION%/g, gApp.version);
2034 url = url.replace(/%BUILD_ID%/g, gApp.appBuildID);
2035 url = url.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI);
2036 url = url.replace(/%OS_VERSION%/g, gOSVersion);
2037 url = url.replace(/%LOCALE%/g, getLocale());
2038 url = url.replace(/%CHANNEL%/g, getUpdateChannel());
2039 url = url.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion);
2040 url = url.replace(/%DISTRIBUTION%/g,
2041 getDistributionPrefValue(PREF_APP_DISTRIBUTION));
2042 url = url.replace(/%DISTRIBUTION_VERSION%/g,
2043 getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
2044 url = url.replace(/\+/g, "%2B");
2046 if (force)
2047 url += "?force=1"
2049 LOG("Checker", "update url: " + url);
2050 return url;
2054 * See nsIUpdateService.idl
2056 checkForUpdates: function(listener, force) {
2057 if (!listener)
2058 throw Components.results.NS_ERROR_NULL_POINTER;
2060 if (!this.getUpdateURL(force) || (!this.enabled && !force))
2061 return;
2063 this._request =
2064 Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
2065 createInstance(Components.interfaces.nsIXMLHttpRequest);
2066 this._request.open("GET", this.getUpdateURL(force), true);
2067 this._request.channel.notificationCallbacks = new BadCertHandler();
2068 this._request.overrideMimeType("text/xml");
2069 this._request.setRequestHeader("Cache-Control", "no-cache");
2071 var self = this;
2072 this._request.onerror = function(event) { self.onError(event); };
2073 this._request.onload = function(event) { self.onLoad(event); };
2074 this._request.onprogress = function(event) { self.onProgress(event); };
2076 LOG("Checker", "checkForUpdates: sending request to " + this.getUpdateURL(force));
2077 this._request.send(null);
2079 this._callback = listener;
2083 * When progress associated with the XMLHttpRequest is received.
2084 * @param event
2085 * The nsIDOMLSProgressEvent for the load.
2087 onProgress: function(event) {
2088 LOG("Checker", "onProgress: " + event.position + "/" + event.totalSize);
2089 this._callback.onProgress(event.target, event.position, event.totalSize);
2093 * Returns an array of nsIUpdate objects discovered by the update check.
2095 get _updates() {
2096 var updatesElement = this._request.responseXML.documentElement;
2097 if (!updatesElement) {
2098 LOG("Checker", "get_updates: empty updates document?!");
2099 return [];
2102 if (updatesElement.nodeName != "updates") {
2103 LOG("Checker", "get_updates: unexpected node name!");
2104 throw "";
2107 var updates = [];
2108 for (var i = 0; i < updatesElement.childNodes.length; ++i) {
2109 var updateElement = updatesElement.childNodes.item(i);
2110 if (updateElement.nodeType != Node.ELEMENT_NODE ||
2111 updateElement.localName != "update")
2112 continue;
2114 updateElement.QueryInterface(Components.interfaces.nsIDOMElement);
2115 try {
2116 var update = new Update(updateElement);
2117 } catch (e) {
2118 LOG("Checker", "Invalid <update/>, ignoring...");
2119 continue;
2121 update.serviceURL = this.getUpdateURL(this._forced);
2122 update.channel = getUpdateChannel();
2123 updates.push(update);
2126 return updates;
2130 * The XMLHttpRequest succeeded and the document was loaded.
2131 * @param event
2132 * The nsIDOMLSEvent for the load
2134 onLoad: function(event) {
2135 LOG("Checker", "onLoad: request completed downloading document");
2137 try {
2138 checkCert(this._request.channel);
2140 // Analyze the resulting DOM and determine the set of updates to install
2141 var updates = this._updates;
2143 LOG("Checker", "Updates available: " + updates.length);
2145 // ... and tell the Update Service about what we discovered.
2146 this._callback.onCheckComplete(event.target, updates, updates.length);
2148 catch (e) {
2149 LOG("Checker", "There was a problem with the update service URL specified, " +
2150 "either the XML file was malformed or it does not exist at the location " +
2151 "specified. Exception: " + e);
2152 var update = new Update(null);
2153 update.statusText = getStatusTextFromCode(404, 404);
2154 this._callback.onError(event.target, update);
2157 this._request = null;
2161 * There was an error of some kind during the XMLHttpRequest
2162 * @param event
2163 * The nsIDOMLSEvent for the load
2165 onError: function(event) {
2166 LOG("Checker", "onError: error during load");
2168 var request = event.target;
2169 try {
2170 var status = request.status;
2172 catch (e) {
2173 var req = request.channel.QueryInterface(Components.interfaces.nsIRequest);
2174 status = req.status;
2177 // If we can't find an error string specific to this status code,
2178 // just use the 200 message from above, which means everything
2179 // "looks" fine but there was probably an XML error or a bogus file.
2180 var update = new Update(null);
2181 update.statusText = getStatusTextFromCode(status, 200);
2182 this._callback.onError(request, update);
2184 this._request = null;
2188 * Whether or not we are allowed to do update checking.
2190 _enabled: true,
2193 * See nsIUpdateService.idl
2195 get enabled() {
2196 var aus =
2197 Components.classes["@mozilla.org/updates/update-service;1"].
2198 getService(Components.interfaces.nsIApplicationUpdateService);
2199 var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) &&
2200 aus.canUpdate && this._enabled;
2201 return enabled;
2205 * See nsIUpdateService.idl
2207 stopChecking: function(duration) {
2208 // Always stop the current check
2209 if (this._request)
2210 this._request.abort();
2212 const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker;
2213 switch (duration) {
2214 case nsIUpdateChecker.CURRENT_SESSION:
2215 this._enabled = false;
2216 break;
2217 case nsIUpdateChecker.ANY_CHECKS:
2218 this._enabled = false;
2219 gPref.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled);
2220 break;
2225 * See nsISupports.idl
2227 QueryInterface: function(iid) {
2228 if (!iid.equals(Components.interfaces.nsIUpdateChecker) &&
2229 !iid.equals(Components.interfaces.nsISupports))
2230 throw Components.results.NS_ERROR_NO_INTERFACE;
2231 return this;
2236 * Manages the download of updates
2237 * @param background
2238 * Whether or not this downloader is operating in background
2239 * update mode.
2240 * @constructor
2242 function Downloader(background) {
2243 this.background = background;
2245 Downloader.prototype = {
2247 * The nsIUpdatePatch that we are downloading
2249 _patch: null,
2252 * The nsIUpdate that we are downloading
2254 _update: null,
2257 * The nsIIncrementalDownload object handling the download
2259 _request: null,
2262 * Whether or not the update being downloaded is a complete replacement of
2263 * the user's existing installation or a patch representing the difference
2264 * between the new version and the previous version.
2266 isCompleteUpdate: null,
2269 * Cancels the active download.
2271 cancel: function() {
2272 if (this._request &&
2273 this._request instanceof Components.interfaces.nsIRequest) {
2274 const NS_BINDING_ABORTED = 0x804b0002;
2275 this._request.cancel(NS_BINDING_ABORTED);
2280 * Whether or not a patch has been downloaded and staged for installation.
2282 get patchIsStaged() {
2283 return readStatusFile(getUpdatesDir()) == STATE_PENDING;
2287 * Verify the downloaded file. We assume that the download is complete at
2288 * this point.
2290 _verifyDownload: function() {
2291 if (!this._request)
2292 return false;
2294 var destination = this._request.destination;
2296 // Ensure that the file size matches the expected file size.
2297 if (destination.fileSize != this._patch.size)
2298 return false;
2300 var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
2301 createInstance(nsIFileInputStream);
2302 fileStream.init(destination, MODE_RDONLY, PERMS_FILE, 0);
2304 try {
2305 var hash = Components.classes["@mozilla.org/security/hash;1"].
2306 createInstance(nsICryptoHash);
2307 var hashFunction = nsICryptoHash[this._patch.hashFunction.toUpperCase()];
2308 if (hashFunction == undefined)
2309 throw Components.results.NS_ERROR_UNEXPECTED;
2310 hash.init(hashFunction);
2311 hash.updateFromStream(fileStream, -1);
2312 // NOTE: For now, we assume that the format of _patch.hashValue is hex
2313 // encoded binary (such as what is typically output by programs like
2314 // sha1sum). In the future, this may change to base64 depending on how
2315 // we choose to compute these hashes.
2316 digest = binaryToHex(hash.finish(false));
2317 } catch (e) {
2318 LOG("Downloader", "failed to compute hash of downloaded update archive");
2319 digest = "";
2322 fileStream.close();
2324 return digest == this._patch.hashValue.toLowerCase();
2328 * Select the patch to use given the current state of updateDir and the given
2329 * set of update patches.
2330 * @param update
2331 * A nsIUpdate object to select a patch from
2332 * @param updateDir
2333 * A nsIFile representing the update directory
2334 * @returns A nsIUpdatePatch object to download
2336 _selectPatch: function(update, updateDir) {
2337 // Given an update to download, we will always try to download the patch
2338 // for a partial update over the patch for a full update.
2341 * Return the first UpdatePatch with the given type.
2342 * @param type
2343 * The type of the patch ("complete" or "partial")
2344 * @returns A nsIUpdatePatch object matching the type specified
2346 function getPatchOfType(type) {
2347 for (var i = 0; i < update.patchCount; ++i) {
2348 var patch = update.getPatchAt(i);
2349 if (patch && patch.type == type)
2350 return patch;
2352 return null;
2355 // Look to see if any of the patches in the Update object has been
2356 // pre-selected for download, otherwise we must figure out which one
2357 // to select ourselves.
2358 var selectedPatch = update.selectedPatch;
2360 var state = readStatusFile(updateDir);
2362 // If this is a patch that we know about, then select it. If it is a patch
2363 // that we do not know about, then remove it and use our default logic.
2364 var useComplete = false;
2365 if (selectedPatch) {
2366 LOG("Downloader", "found existing patch [state="+state+"]");
2367 switch (state) {
2368 case STATE_DOWNLOADING:
2369 LOG("Downloader", "resuming download");
2370 return selectedPatch;
2371 case STATE_PENDING:
2372 LOG("Downloader", "already downloaded and staged");
2373 return null;
2374 default:
2375 // Something went wrong when we tried to apply the previous patch.
2376 // Try the complete patch next time.
2377 if (update && selectedPatch.type == "partial") {
2378 useComplete = true;
2379 } else {
2380 // This is a pretty fatal error. Just bail.
2381 LOG("Downloader", "failed to apply complete patch!");
2382 writeStatusFile(updateDir, STATE_NONE);
2383 writeVersionFile(getUpdatesDir(), null);
2384 return null;
2388 selectedPatch = null;
2391 // If we were not able to discover an update from a previous download, we
2392 // select the best patch from the given set.
2393 var partialPatch = getPatchOfType("partial");
2394 if (!useComplete)
2395 selectedPatch = partialPatch;
2396 if (!selectedPatch) {
2397 if (partialPatch)
2398 partialPatch.selected = false;
2399 selectedPatch = getPatchOfType("complete");
2402 // Restore the updateDir since we may have deleted it.
2403 updateDir = getUpdatesDir();
2405 // if update only contains a partial patch, selectedPatch == null here if
2406 // the partial patch has been attempted and fails and we're trying to get a
2407 // complete patch
2408 if (selectedPatch)
2409 selectedPatch.selected = true;
2411 update.isCompleteUpdate = useComplete;
2413 // Reset the Active Update object on the Update Manager and flush the
2414 // Active Update DB.
2415 var um = Components.classes["@mozilla.org/updates/update-manager;1"]
2416 .getService(Components.interfaces.nsIUpdateManager);
2417 um.activeUpdate = update;
2419 return selectedPatch;
2423 * Whether or not we are currently downloading something.
2425 get isBusy() {
2426 return this._request != null;
2430 * Download and stage the given update.
2431 * @param update
2432 * A nsIUpdate object to download a patch for. Cannot be null.
2434 downloadUpdate: function(update) {
2435 if (!update)
2436 throw Components.results.NS_ERROR_NULL_POINTER;
2438 var updateDir = getUpdatesDir();
2440 this._update = update;
2442 // This function may return null, which indicates that there are no patches
2443 // to download.
2444 this._patch = this._selectPatch(update, updateDir);
2445 if (!this._patch) {
2446 LOG("Downloader", "no patch to download");
2447 return readStatusFile(updateDir);
2449 this.isCompleteUpdate = this._patch.type == "complete";
2451 var patchFile = updateDir.clone();
2452 patchFile.append(FILE_UPDATE_ARCHIVE);
2454 var ios = Components.classes["@mozilla.org/network/io-service;1"].
2455 getService(Components.interfaces.nsIIOService);
2456 var uri = ios.newURI(this._patch.URL, null, null);
2458 this._request =
2459 Components.classes["@mozilla.org/network/incremental-download;1"].
2460 createInstance(nsIIncrementalDownload);
2462 LOG("Downloader", "downloadUpdate: Downloading from " + uri.spec + " to " +
2463 patchFile.path);
2465 var interval = this.background ? DOWNLOAD_BACKGROUND_INTERVAL
2466 : DOWNLOAD_FOREGROUND_INTERVAL;
2467 this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval);
2468 this._request.start(this, null);
2470 writeStatusFile(updateDir, STATE_DOWNLOADING);
2471 this._patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
2472 this._patch.state = STATE_DOWNLOADING;
2473 var um = Components.classes["@mozilla.org/updates/update-manager;1"]
2474 .getService(Components.interfaces.nsIUpdateManager);
2475 um.saveUpdates();
2476 return STATE_DOWNLOADING;
2480 * An array of download listeners to notify when we receive
2481 * nsIRequestObserver or nsIProgressEventSink method calls.
2483 _listeners: [],
2486 * Adds a listener to the download process
2487 * @param listener
2488 * A download listener, implementing nsIRequestObserver and
2489 * nsIProgressEventSink
2491 addDownloadListener: function(listener) {
2492 for (var i = 0; i < this._listeners.length; ++i) {
2493 if (this._listeners[i] == listener)
2494 return;
2496 this._listeners.push(listener);
2500 * Removes a download listener
2501 * @param listener
2502 * The listener to remove.
2504 removeDownloadListener: function(listener) {
2505 for (var i = 0; i < this._listeners.length; ++i) {
2506 if (this._listeners[i] == listener) {
2507 this._listeners.splice(i, 1);
2508 return;
2514 * When the async request begins
2515 * @param request
2516 * The nsIRequest object for the transfer
2517 * @param context
2518 * Additional data
2520 onStartRequest: function(request, context) {
2521 request.QueryInterface(nsIIncrementalDownload);
2522 LOG("Downloader", "onStartRequest: " + request.URI.spec);
2524 var listenerCount = this._listeners.length;
2525 for (var i = 0; i < listenerCount; ++i)
2526 this._listeners[i].onStartRequest(request, context);
2530 * When new data has been downloaded
2531 * @param request
2532 * The nsIRequest object for the transfer
2533 * @param context
2534 * Additional data
2535 * @param progress
2536 * The current number of bytes transferred
2537 * @param maxProgress
2538 * The total number of bytes that must be transferred
2540 onProgress: function(request, context, progress, maxProgress) {
2541 request.QueryInterface(nsIIncrementalDownload);
2542 LOG("Downloader.onProgress", "onProgress: " + request.URI.spec + ", " + progress + "/" + maxProgress);
2544 var listenerCount = this._listeners.length;
2545 for (var i = 0; i < listenerCount; ++i) {
2546 var listener = this._listeners[i];
2547 if (listener instanceof Components.interfaces.nsIProgressEventSink)
2548 listener.onProgress(request, context, progress, maxProgress);
2553 * When we have new status text
2554 * @param request
2555 * The nsIRequest object for the transfer
2556 * @param context
2557 * Additional data
2558 * @param status
2559 * A status code
2560 * @param statusText
2561 * Human readable version of |status|
2563 onStatus: function(request, context, status, statusText) {
2564 request.QueryInterface(nsIIncrementalDownload);
2565 LOG("Downloader", "onStatus: " + request.URI.spec + " status = " + status + ", text = " + statusText);
2566 var listenerCount = this._listeners.length;
2567 for (var i = 0; i < listenerCount; ++i) {
2568 var listener = this._listeners[i];
2569 if (listener instanceof Components.interfaces.nsIProgressEventSink)
2570 listener.onStatus(request, context, status, statusText);
2575 * When data transfer ceases
2576 * @param request
2577 * The nsIRequest object for the transfer
2578 * @param context
2579 * Additional data
2580 * @param status
2581 * Status code containing the reason for the cessation.
2583 onStopRequest: function(request, context, status) {
2584 request.QueryInterface(nsIIncrementalDownload);
2585 LOG("Downloader", "onStopRequest: " + request.URI.spec + ", status = " + status);
2587 var state = this._patch.state;
2588 var shouldShowPrompt = false;
2589 var deleteActiveUpdate = false;
2590 const NS_BINDING_ABORTED = 0x804b0002;
2591 const NS_ERROR_ABORT = 0x80004004;
2592 if (Components.isSuccessCode(status)) {
2593 var sbs =
2594 Components.classes["@mozilla.org/intl/stringbundle;1"].
2595 getService(Components.interfaces.nsIStringBundleService);
2596 var updateStrings = sbs.createBundle(URI_UPDATES_PROPERTIES);
2597 if (this._verifyDownload()) {
2598 state = STATE_PENDING;
2600 // We only need to explicitly show the prompt if this is a backround
2601 // download, since otherwise some kind of UI is already visible and
2602 // that UI will notify.
2603 if (this.background)
2604 shouldShowPrompt = true;
2606 // Tell the updater.exe we're ready to apply.
2607 writeStatusFile(getUpdatesDir(), state);
2608 writeVersionFile(getUpdatesDir(), this._update.extensionVersion);
2609 this._update.installDate = (new Date()).getTime();
2610 this._update.statusText = updateStrings.
2611 GetStringFromName("installPending");
2612 } else {
2613 LOG("Downloader", "onStopRequest: download verification failed");
2614 state = STATE_DOWNLOAD_FAILED;
2616 var brandStrings = sbs.createBundle(URI_BRAND_PROPERTIES);
2617 var brandShortName = brandStrings.GetStringFromName("brandShortName");
2618 this._update.statusText = updateStrings.
2619 formatStringFromName("verificationError", [brandShortName], 1);
2621 // TODO: use more informative error code here
2622 status = Components.results.NS_ERROR_UNEXPECTED;
2624 var message = getStatusTextFromCode("verification_failed",
2625 "verification_failed");
2626 this._update.statusText = message;
2628 if (this._update.isCompleteUpdate)
2629 deleteActiveUpdate = true;
2631 // Destroy the updates directory, since we're done with it.
2632 cleanUpUpdatesDir();
2635 else if (status != NS_BINDING_ABORTED &&
2636 status != NS_ERROR_ABORT) {
2637 LOG("Downloader", "onStopRequest: Non-verification failure");
2638 // Some sort of other failure, log this in the |statusText| property
2639 state = STATE_DOWNLOAD_FAILED;
2641 // XXXben - if |request| (The Incremental Download) provided a means
2642 // for accessing the http channel we could do more here.
2644 const NS_BINDING_FAILED = 2152398849;
2645 this._update.statusText = getStatusTextFromCode(status,
2646 NS_BINDING_FAILED);
2648 // Destroy the updates directory, since we're done with it.
2649 cleanUpUpdatesDir();
2651 deleteActiveUpdate = true;
2653 LOG("Downloader", "Setting state to: " + state);
2654 this._patch.state = state;
2655 var um =
2656 Components.classes["@mozilla.org/updates/update-manager;1"].
2657 getService(Components.interfaces.nsIUpdateManager);
2658 if (deleteActiveUpdate) {
2659 this._update.installDate = (new Date()).getTime();
2660 um.activeUpdate = null;
2662 else {
2663 um.activeUpdate.state = state;
2665 um.saveUpdates();
2667 var listenerCount = this._listeners.length;
2668 for (var i = 0; i < listenerCount; ++i)
2669 this._listeners[i].onStopRequest(request, context, status);
2671 this._request = null;
2673 if (state == STATE_DOWNLOAD_FAILED) {
2674 if (!this._update.isCompleteUpdate) {
2675 var allFailed = true;
2677 // If we were downloading a patch and the patch verification phase
2678 // failed, log this and then commence downloading the complete update.
2679 LOG("Downloader", "onStopRequest: Verification of patch failed, downloading complete update");
2680 this._update.isCompleteUpdate = true;
2681 var status = this.downloadUpdate(this._update);
2683 if (status == STATE_NONE) {
2684 cleanupActiveUpdate();
2685 } else {
2686 allFailed = false;
2688 // This will reset the |.state| property on this._update if a new
2689 // download initiates.
2692 // if we still fail after trying a complete download, give up completely
2693 if (allFailed) {
2694 // In all other failure cases, i.e. we're S.O.L. - no more failing over
2695 // ...
2697 // If this was ever a foreground download, and now there is no UI active
2698 // (e.g. because the user closed the download window) and there was an
2699 // error, we must notify now. Otherwise we can keep the failure to
2700 // ourselves since the user won't be expecting it.
2701 try {
2702 this._update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
2703 var fgdl = this._update.getProperty("foregroundDownload");
2705 catch (e) {
2708 if (fgdl == "true") {
2709 var prompter =
2710 Components.classes["@mozilla.org/updates/update-prompt;1"].
2711 createInstance(Components.interfaces.nsIUpdatePrompt);
2712 this._update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
2713 this._update.setProperty("downloadFailed", "true");
2714 prompter.showUpdateError(this._update);
2718 // the complete download succeeded or total failure was handled, so exit
2719 return;
2722 // Do this after *everything* else, since it will likely cause the app
2723 // to shut down.
2724 if (shouldShowPrompt) {
2725 // Notify the user that an update has been downloaded and is ready for
2726 // installation (i.e. that they should restart the application). We do
2727 // not notify on failed update attempts.
2728 var prompter =
2729 Components.classes["@mozilla.org/updates/update-prompt;1"].
2730 createInstance(Components.interfaces.nsIUpdatePrompt);
2731 prompter.showUpdateDownloaded(this._update, true);
2736 * See nsIInterfaceRequestor.idl
2738 getInterface: function(iid) {
2739 // The network request may require proxy authentication, so provide the
2740 // default nsIAuthPrompt if requested.
2741 if (iid.equals(Components.interfaces.nsIAuthPrompt)) {
2742 var prompt =
2743 Components.classes["@mozilla.org/network/default-auth-prompt;1"].
2744 createInstance();
2745 return prompt.QueryInterface(iid);
2747 throw Components.results.NS_ERROR_NO_INTERFACE;
2751 * See nsISupports.idl
2753 QueryInterface: function(iid) {
2754 if (!iid.equals(Components.interfaces.nsIRequestObserver) &&
2755 !iid.equals(Components.interfaces.nsIProgressEventSink) &&
2756 !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
2757 !iid.equals(Components.interfaces.nsISupports))
2758 throw Components.results.NS_ERROR_NO_INTERFACE;
2759 return this;
2764 * A manager for update check timers. Manages timers that fire over long
2765 * periods of time (e.g. days, weeks).
2766 * @constructor
2768 function TimerManager() {
2769 getObserverService().addObserver(this, "xpcom-shutdown", false);
2771 const nsITimer = Components.interfaces.nsITimer;
2772 this._timer = Components.classes["@mozilla.org/timer;1"]
2773 .createInstance(nsITimer);
2774 var timerInterval = getPref("getIntPref", PREF_APP_UPDATE_TIMER, 600000);
2775 this._timer.initWithCallback(this, timerInterval,
2776 nsITimer.TYPE_REPEATING_SLACK);
2778 TimerManager.prototype = {
2780 * See nsIObserver.idl
2782 observe: function(subject, topic, data) {
2783 if (topic == "xpcom-shutdown") {
2784 getObserverService().removeObserver(this, "xpcom-shutdown");
2786 // Release everything we hold onto.
2787 for (var timerID in this._timers)
2788 delete this._timers[timerID];
2789 this._timer = null;
2790 this._timers = null;
2795 * The Checker Timer
2797 _timer: null,
2800 * The set of registered timers.
2802 _timers: { },
2805 * Called when the checking timer fires.
2806 * @param timer
2807 * The checking timer that fired.
2809 notify: function(timer) {
2810 for (var timerID in this._timers) {
2811 var timerData = this._timers[timerID];
2812 var lastUpdateTime = timerData.lastUpdateTime;
2813 var now = Math.round(Date.now() / 1000);
2815 // Fudge the lastUpdateTime by some random increment of the update
2816 // check interval (e.g. some random slice of 10 minutes) so that when
2817 // the time comes to check, we offset each client request by a random
2818 // amount so they don't all hit at once. app.update.timer is in milliseconds,
2819 // whereas app.update.lastUpdateTime is in seconds
2820 var timerInterval = getPref("getIntPref", PREF_APP_UPDATE_TIMER, 600000);
2821 lastUpdateTime += Math.round(Math.random() * timerInterval / 1000);
2823 if ((now - lastUpdateTime) > timerData.interval &&
2824 timerData.callback instanceof Components.interfaces.nsITimerCallback) {
2825 timerData.callback.notify(timer);
2826 timerData.lastUpdateTime = now;
2827 var preference = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
2828 gPref.setIntPref(preference, now);
2834 * See nsIUpdateService.idl
2836 registerTimer: function(id, callback, interval) {
2837 var preference = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
2838 var now = Math.round(Date.now() / 1000);
2839 var lastUpdateTime = null;
2840 if (gPref.prefHasUserValue(preference)) {
2841 lastUpdateTime = gPref.getIntPref(preference);
2842 } else {
2843 gPref.setIntPref(preference, now);
2844 lastUpdateTime = now;
2846 this._timers[id] = { callback : callback,
2847 interval : interval,
2848 lastUpdateTime : lastUpdateTime };
2852 * See nsISupports.idl
2854 QueryInterface: function(iid) {
2855 if (!iid.equals(Components.interfaces.nsIUpdateTimerManager) &&
2856 !iid.equals(Components.interfaces.nsITimerCallback) &&
2857 !iid.equals(Components.interfaces.nsIObserver) &&
2858 !iid.equals(Components.interfaces.nsISupports))
2859 throw Components.results.NS_ERROR_NO_INTERFACE;
2860 return this;
2864 #ifdef MOZ_XUL_APP
2866 * UpdatePrompt
2867 * An object which can prompt the user with information about updates, request
2868 * action, etc. Embedding clients can override this component with one that
2869 * invokes a native front end.
2870 * @constructor
2872 function UpdatePrompt() {
2874 UpdatePrompt.prototype = {
2876 * See nsIUpdateService.idl
2878 checkForUpdates: function() {
2879 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, "Update:Wizard",
2880 null, null);
2884 * See nsIUpdateService.idl
2886 showUpdateAvailable: function(update) {
2887 if (!this._enabled)
2888 return;
2889 var bundle = this._updateBundle;
2890 var stringsPrefix = "updateAvailable_" + update.type + ".";
2891 var title = bundle.formatStringFromName(stringsPrefix + "title", [update.name], 1);
2892 var text = bundle.GetStringFromName(stringsPrefix + "text");
2893 var imageUrl = "";
2894 this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
2895 "Update:Wizard", "updatesavailable", update,
2896 title, text, imageUrl);
2900 * See nsIUpdateService.idl
2902 showUpdateDownloaded: function(update, background) {
2903 if (background) {
2904 if (!this._enabled)
2905 return;
2906 var bundle = this._updateBundle;
2907 var stringsPrefix = "updateDownloaded_" + update.type + ".";
2908 var title = bundle.formatStringFromName(stringsPrefix + "title", [update.name], 1);
2909 var text = bundle.GetStringFromName(stringsPrefix + "text");
2910 var imageUrl = "";
2911 this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
2912 "Update:Wizard", "finishedBackground", update,
2913 title, text, imageUrl);
2914 } else {
2915 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
2916 "Update:Wizard", "finishedBackground", update);
2921 * See nsIUpdateService.idl
2923 showUpdateInstalled: function(update) {
2924 var showUpdateInstalledUI = getPref("getBoolPref",
2925 PREF_APP_UPDATE_SHOW_INSTALLED_UI, true);
2926 if (this._enabled && showUpdateInstalledUI) {
2927 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, "Update:Wizard",
2928 "installed", update);
2933 * See nsIUpdateService.idl
2935 showUpdateError: function(update) {
2936 if (this._enabled) {
2937 // In some cases, we want to just show a simple alert dialog:
2938 if (update.state == STATE_FAILED && update.errorCode == WRITE_ERROR) {
2939 var updateBundle = this._updateBundle;
2940 var title = updateBundle.GetStringFromName("updaterIOErrorTitle");
2941 var text = updateBundle.formatStringFromName("updaterIOErrorMsg",
2942 [gApp.name, gApp.name], 2);
2943 var ww =
2944 Components.classes["@mozilla.org/embedcomp/window-watcher;1"].
2945 getService(Components.interfaces.nsIWindowWatcher);
2946 ww.getNewPrompter(null).alert(title, text);
2947 } else {
2948 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, "Update:Wizard",
2949 "errors", update);
2955 * See nsIUpdateService.idl
2957 showUpdateHistory: function(parent) {
2958 this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal,dialog=yes", "Update:History",
2959 null, null);
2963 * Whether or not we are enabled (i.e. not in Silent mode)
2965 get _enabled() {
2966 return !getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false);
2969 get _updateBundle() {
2970 return Components.classes["@mozilla.org/intl/stringbundle;1"]
2971 .getService(Components.interfaces.nsIStringBundleService)
2972 .createBundle(URI_UPDATES_PROPERTIES);
2976 * Initiate a less obtrusive UI, starting with a non-modal notification alert
2977 * @param parent
2978 * A parent window, can be null
2979 * @param uri
2980 * The URI string of the dialog to show
2981 * @param name
2982 * The Window Name of the dialog to show, in case it is already open
2983 * and can merely be focused
2984 * @param page
2985 * The page of the wizard to be displayed, if one is already open.
2986 * @param update
2987 * An update to pass to the UI in the window arguments.
2988 * Can be null
2989 * @param title
2990 * The title for the notification alert.
2991 * @param text
2992 * The contents of the notification alert.
2993 * @param imageUrl
2994 * A URL identifying the image to put in the notification alert.
2996 _showUnobtrusiveUI: function(parent, uri, features, name, page, update,
2997 title, text, imageUrl) {
2998 var observer = {
2999 updatePrompt: this,
3000 service: null,
3001 timer: null,
3002 notify: function () {
3003 // the user hasn't restarted yet => prompt when idle
3004 this.service.removeObserver(this, "quit-application");
3005 this.updatePrompt._showUIWhenIdle(parent, uri, features, name, page, update);
3007 observe: function (aSubject, aTopic, aData) {
3008 switch (aTopic) {
3009 case "alertclickcallback":
3010 this.updatePrompt._showUI(parent, uri, features, name, page, update);
3011 // fall thru
3012 case "quit-application":
3013 this.timer.cancel();
3014 this.service.removeObserver(this, "quit-application");
3015 break;
3020 try {
3021 var notifier = Components.classes["@mozilla.org/alerts-service;1"]
3022 .getService(Components.interfaces.nsIAlertsService);
3023 notifier.showAlertNotification(imageUrl, title, text, true, "", observer);
3025 catch (e) {
3026 // Failed to retrieve alerts service, platform unsupported
3027 this._showUIWhenIdle(parent, uri, features, name, page, update);
3028 return;
3031 observer.service =
3032 Components.classes["@mozilla.org/observer-service;1"]
3033 .getService(Components.interfaces.nsIObserverService);
3034 observer.service.addObserver(observer, "quit-application", false);
3036 // Give the user x seconds to react before showing the big UI
3037 var promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
3038 observer.timer =
3039 Components.classes["@mozilla.org/timer;1"]
3040 .createInstance(Components.interfaces.nsITimer);
3041 observer.timer.initWithCallback(observer, promptWaitTime * 1000,
3042 observer.timer.TYPE_ONE_SHOT);
3046 * Show the UI when the user was idle
3047 * @param parent
3048 * A parent window, can be null
3049 * @param uri
3050 * The URI string of the dialog to show
3051 * @param name
3052 * The Window Name of the dialog to show, in case it is already open
3053 * and can merely be focused
3054 * @param page
3055 * The page of the wizard to be displayed, if one is already open.
3056 * @param update
3057 * An update to pass to the UI in the window arguments.
3058 * Can be null
3060 _showUIWhenIdle: function(parent, uri, features, name, page, update) {
3061 var idleService =
3062 Components.classes["@mozilla.org/widget/idleservice;1"]
3063 .getService(Components.interfaces.nsIIdleService);
3065 const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
3066 if (idleService.idleTime / 1000 >= IDLE_TIME) {
3067 this._showUI(parent, uri, features, name, page, update);
3068 } else {
3069 var observerService =
3070 Components.classes["@mozilla.org/observer-service;1"]
3071 .getService(Components.interfaces.nsIObserverService);
3072 var observer = {
3073 updatePrompt: this,
3074 observe: function (aSubject, aTopic, aData) {
3075 switch (aTopic) {
3076 case "idle":
3077 this.updatePrompt._showUI(parent, uri, features, name, page, update);
3078 // fall thru
3079 case "quit-application":
3080 idleService.removeIdleObserver(this, IDLE_TIME);
3081 observerService.removeObserver(this, "quit-application");
3082 break;
3086 idleService.addIdleObserver(observer, IDLE_TIME);
3087 observerService.addObserver(observer, "quit-application", false);
3092 * Show the Update Checking UI
3093 * @param parent
3094 * A parent window, can be null
3095 * @param uri
3096 * The URI string of the dialog to show
3097 * @param name
3098 * The Window Name of the dialog to show, in case it is already open
3099 * and can merely be focused
3100 * @param page
3101 * The page of the wizard to be displayed, if one is already open.
3102 * @param update
3103 * An update to pass to the UI in the window arguments.
3104 * Can be null
3106 _showUI: function(parent, uri, features, name, page, update) {
3107 var ary = null;
3108 if (update) {
3109 ary = Components.classes["@mozilla.org/supports-array;1"]
3110 .createInstance(Components.interfaces.nsISupportsArray);
3111 ary.AppendElement(update);
3114 var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
3115 .getService(Components.interfaces.nsIWindowMediator);
3116 var win = wm.getMostRecentWindow(name);
3117 if (win) {
3118 if (page && "setCurrentPage" in win)
3119 win.setCurrentPage(page);
3120 win.focus();
3122 else {
3123 var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
3124 if (features)
3125 openFeatures += "," + features;
3126 var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
3127 .getService(Components.interfaces.nsIWindowWatcher);
3128 ww.openWindow(parent, uri, "", openFeatures, ary);
3133 * See nsISupports.idl
3135 QueryInterface: function(iid) {
3136 if (!iid.equals(Components.interfaces.nsIUpdatePrompt) &&
3137 !iid.equals(Components.interfaces.nsISupports))
3138 throw Components.results.NS_ERROR_NO_INTERFACE;
3139 return this;
3142 #endif
3144 var gModule = {
3145 registerSelf: function(componentManager, fileSpec, location, type) {
3146 componentManager = componentManager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
3148 for (var key in this._objects) {
3149 var obj = this._objects[key];
3150 componentManager.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
3151 fileSpec, location, type);
3154 // Make the Update Service a startup observer
3155 var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
3156 .getService(Components.interfaces.nsICategoryManager);
3157 categoryManager.addCategoryEntry("app-startup", this._objects.service.className,
3158 "service," + this._objects.service.contractID,
3159 true, true);
3162 getClassObject: function(componentManager, cid, iid) {
3163 if (!iid.equals(Components.interfaces.nsIFactory))
3164 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
3166 for (var key in this._objects) {
3167 if (cid.equals(this._objects[key].CID))
3168 return this._objects[key].factory;
3171 throw Components.results.NS_ERROR_NO_INTERFACE;
3174 _objects: {
3175 service: { CID : Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}"),
3176 contractID : "@mozilla.org/updates/update-service;1",
3177 className : "Update Service",
3178 factory : makeFactory(UpdateService)
3180 checker: { CID : Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"),
3181 contractID : "@mozilla.org/updates/update-checker;1",
3182 className : "Update Checker",
3183 factory : makeFactory(Checker)
3185 #ifdef MOZ_XUL_APP
3186 prompt: { CID : Components.ID("{27ABA825-35B5-4018-9FDD-F99250A0E722}"),
3187 contractID : "@mozilla.org/updates/update-prompt;1",
3188 className : "Update Prompt",
3189 factory : makeFactory(UpdatePrompt)
3191 #endif
3192 timers: { CID : Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"),
3193 contractID : "@mozilla.org/updates/timer-manager;1",
3194 className : "Timer Manager",
3195 factory : makeFactory(TimerManager)
3197 manager: { CID : Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
3198 contractID : "@mozilla.org/updates/update-manager;1",
3199 className : "Update Manager",
3200 factory : makeFactory(UpdateManager)
3204 canUnload: function(componentManager) {
3205 return true;
3210 * Creates a factory for instances of an object created using the passed-in
3211 * constructor.
3213 function makeFactory(ctor) {
3214 function ci(outer, iid) {
3215 if (outer != null)
3216 throw Components.results.NS_ERROR_NO_AGGREGATION;
3217 return (new ctor()).QueryInterface(iid);
3219 return { createInstance: ci };
3222 function NSGetModule(compMgr, fileSpec) {
3223 return gModule;
3227 * Determines whether or there are installed addons which are incompatible
3228 * with this update.
3229 * @param update
3230 * The update to check compatibility against
3231 * @returns true if there are no addons installed that are incompatible with
3232 * the specified update, false otherwise.
3234 function isCompatible(update) {
3235 #ifdef MOZ_XUL_APP
3236 var em =
3237 Components.classes["@mozilla.org/extensions/manager;1"].
3238 getService(nsIExtensionManager);
3239 var items = em.getIncompatibleItemList("", update.extensionVersion,
3240 update.platformVersion, nsIUpdateItem.TYPE_ANY, false, { });
3241 return items.length == 0;
3242 #else
3243 return true;
3244 #endif
3248 * Shows a prompt for an update, provided there are no incompatible addons.
3249 * If there are, kick off an update check and see if updates are available
3250 * that will resolve the incompatibilities.
3251 * @param update
3252 * The available update to show
3254 function showPromptIfNoIncompatibilities(update) {
3255 function showPrompt(update) {
3256 LOG("UpdateService", "_selectAndInstallUpdate: need to prompt user before continuing...");
3257 var prompter =
3258 Components.classes["@mozilla.org/updates/update-prompt;1"].
3259 createInstance(Components.interfaces.nsIUpdatePrompt);
3260 prompter.showUpdateAvailable(update);
3263 #ifdef MOZ_XUL_APP
3265 * Determines if an addon is compatible with a particular update.
3266 * @param addon
3267 * The addon to check
3268 * @param version
3269 * The extensionVersion of the update to check for compatibility
3270 * against.
3271 * @returns true if the addon is compatible, false otherwise
3273 function addonIsCompatible(addon, version) {
3274 var vc =
3275 Components.classes["@mozilla.org/xpcom/version-comparator;1"].
3276 getService(Components.interfaces.nsIVersionComparator);
3277 return (vc.compare(version, addon.minAppVersion) >= 0) &&
3278 (vc.compare(version, addon.maxAppVersion) <= 0);
3282 * An object implementing nsIAddonUpdateCheckListener that looks for
3283 * available updates to addons and if updates are found that will make the
3284 * user's installed addon set compatible with the update, suppresses the
3285 * prompt that would otherwise be shown.
3286 * @param addons
3287 * An array of incompatible addons that are installed.
3288 * @constructor
3290 function Listener(addons) {
3291 this._addons = addons;
3293 Listener.prototype = {
3294 _addons: null,
3297 * See nsIUpdateService.idl
3299 onUpdateStarted: function() {
3301 onUpdateEnded: function() {
3302 // There are still incompatibilities, even after an extension update
3303 // check to see if there were newer, compatible versions available, so
3304 // we have to prompt.
3306 // PREF_APP_UPDATE_INCOMPATIBLE_MODE controls the mode in which we
3307 // handle incompatibilities:
3308 // UPDATE_CHECK_NEWVERSION We count both VersionInfo updates _and_
3309 // NewerVersion updates against the list of incompatible addons
3310 // installed - i.e. if Foo 1.2 is installed and it is incompatible
3311 // with the update, and we find Foo 2.0 which is but which is not
3312 // yet downloaded or installed, then we do NOT prompt because the
3313 // user can download Foo 2.0 when they restart after the update
3314 // during the mismatch checking UI. This is the default, since it
3315 // suppresses most prompt dialogs.
3316 // UPDATE_CHECK_COMPATIBILITY We count only VersionInfo updates
3317 // against the list of incompatible addons installed - i.e. if the
3318 // situation above with Foo 1.2 and available update to 2.0
3319 // applies, we DO show the prompt since a download operation will
3320 // be required after the update. This is not the default and is
3321 // supplied only as a hidden option for those that want it.
3322 var mode = getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE,
3323 nsIExtensionManager.UPDATE_CHECK_NEWVERSION);
3324 if ((mode == nsIExtensionManager.UPDATE_CHECK_NEWVERSION
3325 && this._addons.length) || !isCompatible(update))
3326 showPrompt(update);
3328 onAddonUpdateStarted: function(addon) {
3330 onAddonUpdateEnded: function(addon, status) {
3331 const Ci = Components.interfaces;
3332 if (status != Ci.nsIAddonUpdateCheckListener.STATUS_UPDATE)
3333 return;
3335 var reqVersion = addon.targetAppID == TOOLKIT_ID ?
3336 update.platformVersion :
3337 update.extensionVersion;
3338 if (!addonIsCompatible(addon, reqVersion))
3339 return;
3341 for (var i = 0; i < this._addons.length; ++i) {
3342 if (this._addons[i] == addon) {
3343 this._addons.splice(i, 1);
3344 break;
3349 * See nsISupports.idl
3351 QueryInterface: function(iid) {
3352 if (!iid.equals(Components.interfaces.nsIAddonUpdateCheckListener) &&
3353 !iid.equals(Components.interfaces.nsISupports))
3354 throw Components.results.NS_ERROR_NO_INTERFACE;
3355 return this;
3359 if (!isCompatible(update)) {
3360 var em =
3361 Components.classes["@mozilla.org/extensions/manager;1"].
3362 getService(nsIExtensionManager);
3363 var items = em.getIncompatibleItemList("", update.extensionVersion,
3364 update.platformVersion, nsIUpdateItem.TYPE_ANY, false, { });
3365 var listener = new Listener(items);
3366 // See documentation on |mode| above.
3367 var mode = getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE,
3368 nsIExtensionManager.UPDATE_CHECK_NEWVERSION);
3369 em.update([], 0, mode, listener);
3371 else
3372 #endif
3373 showPrompt(update);