Bug 452317 - FeedConverter.js: QueryInterface should throw NS_ERROR_NO_INTERFACE...
[wine-gecko.git] / toolkit / mozapps / update / src / nsUpdateService.js.in
blob667daa46f8d0393346e3b524b4d9856751991273
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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>
29 # Robert Strong <robert.bugzilla@gmail.com>
31 # Alternatively, the contents of this file may be used under the terms of
32 # either the GNU General Public License Version 2 or later (the "GPL"), or
33 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
34 # in which case the provisions of the GPL or the LGPL are applicable instead
35 # of those above. If you wish to allow use of your version of this file only
36 # under the terms of either the GPL or the LGPL, and not to allow others to
37 # use your version of this file under the terms of the MPL, indicate your
38 # decision by deleting the provisions above and replace them with the notice
39 # and other provisions required by the GPL or the LGPL. If you do not delete
40 # the provisions above, a recipient may use your version of this file under
41 # the terms of any one of the MPL, the GPL or the LGPL.
43 # ***** END LICENSE BLOCK ***** */
46 const Cc = Components.classes;
47 const Ci = Components.interfaces;
48 const Cr = Components.results;
50 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
52 const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
53 const PREF_APP_UPDATE_AUTO = "app.update.auto";
54 const PREF_APP_UPDATE_MODE = "app.update.mode";
55 const PREF_APP_UPDATE_SILENT = "app.update.silent";
56 const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
57 const PREF_APP_UPDATE_TIMER = "app.update.timer";
58 const PREF_APP_UPDATE_IDLETIME = "app.update.idletime";
59 const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
60 const PREF_APP_UPDATE_LOG_BRANCH = "app.update.log.";
61 const PREF_APP_UPDATE_URL = "app.update.url";
62 const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override";
63 const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
64 const PREF_APP_UPDATE_CHANNEL = "app.update.channel";
65 const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI";
66 const PREF_APP_UPDATE_LASTUPDATETIME_FMT = "app.update.lastUpdateTime.%ID%";
67 const PREF_APP_UPDATE_INCOMPATIBLE_MODE = "app.update.incompatible.mode";
68 const PREF_UPDATE_NEVER_BRANCH = "app.update.never.";
69 const PREF_PARTNER_BRANCH = "app.partner.";
70 const PREF_APP_DISTRIBUTION = "distribution.id";
71 const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
73 const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
74 const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
75 const URI_BRAND_PROPERTIES = "chrome://branding/locale/brand.properties";
76 const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
77 const URI_UPDATE_NS = "http://www.mozilla.org/2005/app-update";
79 const KEY_APPDIR = "XCurProcD";
80 #ifdef XP_WIN
81 const KEY_UPDROOT = "UpdRootD";
82 const KEY_UAPPDATA = "UAppData";
83 #endif
85 const DIR_UPDATES = "updates";
86 const FILE_UPDATE_STATUS = "update.status";
87 const FILE_UPDATE_ARCHIVE = "update.mar";
88 const FILE_UPDATE_LOG = "update.log"
89 const FILE_UPDATES_DB = "updates.xml";
90 const FILE_UPDATE_ACTIVE = "active-update.xml";
91 const FILE_PERMS_TEST = "update.test";
92 const FILE_LAST_LOG = "last-update.log";
93 const FILE_UPDATER_INI = "updater.ini";
95 const MODE_RDONLY = 0x01;
96 const MODE_WRONLY = 0x02;
97 const MODE_CREATE = 0x08;
98 const MODE_APPEND = 0x10;
99 const MODE_TRUNCATE = 0x20;
101 const PERMS_FILE = 0644;
102 const PERMS_DIRECTORY = 0755;
104 const STATE_NONE = "null";
105 const STATE_DOWNLOADING = "downloading";
106 const STATE_PENDING = "pending";
107 const STATE_APPLYING = "applying";
108 const STATE_SUCCEEDED = "succeeded";
109 const STATE_DOWNLOAD_FAILED = "download-failed";
110 const STATE_FAILED = "failed";
112 // From updater/errors.h:
113 const WRITE_ERROR = 7;
115 const DOWNLOAD_CHUNK_SIZE = 300000; // bytes
116 const DOWNLOAD_BACKGROUND_INTERVAL = 600; // seconds
117 const DOWNLOAD_FOREGROUND_INTERVAL = 0;
119 const TOOLKIT_ID = "toolkit@mozilla.org";
121 const POST_UPDATE_CONTRACTID = "@mozilla.org/updates/post-update;1";
123 var gApp = null;
124 var gPref = null;
125 var gABI = null;
126 var gOSVersion = null;
127 var gLocale = null;
128 var gConsole = null;
129 var gLogEnabled = { };
131 // shared code for suppressing bad cert dialogs
132 #include ../../shared/src/badCertHandler.js
135 * Logs a string to the error console.
136 * @param string
137 * The string to write to the error console..
139 function LOG(module, string) {
140 if (module in gLogEnabled || "all" in gLogEnabled) {
141 dump("*** " + module + ": " + string + "\n");
142 gConsole.logStringMessage(string);
147 * Convert a string containing binary values to hex.
149 function binaryToHex(input) {
150 var result = "";
151 for (var i = 0; i < input.length; ++i) {
152 var hex = input.charCodeAt(i).toString(16);
153 if (hex.length == 1)
154 hex = "0" + hex;
155 result += hex;
157 return result;
161 * Gets a File URL spec for a nsIFile
162 * @param file
163 * The file to get a file URL spec to
164 * @returns The file URL spec to the file
166 function getURLSpecFromFile(file) {
167 var ioServ = Cc["@mozilla.org/network/io-service;1"].
168 getService(Ci.nsIIOService);
169 var fph = ioServ.getProtocolHandler("file").
170 QueryInterface(Ci.nsIFileProtocolHandler);
171 return fph.getURLSpecFromFile(file);
175 * Gets the specified directory at the specified hierarchy under a
176 * Directory Service key.
177 * @param key
178 * The Directory Service Key to start from
179 * @param pathArray
180 * An array of path components to locate beneath the directory
181 * specified by |key|
182 * @return nsIFile object for the location specified. If the directory
183 * requested does not exist, it is created, along with any
184 * parent directories that need to be created.
186 function getDir(key, pathArray) {
187 return getDirInternal(key, pathArray, true, false);
191 * Gets the specified directory at the specified hierarchy under a
192 * Directory Service key.
193 * @param key
194 * The Directory Service Key to start from
195 * @param pathArray
196 * An array of path components to locate beneath the directory
197 * specified by |key|
198 * @return nsIFile object for the location specified. If the directory
199 * requested does not exist, it is NOT created.
201 function getDirNoCreate(key, pathArray) {
202 return getDirInternal(key, pathArray, false, false);
206 * Gets the specified directory at the specified hierarchy under the
207 * update root directory.
208 * @param pathArray
209 * An array of path components to locate beneath the directory
210 * specified by |key|
211 * @return nsIFile object for the location specified. If the directory
212 * requested does not exist, it is created, along with any
213 * parent directories that need to be created.
215 function getUpdateDir(pathArray) {
216 return getDirInternal(KEY_APPDIR, pathArray, true, true);
220 * Gets the specified directory at the specified hierarchy under a
221 * Directory Service key.
222 * @param key
223 * The Directory Service Key to start from
224 * @param pathArray
225 * An array of path components to locate beneath the directory
226 * specified by |key|
227 * @param shouldCreate
228 * true if the directory hierarchy specified in |pathArray|
229 * should be created if it does not exist,
230 * false otherwise.
231 * @param update
232 * true if finding the update directory,
233 * false otherwise.
234 * @return nsIFile object for the location specified.
236 function getDirInternal(key, pathArray, shouldCreate, update) {
237 var fileLocator = Cc["@mozilla.org/file/directory_service;1"].
238 getService(Ci.nsIProperties);
239 var dir = fileLocator.get(key, Ci.nsIFile);
240 #ifdef XP_WIN
241 if (update) {
242 try {
243 dir = fileLocator.get(KEY_UPDROOT, Ci.nsIFile);
244 } catch (e) {
247 #endif
248 for (var i = 0; i < pathArray.length; ++i) {
249 dir.append(pathArray[i]);
250 if (shouldCreate && !dir.exists())
251 dir.create(Ci.nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
253 return dir;
257 * Gets the file at the specified hierarchy under a Directory Service key.
258 * @param key
259 * The Directory Service Key to start from
260 * @param pathArray
261 * An array of path components to locate beneath the directory
262 * specified by |key|. The last item in this array must be the
263 * leaf name of a file.
264 * @return nsIFile object for the file specified. The file is NOT created
265 * if it does not exist, however all required directories along
266 * the way are.
268 function getFile(key, pathArray) {
269 var file = getDir(key, pathArray.slice(0, -1));
270 file.append(pathArray[pathArray.length - 1]);
271 return file;
275 * Gets the file at the specified hierarchy under the update root directory.
276 * @param pathArray
277 * An array of path components to locate beneath the directory
278 * specified by |key|. The last item in this array must be the
279 * leaf name of a file.
280 * @return nsIFile object for the file specified. The file is NOT created
281 * if it does not exist, however all required directories along
282 * the way are.
284 function getUpdateFile(pathArray) {
285 var file = getUpdateDir(pathArray.slice(0, -1));
286 file.append(pathArray[pathArray.length - 1]);
287 return file;
291 * Closes a Safe Output Stream
292 * @param fos
293 * The Safe Output Stream to close
295 function closeSafeOutputStream(fos) {
296 if (fos instanceof Ci.nsISafeOutputStream) {
297 try {
298 fos.finish();
300 catch (e) {
301 fos.close();
304 else
305 fos.close();
309 * Returns human readable status text from the updates.properties bundle
310 * based on an error code
311 * @param code
312 * The error code to look up human readable status text for
313 * @param defaultCode
314 * The default code to look up should human readable status text
315 * not exist for |code|
316 * @returns A human readable status text string
318 function getStatusTextFromCode(code, defaultCode) {
319 const updateBundle = Cc["@mozilla.org/intl/stringbundle;1"].
320 getService(Ci.nsIStringBundleService).
321 createBundle(URI_UPDATES_PROPERTIES);
322 var reason;
323 try {
324 reason = updateBundle.GetStringFromName("checker_error-" + code);
325 LOG("General", "Transfer Error: " + reason + ", code: " + code);
327 catch (e) {
328 // Use the default reason
329 reason = updateBundle.GetStringFromName("checker_error-" + defaultCode);
330 LOG("General", "Transfer Error: " + reason + ", default code: " + defaultCode);
332 return reason;
336 * Get the Active Updates directory
337 * @param key
338 * The Directory Service Key (optional).
339 * If used, don't search local appdata on Win32 and don't create dir.
340 * @returns The active updates directory, as a nsIFile object
342 function getUpdatesDir(key) {
343 // Right now, we only support downloading one patch at a time, so we always
344 // use the same target directory.
345 var fileLocator = Cc["@mozilla.org/file/directory_service;1"].
346 getService(Ci.nsIProperties);
347 var updateDir;
348 if (key)
349 updateDir = fileLocator.get(key, Ci.nsIFile);
350 else {
351 updateDir = fileLocator.get(KEY_APPDIR, Ci.nsIFile);
352 #ifdef XP_WIN
353 try {
354 updateDir = fileLocator.get(KEY_UPDROOT, Ci.nsIFile);
355 } catch (e) {
357 #endif
359 updateDir.append(DIR_UPDATES);
360 updateDir.append("0");
361 if (!updateDir.exists() && !key)
362 updateDir.create(Ci.nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
363 return updateDir;
367 * Reads the update state from the update.status file in the specified
368 * directory.
369 * @param dir
370 * The dir to look for an update.status file in
371 * @returns The status value of the update.
373 function readStatusFile(dir) {
374 var statusFile = dir.clone();
375 statusFile.append(FILE_UPDATE_STATUS);
376 LOG("General", "Reading Status File: " + statusFile.path);
377 return readStringFromFile(statusFile) || STATE_NONE;
381 * Writes the current update operation/state to a file in the patch
382 * directory, indicating to the patching system that operations need
383 * to be performed.
384 * @param dir
385 * The patch directory where the update.status file should be
386 * written.
387 * @param state
388 * The state value to write.
390 function writeStatusFile(dir, state) {
391 var statusFile = dir.clone();
392 statusFile.append(FILE_UPDATE_STATUS);
393 writeStringToFile(statusFile, state);
397 * Removes the Updates Directory
398 * @param key
399 * The Directory Service Key under which update directory resides
400 * (optional).
402 function cleanUpUpdatesDir(key) {
403 // Bail out if we don't have appropriate permissions
404 var updateDir;
405 try {
406 updateDir = getUpdatesDir(key);
408 catch (e) {
409 return;
412 var e = updateDir.directoryEntries;
413 while (e.hasMoreElements()) {
414 var f = e.getNext().QueryInterface(Ci.nsIFile);
415 // Preserve the last update log file for debugging purposes
416 if (f.leafName == FILE_UPDATE_LOG) {
417 try {
418 var dir = f.parent.parent;
419 var logFile = dir.clone();
420 logFile.append(FILE_LAST_LOG);
421 if (logFile.exists())
422 logFile.remove(false);
423 f.copyTo(dir, FILE_LAST_LOG);
425 catch (e) {
426 LOG("General", "Failed to copy file: " + f.path);
429 // Now, recursively remove this file. The recusive removal is really
430 // only needed on Mac OSX because this directory will contain a copy of
431 // updater.app, which is itself a directory.
432 try {
433 f.remove(true);
435 catch (e) {
436 LOG("General", "Failed to remove file: " + f.path);
439 try {
440 updateDir.remove(false);
441 } catch (e) {
442 LOG("General", "Failed to remove update directory: " + updateDir.path +
443 " - This is almost always bad. Exception = " + e);
444 throw e;
449 * Clean up updates list and the updates directory.
450 * @param key
451 * The Directory Service Key under which update directory resides
452 * (optional).
454 function cleanupActiveUpdate(key) {
455 // Move the update from the Active Update list into the Past Updates list.
456 var um = Cc["@mozilla.org/updates/update-manager;1"].
457 getService(Ci.nsIUpdateManager);
458 um.activeUpdate = null;
459 um.saveUpdates();
461 // Now trash the updates directory, since we're done with it
462 cleanUpUpdatesDir(key);
466 * Gets a preference value, handling the case where there is no default.
467 * @param func
468 * The name of the preference function to call, on nsIPrefBranch
469 * @param preference
470 * The name of the preference
471 * @param defaultValue
472 * The default value to return in the event the preference has
473 * no setting
474 * @returns The value of the preference, or undefined if there was no
475 * user or default value.
477 function getPref(func, preference, defaultValue) {
478 try {
479 return gPref[func](preference);
481 catch (e) {
483 return defaultValue;
487 * Gets the locale specified by the 'Locale' key in the 'Installation' section
488 * of updater.ini.
490 function getLocale() {
491 if (gLocale)
492 return gLocale;
494 var updaterIni = getFile(KEY_APPDIR, [FILE_UPDATER_INI]);
495 var iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
496 getService(Ci.nsIINIParserFactory).
497 createINIParser(updaterIni);
498 gLocale = iniParser.getString("Installation", "Locale");
499 LOG("General", "Getting Locale from File: " + updaterIni.path + " Locale: " + gLocale);
500 return gLocale;
504 * Read the update channel from defaults only. We do this to ensure that
505 * the channel is tightly coupled with the application and does not apply
506 * to other instances of the application that may use the same profile.
508 function getUpdateChannel() {
509 var channel = "default";
510 var prefName;
511 var prefValue;
513 try {
514 channel = getDefaultPrefBranch().getCharPref(PREF_APP_UPDATE_CHANNEL);
515 } catch (e) {
516 // use default when pref not found
519 try {
520 var partners = gPref.getChildList(PREF_PARTNER_BRANCH, { });
521 if (partners.length) {
522 channel += "-cck";
523 partners.sort();
525 for each (prefName in partners) {
526 prefValue = gPref.getCharPref(prefName);
527 channel += "-" + prefValue;
531 catch (e) {
532 Components.utils.reportError(e);
535 return channel;
538 /* Get the distribution pref values, from defaults only */
539 function getDistributionPrefValue(aPrefName) {
540 var prefValue = "default";
542 try {
543 prefValue = getDefaultPrefBranch().getCharPref(aPrefName);
544 } catch (e) {
545 // use default when pref not found
548 return prefValue;
552 * An enumeration of items in a JS array.
553 * @constructor
555 function ArrayEnumerator(aItems) {
556 this._index = 0;
557 if (aItems) {
558 for (var i = 0; i < aItems.length; ++i) {
559 if (!aItems[i])
560 aItems.splice(i, 1);
563 this._contents = aItems;
566 ArrayEnumerator.prototype = {
567 _index: 0,
568 _contents: [],
570 hasMoreElements: function() {
571 return this._index < this._contents.length;
574 getNext: function() {
575 return this._contents[this._index++];
580 * Trims a prefix from a string.
581 * @param string
582 * The source string
583 * @param prefix
584 * The prefix to remove.
585 * @returns The suffix (string - prefix)
587 function stripPrefix(string, prefix) {
588 return string.substr(prefix.length);
592 * Writes a string of text to a file. A newline will be appended to the data
593 * written to the file. This function only works with ASCII text.
595 function writeStringToFile(file, text) {
596 var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
597 createInstance(Ci.nsIFileOutputStream);
598 var modeFlags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
599 if (!file.exists())
600 file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
601 fos.init(file, modeFlags, PERMS_FILE, 0);
602 text += "\n";
603 fos.write(text, text.length);
604 closeSafeOutputStream(fos);
608 * Reads a string of text from a file. A trailing newline will be removed
609 * before the result is returned. This function only works with ASCII text.
611 function readStringFromFile(file) {
612 var fis = Cc["@mozilla.org/network/file-input-stream;1"].
613 createInstance(Ci.nsIFileInputStream);
614 var modeFlags = MODE_RDONLY;
615 if (!file.exists())
616 return null;
617 fis.init(file, modeFlags, PERMS_FILE, 0);
618 var sis = Cc["@mozilla.org/scriptableinputstream;1"].
619 createInstance(Ci.nsIScriptableInputStream);
620 sis.init(fis);
621 var text = sis.read(sis.available());
622 sis.close();
623 if (text[text.length - 1] == "\n")
624 text = text.slice(0, -1);
625 return text;
628 function getObserverService()
630 return Cc["@mozilla.org/observer-service;1"].
631 getService(Ci.nsIObserverService);
634 function getDefaultPrefBranch()
636 return gPref.QueryInterface(Ci.nsIPrefService).getDefaultBranch(null);
639 * Update Patch
640 * @param patch
641 * A <patch> element to initialize this object with
642 * @throws if patch has a size of 0
643 * @constructor
645 function UpdatePatch(patch) {
646 this._properties = {};
647 for (var i = 0; i < patch.attributes.length; ++i) {
648 var attr = patch.attributes.item(i);
649 attr.QueryInterface(Ci.nsIDOMAttr);
650 switch (attr.name) {
651 case "selected":
652 this.selected = attr.value == "true";
653 break;
654 case "size":
655 if (0 == parseInt(attr.value)) {
656 LOG("UpdatePatch", "0-sized patch!");
657 throw Cr.NS_ERROR_ILLEGAL_VALUE;
659 // fall through
660 default:
661 this[attr.name] = attr.value;
662 break;
666 UpdatePatch.prototype = {
668 * See nsIUpdateService.idl
670 serialize: function(updates) {
671 var patch = updates.createElementNS(URI_UPDATE_NS, "patch");
672 patch.setAttribute("type", this.type);
673 patch.setAttribute("URL", this.URL);
674 patch.setAttribute("hashFunction", this.hashFunction);
675 patch.setAttribute("hashValue", this.hashValue);
676 patch.setAttribute("size", this.size);
677 patch.setAttribute("selected", this.selected);
678 patch.setAttribute("state", this.state);
680 for (var p in this._properties) {
681 if (this._properties[p].present)
682 patch.setAttribute(p, this._properties[p].data);
685 return patch;
689 * A hash of custom properties
691 _properties: null,
694 * See nsIWritablePropertyBag.idl
696 setProperty: function(name, value) {
697 this._properties[name] = { data: value, present: true };
701 * See nsIWritablePropertyBag.idl
703 deleteProperty: function(name) {
704 if (name in this._properties)
705 this._properties[name].present = false;
706 else
707 throw Cr.NS_ERROR_FAILURE;
711 * See nsIPropertyBag.idl
713 get enumerator() {
714 var properties = [];
715 for (var p in this._properties)
716 properties.push(this._properties[p].data);
717 return new ArrayEnumerator(properties);
721 * See nsIPropertyBag.idl
723 getProperty: function(name) {
724 if (name in this._properties &&
725 this._properties[name].present)
726 return this._properties[name].data;
727 throw Cr.NS_ERROR_FAILURE;
731 * Returns whether or not the update.status file for this patch exists at the
732 * appropriate location.
734 get statusFileExists() {
735 var statusFile = getUpdatesDir();
736 statusFile.append(FILE_UPDATE_STATUS);
737 return statusFile.exists();
741 * See nsIUpdateService.idl
743 get state() {
744 if (!this.statusFileExists)
745 return STATE_NONE;
746 return this._properties.state;
748 set state(val) {
749 this._properties.state = val;
752 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePatch,
753 Ci.nsIPropertyBag,
754 Ci.nsIWritablePropertyBag])
758 * Update
759 * Implements nsIUpdate
760 * @param update
761 * An <update> element to initialize this object with
762 * @throws if the update contains no patches
763 * @constructor
765 function Update(update) {
766 this._properties = {};
767 this._patches = [];
768 this.installDate = 0;
769 this.isCompleteUpdate = false;
770 this.channel = "default"
772 // Null <update>, assume this is a message container and do no
773 // further initialization
774 if (!update)
775 return;
777 const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
778 for (var i = 0; i < update.childNodes.length; ++i) {
779 var patchElement = update.childNodes.item(i);
780 if (patchElement.nodeType != ELEMENT_NODE ||
781 patchElement.localName != "patch")
782 continue;
784 patchElement.QueryInterface(Ci.nsIDOMElement);
785 try {
786 var patch = new UpdatePatch(patchElement);
787 } catch (e) {
788 continue;
790 this._patches.push(patch);
793 if (0 == this._patches.length)
794 throw Cr.NS_ERROR_ILLEGAL_VALUE;
796 for (var i = 0; i < update.attributes.length; ++i) {
797 var attr = update.attributes.item(i);
798 attr.QueryInterface(Ci.nsIDOMAttr);
799 if (attr.name == "installDate" && attr.value)
800 this.installDate = parseInt(attr.value);
801 else if (attr.name == "isCompleteUpdate")
802 this.isCompleteUpdate = attr.value == "true";
803 else if (attr.name == "isSecurityUpdate")
804 this.isSecurityUpdate = attr.value == "true";
805 else if (attr.name == "detailsURL")
806 this._detailsURL = attr.value;
807 else if (attr.name == "channel")
808 this.channel = attr.value;
809 else
810 this[attr.name] = attr.value;
813 // The Update Name is either the string provided by the <update> element, or
814 // the string: "<App Name> <Update App Version>"
815 var name = "";
816 if (update.hasAttribute("name"))
817 name = update.getAttribute("name");
818 else {
819 var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
820 getService(Ci.nsIStringBundleService);
821 var brandBundle = sbs.createBundle(URI_BRAND_PROPERTIES);
822 var updateBundle = sbs.createBundle(URI_UPDATES_PROPERTIES);
823 var appName = brandBundle.GetStringFromName("brandShortName");
824 name = updateBundle.formatStringFromName("updateName",
825 [appName, this.version], 2);
827 this.name = name;
829 Update.prototype = {
831 * See nsIUpdateService.idl
833 get patchCount() {
834 return this._patches.length;
838 * See nsIUpdateService.idl
840 getPatchAt: function(index) {
841 return this._patches[index];
845 * See nsIUpdateService.idl
847 * We use a copy of the state cached on this object in |_state| only when
848 * there is no selected patch, i.e. in the case when we could not load
849 * |.activeUpdate| from the update manager for some reason but still have
850 * the update.status file to work with.
852 _state: "",
853 set state(state) {
854 if (this.selectedPatch)
855 this.selectedPatch.state = state;
856 this._state = state;
857 return state;
859 get state() {
860 if (this.selectedPatch)
861 return this.selectedPatch.state;
862 return this._state;
866 * See nsIUpdateService.idl
868 errorCode: 0,
871 * See nsIUpdateService.idl
873 get selectedPatch() {
874 for (var i = 0; i < this.patchCount; ++i) {
875 if (this._patches[i].selected)
876 return this._patches[i];
878 return null;
882 * See nsIUpdateService.idl
884 get detailsURL() {
885 if (!this._detailsURL) {
886 try {
887 // Try using a default details URL supplied by the distribution
888 // if the update XML does not supply one.
889 var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
890 getService(Ci.nsIURLFormatter);
891 return formatter.formatURLPref(PREF_APP_UPDATE_URL_DETAILS);
893 catch (e) {
896 return this._detailsURL || "";
900 * See nsIUpdateService.idl
902 serialize: function(updates) {
903 var update = updates.createElementNS(URI_UPDATE_NS, "update");
904 update.setAttribute("type", this.type);
905 update.setAttribute("name", this.name);
906 update.setAttribute("version", this.version);
907 update.setAttribute("platformVersion", this.platformVersion);
908 update.setAttribute("extensionVersion", this.extensionVersion);
909 update.setAttribute("detailsURL", this.detailsURL);
910 update.setAttribute("licenseURL", this.licenseURL);
911 update.setAttribute("serviceURL", this.serviceURL);
912 update.setAttribute("installDate", this.installDate);
913 update.setAttribute("statusText", this.statusText);
914 update.setAttribute("buildID", this.buildID);
915 update.setAttribute("isCompleteUpdate", this.isCompleteUpdate);
916 update.setAttribute("channel", this.channel);
917 updates.documentElement.appendChild(update);
919 for (var p in this._properties) {
920 if (this._properties[p].present)
921 update.setAttribute(p, this._properties[p].data);
924 for (var i = 0; i < this.patchCount; ++i)
925 update.appendChild(this.getPatchAt(i).serialize(updates));
927 return update;
931 * A hash of custom properties
933 _properties: null,
936 * See nsIWritablePropertyBag.idl
938 setProperty: function(name, value) {
939 this._properties[name] = { data: value, present: true };
943 * See nsIWritablePropertyBag.idl
945 deleteProperty: function(name) {
946 if (name in this._properties)
947 this._properties[name].present = false;
948 else
949 throw Cr.NS_ERROR_FAILURE;
953 * See nsIPropertyBag.idl
955 get enumerator() {
956 var properties = [];
957 for (var p in this._properties)
958 properties.push(this._properties[p].data);
959 return new ArrayEnumerator(properties);
963 * See nsIPropertyBag.idl
965 getProperty: function(name) {
966 if (name in this._properties &&
967 this._properties[name].present)
968 return this._properties[name].data;
969 throw Cr.NS_ERROR_FAILURE;
972 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdate,
973 Ci.nsIPropertyBag,
974 Ci.nsIWritablePropertyBag])
977 const UpdateServiceFactory = {
978 _instance: null,
979 createInstance: function (outer, iid) {
980 if (outer != null)
981 throw Components.results.NS_ERROR_NO_AGGREGATION;
982 return this._instance == null ? this._instance = new UpdateService() :
983 this._instance;
988 * UpdateService
989 * A Service for managing the discovery and installation of software updates.
990 * @constructor
992 function UpdateService() {
993 gApp = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).
994 QueryInterface(Ci.nsIXULRuntime);
995 gPref = Cc["@mozilla.org/preferences-service;1"].
996 getService(Ci.nsIPrefBranch2);
997 gConsole = Cc["@mozilla.org/consoleservice;1"].
998 getService(Ci.nsIConsoleService);
1000 // Not all builds have a known ABI
1001 try {
1002 gABI = gApp.XPCOMABI;
1004 catch (e) {
1005 LOG("UpdateService", "XPCOM ABI unknown: updates are not possible.");
1008 var osVersion;
1009 var sysInfo = Cc["@mozilla.org/system-info;1"].
1010 getService(Ci.nsIPropertyBag2);
1011 try {
1012 osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
1014 catch (e) {
1015 LOG("UpdateService", "OS Version unknown: updates are not possible.");
1018 if (osVersion) {
1019 try {
1020 osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
1022 catch (e) {
1023 // Not all platforms have a secondary widget library, so an error is nothing to worry about.
1025 gOSVersion = encodeURIComponent(osVersion);
1028 #ifdef XP_MACOSX
1029 // Mac universal build should report a different ABI than either macppc
1030 // or mactel.
1031 var macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
1032 getService(Ci.nsIMacUtils);
1034 if (macutils.isUniversalBinary)
1035 gABI = "Universal-gcc3";
1036 #endif
1038 // Start the update timer only after a profile has been selected so that the
1039 // appropriate values for the update check are read from the user's profile.
1040 var os = getObserverService();
1042 os.addObserver(this, "profile-after-change", false);
1044 // Observe xpcom-shutdown to unhook pref branch observers above to avoid
1045 // shutdown leaks.
1046 os.addObserver(this, "xpcom-shutdown", false);
1049 UpdateService.prototype = {
1051 * The downloader we are using to download updates. There is only ever one of
1052 * these.
1054 _downloader: null,
1057 * Handle Observer Service notifications
1058 * @param subject
1059 * The subject of the notification
1060 * @param topic
1061 * The notification name
1062 * @param data
1063 * Additional data
1065 observe: function(subject, topic, data) {
1066 var os = getObserverService();
1068 switch (topic) {
1069 case "profile-after-change":
1070 os.removeObserver(this, "profile-after-change");
1071 this._start();
1072 break;
1073 case "xpcom-shutdown":
1074 os.removeObserver(this, "xpcom-shutdown");
1076 // Release Services
1077 gApp = null;
1078 gPref = null;
1079 gConsole = null;
1080 break;
1085 * Start the Update Service
1087 _start: function() {
1088 // Start logging
1089 this._initLoggingPrefs();
1091 // Clean up any extant updates
1092 this._postUpdateProcessing();
1094 // Register a background update check timer
1095 var tm = Cc["@mozilla.org/updates/timer-manager;1"].
1096 getService(Ci.nsIUpdateTimerManager);
1097 var interval = getPref("getIntPref", PREF_APP_UPDATE_INTERVAL, 86400);
1098 tm.registerTimer("background-update-timer", this, interval);
1100 // Resume fetching...
1101 var um = Cc["@mozilla.org/updates/update-manager;1"].
1102 getService(Ci.nsIUpdateManager);
1103 var activeUpdate = um.activeUpdate;
1104 if (activeUpdate) {
1105 var status = this.downloadUpdate(activeUpdate, true);
1106 if (status == STATE_NONE)
1107 cleanupActiveUpdate();
1112 * Perform post-processing on updates lingering in the updates directory
1113 * from a previous browser session - either report install failures (and
1114 * optionally attempt to fetch a different version if appropriate) or
1115 * notify the user of install success.
1117 _postUpdateProcessing: function() {
1118 // Detect installation failures and notify
1120 // Bail out if we don't have appropriate permissions
1121 if (!this.canUpdate)
1122 return;
1124 var status = readStatusFile(getUpdatesDir());
1126 // Make sure to cleanup after an update that failed for an unknown reason
1127 if (status == "null")
1128 status = null;
1130 var updRootKey = null;
1131 #ifdef XP_WIN
1132 function findPreviousUpdate(key) {
1133 var updateDir = getUpdatesDir(key);
1134 if (updateDir.exists()) {
1135 status = readStatusFile(updateDir);
1136 // Previous download should succeed. Otherwise, we will not be here!
1137 if (status == STATE_SUCCEEDED)
1138 updRootKey = key;
1139 else
1140 status = null;
1144 // required when updating from Fx 2.0.0.1 to 2.0.0.3 (or later)
1145 // on Windows Vista.
1146 if (status == null)
1147 findPreviousUpdate(KEY_UAPPDATA);
1149 // required to migrate from older versions.
1150 if (status == null)
1151 findPreviousUpdate(KEY_APPDIR);
1152 #endif
1154 if (status == STATE_DOWNLOADING) {
1155 LOG("UpdateService", "_postUpdateProcessing: Downloading patch, resuming...");
1157 else if (status != null) {
1158 // null status means the update.status file is not present, because either:
1159 // 1) no update was performed, and so there's no UI to show
1160 // 2) an update was attempted but failed during checking, transfer or
1161 // verification, and was cleaned up at that point, and UI notifying of
1162 // that error was shown at that stage.
1163 var um = Cc["@mozilla.org/updates/update-manager;1"].
1164 getService(Ci.nsIUpdateManager);
1165 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
1166 createInstance(Ci.nsIUpdatePrompt);
1168 var shouldCleanup = true;
1169 var update = um.activeUpdate;
1170 if (!update) {
1171 update = new Update(null);
1173 update.state = status;
1174 var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
1175 getService(Ci.nsIStringBundleService);
1176 var bundle = sbs.createBundle(URI_UPDATES_PROPERTIES);
1177 if (status == STATE_SUCCEEDED) {
1178 update.statusText = bundle.GetStringFromName("installSuccess");
1180 // Dig through the update history to find the patch that was just
1181 // installed and update its metadata.
1182 for (var i = 0; i < um.updateCount; ++i) {
1183 var umUpdate = um.getUpdateAt(i);
1184 if (umUpdate && umUpdate.version == update.version &&
1185 umUpdate.buildID == update.buildID) {
1186 umUpdate.statusText = update.statusText;
1187 break;
1191 LOG("UpdateService", "_postUpdateProcessing: Install Succeeded, Showing UI");
1192 prompter.showUpdateInstalled(update);
1193 #ifdef MOZ_SUNBIRD
1194 // we need to fix both nsPostUpdateWin.js and
1195 // the uninstaller to work for sunbird
1196 #else
1197 // Perform platform-specific post-update processing.
1198 if (POST_UPDATE_CONTRACTID in Cc) {
1199 Cc[POST_UPDATE_CONTRACTID].createInstance(Ci.nsIRunnable).run();
1201 #endif
1203 // Done with this update. Clean it up.
1204 cleanupActiveUpdate(updRootKey);
1206 else {
1207 // If we hit an error, then the error code will be included in the
1208 // status string following a colon. If we had an I/O error, then we
1209 // assume that the patch is not invalid, and we restage the patch so
1210 // that it can be attempted again the next time we restart.
1211 var ary = status.split(": ");
1212 update.state = ary[0];
1213 if (update.state == STATE_FAILED && ary[1]) {
1214 update.errorCode = ary[1];
1215 if (update.errorCode == WRITE_ERROR) {
1216 prompter.showUpdateError(update);
1217 writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
1218 return;
1222 // Something went wrong with the patch application process.
1223 cleanupActiveUpdate();
1225 update.statusText = bundle.GetStringFromName("patchApplyFailure");
1226 var oldType = update.selectedPatch ? update.selectedPatch.type
1227 : "complete";
1228 if (update.selectedPatch && oldType == "partial") {
1229 // Partial patch application failed, try downloading the complete
1230 // update in the background instead.
1231 LOG("UpdateService", "_postUpdateProcessing: Install of Partial Patch " +
1232 "failed, downloading Complete Patch and maybe showing UI");
1233 var status = this.downloadUpdate(update, true);
1234 if (status == STATE_NONE)
1235 cleanupActiveUpdate();
1237 else {
1238 LOG("UpdateService", "_postUpdateProcessing: Install of Complete or " +
1239 "only patch failed. Showing error.");
1241 update.QueryInterface(Ci.nsIWritablePropertyBag);
1242 update.setProperty("patchingFailed", oldType);
1243 prompter.showUpdateError(update);
1246 else {
1247 LOG("UpdateService", "_postUpdateProcessing: No Status, No Update");
1252 * Initialize Logging preferences, formatted like so:
1253 * app.update.log.<moduleName> = <true|false>
1255 _initLoggingPrefs: function() {
1256 try {
1257 var ps = Cc["@mozilla.org/preferences-service;1"].
1258 getService(Ci.nsIPrefService);
1259 var logBranch = ps.getBranch(PREF_APP_UPDATE_LOG_BRANCH);
1260 var modules = logBranch.getChildList("", { value: 0 });
1262 for (var i = 0; i < modules.length; ++i) {
1263 if (logBranch.prefHasUserValue(modules[i]))
1264 gLogEnabled[modules[i]] = logBranch.getBoolPref(modules[i]);
1267 catch (e) {
1272 * Notified when a timer fires
1273 * @param timer
1274 * The timer that fired
1276 notify: function(timer) {
1277 // If a download is in progress, then do nothing.
1278 if (this.isDownloading || this._downloader && this._downloader.patchIsStaged)
1279 return;
1281 var self = this;
1282 var listener = {
1284 * See nsIUpdateService.idl
1286 onProgress: function(request, position, totalSize) {
1290 * See nsIUpdateService.idl
1292 onCheckComplete: function(request, updates, updateCount) {
1293 self._selectAndInstallUpdate(updates);
1297 * See nsIUpdateService.idl
1299 onError: function(request, update) {
1300 LOG("Checker", "Error during background update: " + update.statusText);
1303 this.backgroundChecker.checkForUpdates(listener, false);
1307 * Determine whether or not an update requires user confirmation before it
1308 * can be installed.
1309 * @param update
1310 * The update to be installed
1311 * @returns true if a prompt UI should be shown asking the user if they want
1312 * to install the update, false if the update should just be
1313 * silently downloaded and installed.
1315 _shouldPrompt: function(update) {
1316 // There are two possible outcomes here:
1317 // 1. download and install the update automatically
1318 // 2. alert the user about the presence of an update before doing anything
1320 // The outcome we follow is determined as follows:
1322 // Note: all Major updates require notification and confirmation
1324 // Update Type Mode Incompatible Outcome
1325 // Major 0 Yes or No Notify and Confirm
1326 // Major 1 No Notify and Confirm
1327 // Major 1 Yes Notify and Confirm
1328 // Major 2 Yes or No Notify and Confirm
1329 // Minor 0 Yes or No Auto Install
1330 // Minor 1 No Auto Install
1331 // Minor 1 Yes Notify and Confirm
1332 // Minor 2 No Auto Install
1333 // Minor 2 Yes Notify and Confirm
1335 // In addition, if there is a license associated with an update, regardless
1336 // of type it must be agreed to.
1338 // If app.update.enabled is set to false, an update check is not performed
1339 // at all, and so none of the decision making above is entered into.
1341 if (update.type == "major") {
1342 LOG("Checker", "_shouldPrompt: Prompting because it is a major update");
1343 return true;
1346 update.QueryInterface(Ci.nsIPropertyBag);
1347 try {
1348 var licenseAccepted = update.getProperty("licenseAccepted") == "true";
1350 catch (e) {
1351 licenseAccepted = false;
1354 var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
1355 if (!updateEnabled) {
1356 LOG("Checker", "_shouldPrompt: Not prompting because update is " +
1357 "disabled");
1358 return false;
1361 // User has turned off automatic download and install
1362 var autoEnabled = getPref("getBoolPref", PREF_APP_UPDATE_AUTO, true);
1363 if (!autoEnabled) {
1364 LOG("Checker", "_shouldPrompt: Prompting because auto install is disabled");
1365 return true;
1368 switch (getPref("getIntPref", PREF_APP_UPDATE_MODE, 1)) {
1369 case 1:
1370 // Mode 1 is do not prompt only if there are no incompatibilities
1371 // releases
1372 LOG("Checker", "_shouldPrompt: Prompting if there are incompatibilities");
1373 return !isCompatible(update);
1374 case 2:
1375 // Mode 2 is do not prompt only if there are no incompatibilities
1376 LOG("Checker", "_shouldPrompt: Prompting if there are incompatibilities");
1377 return !isCompatible(update);
1379 // Mode 0 is do not prompt regardless of incompatibilities
1380 LOG("Checker", "_shouldPrompt: Not prompting the user - they choose to " +
1381 "ignore incompatibilities");
1382 return false;
1386 * Determine which of the specified updates should be installed.
1387 * @param updates
1388 * An array of available updates
1390 selectUpdate: function(updates) {
1391 if (updates.length == 0)
1392 return null;
1394 // Choose the newest of the available minor and major updates.
1395 var majorUpdate = null, minorUpdate = null;
1396 var newestMinor = updates[0], newestMajor = updates[0];
1398 var vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
1399 getService(Ci.nsIVersionComparator);
1400 for (var i = 0; i < updates.length; ++i) {
1401 if (updates[i].type == "major" &&
1402 vc.compare(newestMajor.version, updates[i].version) <= 0)
1403 majorUpdate = newestMajor = updates[i];
1404 if (updates[i].type == "minor" &&
1405 vc.compare(newestMinor.version, updates[i].version) <= 0)
1406 minorUpdate = newestMinor = updates[i];
1409 // IMPORTANT
1410 // If there's a minor update, always try and fetch that one first,
1411 // otherwise use the newest major update.
1412 // selectUpdate() only returns one update.
1413 // if major were to trump minor, and we said "never" to the major
1414 // we'd never get the minor update, since selectUpdate()
1415 // would return the major update that the user said "never" to
1416 // (shadowing the important minor update with security fixes)
1417 return minorUpdate || majorUpdate;
1421 * Determine which of the specified updates should be installed and
1422 * begin the download/installation process, optionally prompting the
1423 * user for permission if required.
1424 * @param updates
1425 * An array of available updates
1427 _selectAndInstallUpdate: function(updates) {
1428 // Don't prompt if there's an active update - the user is already
1429 // aware and is downloading, or performed some user action to prevent
1430 // notification.
1431 var um = Cc["@mozilla.org/updates/update-manager;1"].
1432 getService(Ci.nsIUpdateManager);
1433 if (um.activeUpdate)
1434 return;
1436 var update = this.selectUpdate(updates, updates.length);
1437 if (!update)
1438 return;
1440 // check if the user said "never" to this version
1441 // this check is done here, and not in selectUpdate() so that
1442 // the user can get an upgrade they said "never" to if they
1443 // manually do "Check for Updates..."
1444 // note, selectUpdate() only returns one update.
1445 // but in selectUpdate(), minor updates trump major updates
1446 // if major trumps minor, and we said "never" to the major
1447 // we'd never see the minor update.
1449 // note, the never decision should only apply to major updates
1450 // see bug #350636 for a scenario where this could potentially
1451 // be an issue
1453 // fix for bug #359093
1454 // version might one day come back from AUS as an
1455 // arbitrary (and possibly non ascii) string, so we need to encode it
1456 var neverPrefName = PREF_UPDATE_NEVER_BRANCH + encodeURIComponent(update.version);
1457 var never = getPref("getBoolPref", neverPrefName, false);
1458 if (never && update.type == "major")
1459 return;
1461 if (this._shouldPrompt(update))
1462 showPromptIfNoIncompatibilities(update);
1463 else {
1464 LOG("UpdateService", "_selectAndInstallUpdate: No need to show prompt, just download update");
1465 var status = this.downloadUpdate(update, true);
1466 if (status == STATE_NONE)
1467 cleanupActiveUpdate();
1472 * The Checker used for background update checks.
1474 _backgroundChecker: null,
1477 * See nsIUpdateService.idl
1479 get backgroundChecker() {
1480 if (!this._backgroundChecker)
1481 this._backgroundChecker = new Checker();
1482 return this._backgroundChecker;
1486 * See nsIUpdateService.idl
1488 get canUpdate() {
1489 try {
1490 var appDirFile = getUpdateFile([FILE_PERMS_TEST]);
1491 LOG("UpdateService", "canUpdate? testing " + appDirFile.path);
1492 if (!appDirFile.exists()) {
1493 appDirFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
1494 appDirFile.remove(false);
1496 var updateDir = getUpdatesDir();
1497 var upDirFile = updateDir.clone();
1498 upDirFile.append(FILE_PERMS_TEST);
1499 LOG("UpdateService", "canUpdate? testing " + upDirFile.path);
1500 if (!upDirFile.exists()) {
1501 upDirFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
1502 upDirFile.remove(false);
1504 #ifdef XP_WIN
1505 var sysInfo = Cc["@mozilla.org/system-info;1"].
1506 getService(Ci.nsIPropertyBag2);
1508 // Example windowsVersion: Windows XP == 5.1
1509 var windowsVersion = sysInfo.getProperty("version");
1510 LOG("UpdateService", "canUpdate? windowsVersion = " + windowsVersion);
1512 // For Vista, updates can be performed to a location requiring
1513 // admin privileges by requesting elevation via the UAC prompt when
1514 // launching updater.exe if the appDir is under the Program Files
1515 // directory (e.g. C:\Program Files\) and UAC is turned on and
1516 // we can elevate (e.g. user has a split token)
1518 // Note: this does note attempt to handle the case where UAC is
1519 // turned on and the installation directory is in a restricted
1520 // location that requires admin privileges to update other than
1521 // Program Files.
1523 var userCanElevate = false;
1525 if (parseFloat(windowsVersion) >= 6) {
1526 try {
1527 var fileLocator = Cc["@mozilla.org/file/directory_service;1"].
1528 getService(Ci.nsIProperties);
1529 // KEY_UPDROOT will fail and throw an exception if
1530 // appDir is not under the Program Files, so we rely on that
1531 var dir = fileLocator.get(KEY_UPDROOT, Ci.nsIFile);
1532 // appDir is under Program Files, so check if the user can elevate
1533 userCanElevate = gApp.QueryInterface(Ci.nsIWinAppHelper).
1534 userCanElevate;
1535 LOG("UpdateService",
1536 "canUpdate? on Vista, userCanElevate = " + userCanElevate);
1538 catch (ex) {
1539 // When the installation directory is not under Program Files,
1540 // fall through to checking if write access to the
1541 // installation directory is available.
1542 LOG("UpdateService",
1543 "canUpdate? on Vista, appDir is not under the Program Files");
1547 // On Windows, we no longer store the update under the app dir
1548 // if the app dir is under C:\Program Files.
1550 // If we are on Windows (including Vista, if we can't elevate)
1551 // we need to check that
1552 // we can create and remove files from the actual app directory
1553 // (like C:\Program Files\Mozilla Firefox). If we can't
1554 // (because this user is not an adminstrator, for example)
1555 // canUpdate() should return false.
1557 // For Vista, we perform this check to enable updating the
1558 // application when the user has write access to the installation
1559 // directory under the following scenarios:
1560 // 1) the installation directory is not under Program Files
1561 // (e.g. C:\Program Files)
1562 // 2) UAC is turned off
1563 // 3) UAC is turned on and the user is not an admin
1564 // (e.g. the user does not have a split token)
1565 // 4) UAC is turned on and the user is already elevated,
1566 // so they can't be elevated again.
1567 if (!userCanElevate) {
1568 // if we're unable to create the test file
1569 // the code below will throw an exception
1570 var actualAppDir = getDir(KEY_APPDIR, []);
1571 var actualAppDirFile = actualAppDir.clone();
1572 actualAppDirFile.append(FILE_PERMS_TEST);
1573 LOG("UpdateService", "canUpdate? testing " + actualAppDirFile.path);
1574 if (!actualAppDirFile.exists()) {
1575 actualAppDirFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
1576 actualAppDirFile.remove(false);
1579 #endif
1581 catch (e) {
1582 LOG("UpdateService", "can't update, no privileges: " + e);
1583 // No write privileges to install directory
1584 return false;
1586 // If the administrator has locked the app update functionality
1587 // OFF - this is not just a user setting, so disable the manual
1588 // UI too.
1589 var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
1590 if (!enabled && gPref.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
1591 LOG("UpdateService", "can't update, disabled by pref");
1592 return false;
1595 // If we don't know the binary platform we're updating, we can't update.
1596 if (!gABI) {
1597 LOG("UpdateService", "can't update, unknown ABI");
1598 return false;
1601 // If we don't know the OS version we're updating, we can't update.
1602 if (!gOSVersion) {
1603 LOG("UpdateService", "can't update, unknown OS version");
1604 return false;
1607 LOG("UpdateService", "can update");
1608 return true;
1612 * See nsIUpdateService.idl
1614 addDownloadListener: function(listener) {
1615 if (!this._downloader) {
1616 LOG("UpdateService", "addDownloadListener: no downloader!\n");
1617 return;
1619 this._downloader.addDownloadListener(listener);
1623 * See nsIUpdateService.idl
1625 removeDownloadListener: function(listener) {
1626 if (!this._downloader) {
1627 LOG("UpdateService", "removeDownloadListener: no downloader!\n");
1628 return;
1630 this._downloader.removeDownloadListener(listener);
1634 * See nsIUpdateService.idl
1636 downloadUpdate: function(update, background) {
1637 if (!update)
1638 throw Cr.NS_ERROR_NULL_POINTER;
1639 if (this.isDownloading) {
1640 if (update.isCompleteUpdate == this._downloader.isCompleteUpdate &&
1641 background == this._downloader.background) {
1642 LOG("UpdateService", "no support for downloading more than one update at a time");
1643 return readStatusFile(getUpdatesDir());
1645 this._downloader.cancel();
1647 this._downloader = new Downloader(background);
1648 return this._downloader.downloadUpdate(update);
1652 * See nsIUpdateService.idl
1654 pauseDownload: function() {
1655 if (this.isDownloading)
1656 this._downloader.cancel();
1660 * See nsIUpdateService.idl
1662 get isDownloading() {
1663 return this._downloader && this._downloader.isBusy;
1666 // nsIClassInfo
1667 flags: Ci.nsIClassInfo.SINGLETON,
1668 implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
1669 getHelperForLanguage: function(language) null,
1670 getInterfaces: function(count) {
1671 var interfaces = [Ci.nsIApplicationUpdateService, Ci.nsITimerCallback,
1672 Ci.nsIObserver];
1673 count.value = interfaces.length;
1674 return interfaces;
1677 classDescription: "Update Service",
1678 contractID: "@mozilla.org/updates/update-service;1",
1679 classID: Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}"),
1680 _xpcom_categories: [{ category: "app-startup", service: true }],
1681 _xpcom_factory: UpdateServiceFactory,
1682 QueryInterface: XPCOMUtils.generateQI([Ci.nsIApplicationUpdateService,
1683 Ci.nsITimerCallback,
1684 Ci.nsIObserver])
1688 * A service to manage active and past updates.
1689 * @constructor
1691 function UpdateManager() {
1692 // Ensure the Active Update file is loaded
1693 var updates = this._loadXMLFileIntoArray(getUpdateFile([FILE_UPDATE_ACTIVE]));
1694 if (updates.length > 0)
1695 this._activeUpdate = updates[0];
1697 UpdateManager.prototype = {
1699 * All previously downloaded and installed updates, as an array of nsIUpdate
1700 * objects.
1702 _updates: null,
1705 * The current actively downloading/installing update, as a nsIUpdate object.
1707 _activeUpdate: null,
1710 * Loads an updates.xml formatted file into an array of nsIUpdate items.
1711 * @param file
1712 * A nsIFile for the updates.xml file
1713 * @returns The array of nsIUpdate items held in the file.
1715 _loadXMLFileIntoArray: function(file) {
1716 if (!file.exists()) {
1717 LOG("UpdateManager", "_loadXMLFileIntoArray: XML File does not exist");
1718 return [];
1721 var result = [];
1722 var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
1723 createInstance(Ci.nsIFileInputStream);
1724 fileStream.init(file, MODE_RDONLY, PERMS_FILE, 0);
1725 try {
1726 var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
1727 createInstance(Ci.nsIDOMParser);
1728 var doc = parser.parseFromStream(fileStream, "UTF-8",
1729 fileStream.available(), "text/xml");
1731 const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
1732 var updateCount = doc.documentElement.childNodes.length;
1733 for (var i = 0; i < updateCount; ++i) {
1734 var updateElement = doc.documentElement.childNodes.item(i);
1735 if (updateElement.nodeType != ELEMENT_NODE ||
1736 updateElement.localName != "update")
1737 continue;
1739 updateElement.QueryInterface(Ci.nsIDOMElement);
1740 try {
1741 var update = new Update(updateElement);
1742 } catch (e) {
1743 LOG("UpdateManager", "_loadXMLFileIntoArray: invalid update");
1744 continue;
1746 result.push(new Update(updateElement));
1749 catch (e) {
1750 LOG("UpdateManager", "_loadXMLFileIntoArray: Error constructing update list " +
1753 fileStream.close();
1754 return result;
1758 * Load the update manager, initializing state from state files.
1760 _ensureUpdates: function() {
1761 if (!this._updates) {
1762 this._updates = this._loadXMLFileIntoArray(getUpdateFile(
1763 [FILE_UPDATES_DB]));
1765 // Make sure that any active update is part of our updates list
1766 var active = this.activeUpdate;
1767 if (active)
1768 this._addUpdate(active);
1773 * See nsIUpdateService.idl
1775 getUpdateAt: function(index) {
1776 this._ensureUpdates();
1777 return this._updates[index];
1781 * See nsIUpdateService.idl
1783 get updateCount() {
1784 this._ensureUpdates();
1785 return this._updates.length;
1789 * See nsIUpdateService.idl
1791 get activeUpdate() {
1792 if (this._activeUpdate &&
1793 this._activeUpdate.channel != getUpdateChannel()) {
1794 // User switched channels, clear out any old active updates and remove
1795 // partial downloads
1796 this._activeUpdate = null;
1798 // Destroy the updates directory, since we're done with it.
1799 cleanUpUpdatesDir();
1801 return this._activeUpdate;
1803 set activeUpdate(activeUpdate) {
1804 this._addUpdate(activeUpdate);
1805 this._activeUpdate = activeUpdate;
1806 if (!activeUpdate) {
1807 // If |activeUpdate| is null, we have updated both lists - the active list
1808 // and the history list, so we want to write both files.
1809 this.saveUpdates();
1811 else
1812 this._writeUpdatesToXMLFile([this._activeUpdate],
1813 getUpdateFile([FILE_UPDATE_ACTIVE]));
1814 return activeUpdate;
1818 * Add an update to the Updates list. If the item already exists in the list,
1819 * replace the existing value with the new value.
1820 * @param update
1821 * The nsIUpdate object to add.
1823 _addUpdate: function(update) {
1824 if (!update)
1825 return;
1826 this._ensureUpdates();
1827 if (this._updates) {
1828 for (var i = 0; i < this._updates.length; ++i) {
1829 if (this._updates[i] &&
1830 this._updates[i].version == update.version &&
1831 this._updates[i].buildID == update.buildID) {
1832 // Replace the existing entry with the new value, updating
1833 // all metadata.
1834 this._updates[i] = update;
1835 return;
1839 // Otherwise add it to the front of the list.
1840 if (update)
1841 this._updates = [update].concat(this._updates);
1845 * Serializes an array of updates to an XML file
1846 * @param updates
1847 * An array of nsIUpdate objects
1848 * @param file
1849 * The nsIFile object to serialize to
1851 _writeUpdatesToXMLFile: function(updates, file) {
1852 var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
1853 createInstance(Ci.nsIFileOutputStream);
1854 var modeFlags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
1855 if (!file.exists())
1856 file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
1857 fos.init(file, modeFlags, PERMS_FILE, 0);
1859 try {
1860 var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
1861 createInstance(Ci.nsIDOMParser);
1862 const EMPTY_UPDATES_DOCUMENT = "<?xml version=\"1.0\"?><updates xmlns=\"http://www.mozilla.org/2005/app-update\"></updates>";
1863 var doc = parser.parseFromString(EMPTY_UPDATES_DOCUMENT, "text/xml");
1865 for (var i = 0; i < updates.length; ++i) {
1866 if (updates[i])
1867 doc.documentElement.appendChild(updates[i].serialize(doc));
1870 var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
1871 createInstance(Ci.nsIDOMSerializer);
1872 serializer.serializeToStream(doc.documentElement, fos, null);
1874 catch (e) {
1877 closeSafeOutputStream(fos);
1881 * See nsIUpdateService.idl
1883 saveUpdates: function() {
1884 this._writeUpdatesToXMLFile([this._activeUpdate],
1885 getUpdateFile([FILE_UPDATE_ACTIVE]));
1886 if (this._updates) {
1887 this._writeUpdatesToXMLFile(this._updates.slice(0, 10),
1888 getUpdateFile([FILE_UPDATES_DB]));
1892 classDescription: "Update Manager",
1893 contractID: "@mozilla.org/updates/update-manager;1",
1894 classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
1895 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager])
1900 * Checker
1901 * Checks for new Updates
1902 * @constructor
1904 function Checker() {
1906 Checker.prototype = {
1908 * The XMLHttpRequest object that performs the connection.
1910 _request : null,
1913 * The nsIUpdateCheckListener callback
1915 _callback : null,
1918 * The URL of the update service XML file to connect to that contains details
1919 * about available updates.
1921 getUpdateURL: function(force) {
1922 this._forced = force;
1924 // Use the override URL if specified.
1925 var url = getPref("getCharPref", PREF_APP_UPDATE_URL_OVERRIDE, null);
1927 // Otherwise, construct the update URL from component parts.
1928 if (!url) {
1929 try {
1930 url = getDefaultPrefBranch().getCharPref(PREF_APP_UPDATE_URL);
1931 } catch (e) {
1935 if (!url || url == "") {
1936 LOG("Checker", "Update URL not defined");
1937 return null;
1940 url = url.replace(/%PRODUCT%/g, gApp.name);
1941 url = url.replace(/%VERSION%/g, gApp.version);
1942 url = url.replace(/%BUILD_ID%/g, gApp.appBuildID);
1943 url = url.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI);
1944 url = url.replace(/%OS_VERSION%/g, gOSVersion);
1945 if (/%LOCALE%/.test(url))
1946 url = url.replace(/%LOCALE%/g, getLocale());
1947 url = url.replace(/%CHANNEL%/g, getUpdateChannel());
1948 url = url.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion);
1949 url = url.replace(/%DISTRIBUTION%/g,
1950 getDistributionPrefValue(PREF_APP_DISTRIBUTION));
1951 url = url.replace(/%DISTRIBUTION_VERSION%/g,
1952 getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
1953 url = url.replace(/\+/g, "%2B");
1955 if (force)
1956 url += (url.indexOf("?") != -1 ? "&" : "?") + "force=1";
1958 LOG("Checker", "update url: " + url);
1959 return url;
1963 * See nsIUpdateService.idl
1965 checkForUpdates: function(listener, force) {
1966 if (!listener)
1967 throw Cr.NS_ERROR_NULL_POINTER;
1969 var url = this.getUpdateURL(force);
1970 if (!url || (!this.enabled && !force))
1971 return;
1973 this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
1974 createInstance(Ci.nsIXMLHttpRequest);
1975 this._request.open("GET", url, true);
1976 this._request.channel.notificationCallbacks = new BadCertHandler();
1977 this._request.overrideMimeType("text/xml");
1978 this._request.setRequestHeader("Cache-Control", "no-cache");
1980 var self = this;
1981 this._request.onerror = function(event) { self.onError(event); };
1982 this._request.onload = function(event) { self.onLoad(event); };
1983 this._request.onprogress = function(event) { self.onProgress(event); };
1985 LOG("Checker", "checkForUpdates: sending request to " + url);
1986 this._request.send(null);
1988 this._callback = listener;
1992 * When progress associated with the XMLHttpRequest is received.
1993 * @param event
1994 * The nsIDOMLSProgressEvent for the load.
1996 onProgress: function(event) {
1997 LOG("Checker", "onProgress: " + event.position + "/" + event.totalSize);
1998 this._callback.onProgress(event.target, event.position, event.totalSize);
2002 * Returns an array of nsIUpdate objects discovered by the update check.
2004 get _updates() {
2005 var updatesElement = this._request.responseXML.documentElement;
2006 if (!updatesElement) {
2007 LOG("Checker", "get_updates: empty updates document?!");
2008 return [];
2011 if (updatesElement.nodeName != "updates") {
2012 LOG("Checker", "get_updates: unexpected node name!");
2013 throw "";
2016 const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
2017 var updates = [];
2018 for (var i = 0; i < updatesElement.childNodes.length; ++i) {
2019 var updateElement = updatesElement.childNodes.item(i);
2020 if (updateElement.nodeType != ELEMENT_NODE ||
2021 updateElement.localName != "update")
2022 continue;
2024 updateElement.QueryInterface(Ci.nsIDOMElement);
2025 try {
2026 var update = new Update(updateElement);
2027 } catch (e) {
2028 LOG("Checker", "Invalid <update/>, ignoring...");
2029 continue;
2031 update.serviceURL = this.getUpdateURL(this._forced);
2032 update.channel = getUpdateChannel();
2033 updates.push(update);
2036 return updates;
2040 * Returns the status code for the XMLHttpRequest
2042 _getChannelStatus: function(request) {
2043 var status = 0;
2044 try {
2045 status = request.status;
2047 catch (e) {
2050 if (status == 0)
2051 status = request.channel.QueryInterface(Ci.nsIRequest).status;
2052 return status;
2056 * The XMLHttpRequest succeeded and the document was loaded.
2057 * @param event
2058 * The nsIDOMEvent for the load
2060 onLoad: function(event) {
2061 LOG("Checker", "onLoad: request completed downloading document");
2063 try {
2064 checkCert(this._request.channel);
2066 // Analyze the resulting DOM and determine the set of updates to install
2067 var updates = this._updates;
2069 LOG("Checker", "Updates available: " + updates.length);
2071 // ... and tell the Update Service about what we discovered.
2072 this._callback.onCheckComplete(event.target, updates, updates.length);
2074 catch (e) {
2075 LOG("Checker", "There was a problem with the update service URL specified, " +
2076 "either the XML file was malformed or it does not exist at the location " +
2077 "specified. Exception: " + e);
2078 var request = event.target;
2079 var status = this._getChannelStatus(request);
2080 LOG("Checker", "onLoad: request.status = " + status);
2081 var update = new Update(null);
2082 update.statusText = getStatusTextFromCode(status, 404);
2083 this._callback.onError(request, update);
2086 this._request = null;
2090 * There was an error of some kind during the XMLHttpRequest
2091 * @param event
2092 * The nsIDOMEvent for the load
2094 onError: function(event) {
2095 LOG("Checker", "onError: error during load");
2097 var request = event.target;
2098 var status = this._getChannelStatus(request);
2099 LOG("Checker", "onError: request.status = " + status);
2101 // If we can't find an error string specific to this status code,
2102 // just use the 200 message from above, which means everything
2103 // "looks" fine but there was probably an XML error or a bogus file.
2104 var update = new Update(null);
2105 update.statusText = getStatusTextFromCode(status, 200);
2106 this._callback.onError(request, update);
2108 this._request = null;
2112 * Whether or not we are allowed to do update checking.
2114 _enabled: true,
2115 get enabled() {
2116 var aus = Cc["@mozilla.org/updates/update-service;1"].
2117 getService(Ci.nsIApplicationUpdateService);
2118 var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) &&
2119 aus.canUpdate && this._enabled;
2120 return enabled;
2124 * See nsIUpdateService.idl
2126 stopChecking: function(duration) {
2127 // Always stop the current check
2128 if (this._request)
2129 this._request.abort();
2131 switch (duration) {
2132 case Ci.nsIUpdateChecker.CURRENT_SESSION:
2133 this._enabled = false;
2134 break;
2135 case Ci.nsIUpdateChecker.ANY_CHECKS:
2136 this._enabled = false;
2137 gPref.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled);
2138 break;
2142 classDescription: "Update Checker",
2143 contractID: "@mozilla.org/updates/update-checker;1",
2144 classID: Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"),
2145 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateChecker])
2149 * Manages the download of updates
2150 * @param background
2151 * Whether or not this downloader is operating in background
2152 * update mode.
2153 * @constructor
2155 function Downloader(background) {
2156 this.background = background;
2158 Downloader.prototype = {
2160 * The nsIUpdatePatch that we are downloading
2162 _patch: null,
2165 * The nsIUpdate that we are downloading
2167 _update: null,
2170 * The nsIIncrementalDownload object handling the download
2172 _request: null,
2175 * Whether or not the update being downloaded is a complete replacement of
2176 * the user's existing installation or a patch representing the difference
2177 * between the new version and the previous version.
2179 isCompleteUpdate: null,
2182 * Cancels the active download.
2184 cancel: function() {
2185 if (this._request && this._request instanceof Ci.nsIRequest) {
2186 const NS_BINDING_ABORTED = 0x804b0002;
2187 this._request.cancel(NS_BINDING_ABORTED);
2192 * Whether or not a patch has been downloaded and staged for installation.
2194 get patchIsStaged() {
2195 return readStatusFile(getUpdatesDir()) == STATE_PENDING;
2199 * Verify the downloaded file. We assume that the download is complete at
2200 * this point.
2202 _verifyDownload: function() {
2203 if (!this._request)
2204 return false;
2206 var destination = this._request.destination;
2208 // Ensure that the file size matches the expected file size.
2209 if (destination.fileSize != this._patch.size)
2210 return false;
2212 var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
2213 createInstance(Ci.nsIFileInputStream);
2214 fileStream.init(destination, MODE_RDONLY, PERMS_FILE, 0);
2216 try {
2217 var hash = Cc["@mozilla.org/security/hash;1"].
2218 createInstance(Ci.nsICryptoHash);
2219 var hashFunction = Ci.nsICryptoHash[this._patch.hashFunction.toUpperCase()];
2220 if (hashFunction == undefined)
2221 throw Cr.NS_ERROR_UNEXPECTED;
2222 hash.init(hashFunction);
2223 hash.updateFromStream(fileStream, -1);
2224 // NOTE: For now, we assume that the format of _patch.hashValue is hex
2225 // encoded binary (such as what is typically output by programs like
2226 // sha1sum). In the future, this may change to base64 depending on how
2227 // we choose to compute these hashes.
2228 digest = binaryToHex(hash.finish(false));
2229 } catch (e) {
2230 LOG("Downloader", "failed to compute hash of downloaded update archive");
2231 digest = "";
2234 fileStream.close();
2236 return digest == this._patch.hashValue.toLowerCase();
2240 * Select the patch to use given the current state of updateDir and the given
2241 * set of update patches.
2242 * @param update
2243 * A nsIUpdate object to select a patch from
2244 * @param updateDir
2245 * A nsIFile representing the update directory
2246 * @returns A nsIUpdatePatch object to download
2248 _selectPatch: function(update, updateDir) {
2249 // Given an update to download, we will always try to download the patch
2250 // for a partial update over the patch for a full update.
2253 * Return the first UpdatePatch with the given type.
2254 * @param type
2255 * The type of the patch ("complete" or "partial")
2256 * @returns A nsIUpdatePatch object matching the type specified
2258 function getPatchOfType(type) {
2259 for (var i = 0; i < update.patchCount; ++i) {
2260 var patch = update.getPatchAt(i);
2261 if (patch && patch.type == type)
2262 return patch;
2264 return null;
2267 // Look to see if any of the patches in the Update object has been
2268 // pre-selected for download, otherwise we must figure out which one
2269 // to select ourselves.
2270 var selectedPatch = update.selectedPatch;
2272 var state = readStatusFile(updateDir);
2274 // If this is a patch that we know about, then select it. If it is a patch
2275 // that we do not know about, then remove it and use our default logic.
2276 var useComplete = false;
2277 if (selectedPatch) {
2278 LOG("Downloader", "found existing patch [state="+state+"]");
2279 switch (state) {
2280 case STATE_DOWNLOADING:
2281 LOG("Downloader", "resuming download");
2282 return selectedPatch;
2283 case STATE_PENDING:
2284 LOG("Downloader", "already downloaded and staged");
2285 return null;
2286 default:
2287 // Something went wrong when we tried to apply the previous patch.
2288 // Try the complete patch next time.
2289 if (update && selectedPatch.type == "partial") {
2290 useComplete = true;
2291 } else {
2292 // This is a pretty fatal error. Just bail.
2293 LOG("Downloader", "failed to apply complete patch!");
2294 writeStatusFile(updateDir, STATE_NONE);
2295 return null;
2299 selectedPatch = null;
2302 // If we were not able to discover an update from a previous download, we
2303 // select the best patch from the given set.
2304 var partialPatch = getPatchOfType("partial");
2305 if (!useComplete)
2306 selectedPatch = partialPatch;
2307 if (!selectedPatch) {
2308 if (partialPatch)
2309 partialPatch.selected = false;
2310 selectedPatch = getPatchOfType("complete");
2313 // Restore the updateDir since we may have deleted it.
2314 updateDir = getUpdatesDir();
2316 // if update only contains a partial patch, selectedPatch == null here if
2317 // the partial patch has been attempted and fails and we're trying to get a
2318 // complete patch
2319 if (selectedPatch)
2320 selectedPatch.selected = true;
2322 update.isCompleteUpdate = useComplete;
2324 // Reset the Active Update object on the Update Manager and flush the
2325 // Active Update DB.
2326 var um = Cc["@mozilla.org/updates/update-manager;1"].
2327 getService(Ci.nsIUpdateManager);
2328 um.activeUpdate = update;
2330 return selectedPatch;
2334 * Whether or not we are currently downloading something.
2336 get isBusy() {
2337 return this._request != null;
2341 * Download and stage the given update.
2342 * @param update
2343 * A nsIUpdate object to download a patch for. Cannot be null.
2345 downloadUpdate: function(update) {
2346 if (!update)
2347 throw Cr.NS_ERROR_NULL_POINTER;
2349 var updateDir = getUpdatesDir();
2351 this._update = update;
2353 // This function may return null, which indicates that there are no patches
2354 // to download.
2355 this._patch = this._selectPatch(update, updateDir);
2356 if (!this._patch) {
2357 LOG("Downloader", "no patch to download");
2358 return readStatusFile(updateDir);
2360 this.isCompleteUpdate = this._patch.type == "complete";
2362 var patchFile = updateDir.clone();
2363 patchFile.append(FILE_UPDATE_ARCHIVE);
2365 var ios = Cc["@mozilla.org/network/io-service;1"].
2366 getService(Ci.nsIIOService);
2367 var uri = ios.newURI(this._patch.URL, null, null);
2369 this._request = Cc["@mozilla.org/network/incremental-download;1"].
2370 createInstance(Ci.nsIIncrementalDownload);
2372 LOG("Downloader", "downloadUpdate: Downloading from " + uri.spec + " to " +
2373 patchFile.path);
2375 var interval = this.background ? DOWNLOAD_BACKGROUND_INTERVAL
2376 : DOWNLOAD_FOREGROUND_INTERVAL;
2377 this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval);
2378 this._request.start(this, null);
2380 writeStatusFile(updateDir, STATE_DOWNLOADING);
2381 this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
2382 this._patch.state = STATE_DOWNLOADING;
2383 var um = Cc["@mozilla.org/updates/update-manager;1"].
2384 getService(Ci.nsIUpdateManager);
2385 um.saveUpdates();
2386 return STATE_DOWNLOADING;
2390 * An array of download listeners to notify when we receive
2391 * nsIRequestObserver or nsIProgressEventSink method calls.
2393 _listeners: [],
2396 * Adds a listener to the download process
2397 * @param listener
2398 * A download listener, implementing nsIRequestObserver and
2399 * nsIProgressEventSink
2401 addDownloadListener: function(listener) {
2402 for (var i = 0; i < this._listeners.length; ++i) {
2403 if (this._listeners[i] == listener)
2404 return;
2406 this._listeners.push(listener);
2410 * Removes a download listener
2411 * @param listener
2412 * The listener to remove.
2414 removeDownloadListener: function(listener) {
2415 for (var i = 0; i < this._listeners.length; ++i) {
2416 if (this._listeners[i] == listener) {
2417 this._listeners.splice(i, 1);
2418 return;
2424 * When the async request begins
2425 * @param request
2426 * The nsIRequest object for the transfer
2427 * @param context
2428 * Additional data
2430 onStartRequest: function(request, context) {
2431 request.QueryInterface(Ci.nsIIncrementalDownload);
2432 LOG("Downloader", "onStartRequest: " + request.URI.spec);
2434 var listenerCount = this._listeners.length;
2435 for (var i = 0; i < listenerCount; ++i)
2436 this._listeners[i].onStartRequest(request, context);
2440 * When new data has been downloaded
2441 * @param request
2442 * The nsIRequest object for the transfer
2443 * @param context
2444 * Additional data
2445 * @param progress
2446 * The current number of bytes transferred
2447 * @param maxProgress
2448 * The total number of bytes that must be transferred
2450 onProgress: function(request, context, progress, maxProgress) {
2451 request.QueryInterface(Ci.nsIIncrementalDownload);
2452 LOG("Downloader.onProgress", "onProgress: " + request.URI.spec + ", " + progress + "/" + maxProgress);
2454 var listenerCount = this._listeners.length;
2455 for (var i = 0; i < listenerCount; ++i) {
2456 var listener = this._listeners[i];
2457 if (listener instanceof Ci.nsIProgressEventSink)
2458 listener.onProgress(request, context, progress, maxProgress);
2463 * When we have new status text
2464 * @param request
2465 * The nsIRequest object for the transfer
2466 * @param context
2467 * Additional data
2468 * @param status
2469 * A status code
2470 * @param statusText
2471 * Human readable version of |status|
2473 onStatus: function(request, context, status, statusText) {
2474 request.QueryInterface(Ci.nsIIncrementalDownload);
2475 LOG("Downloader", "onStatus: " + request.URI.spec + " status = " + status + ", text = " + statusText);
2476 var listenerCount = this._listeners.length;
2477 for (var i = 0; i < listenerCount; ++i) {
2478 var listener = this._listeners[i];
2479 if (listener instanceof Ci.nsIProgressEventSink)
2480 listener.onStatus(request, context, status, statusText);
2485 * When data transfer ceases
2486 * @param request
2487 * The nsIRequest object for the transfer
2488 * @param context
2489 * Additional data
2490 * @param status
2491 * Status code containing the reason for the cessation.
2493 onStopRequest: function(request, context, status) {
2494 request.QueryInterface(Ci.nsIIncrementalDownload);
2495 LOG("Downloader", "onStopRequest: " + request.URI.spec + ", status = " + status);
2497 var state = this._patch.state;
2498 var shouldShowPrompt = false;
2499 var deleteActiveUpdate = false;
2500 const NS_BINDING_ABORTED = 0x804b0002;
2501 const NS_ERROR_ABORT = 0x80004004;
2502 if (Components.isSuccessCode(status)) {
2503 var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
2504 getService(Ci.nsIStringBundleService);
2505 var updateStrings = sbs.createBundle(URI_UPDATES_PROPERTIES);
2506 if (this._verifyDownload()) {
2507 state = STATE_PENDING;
2509 // We only need to explicitly show the prompt if this is a backround
2510 // download, since otherwise some kind of UI is already visible and
2511 // that UI will notify.
2512 if (this.background)
2513 shouldShowPrompt = true;
2515 // Tell the updater.exe we're ready to apply.
2516 writeStatusFile(getUpdatesDir(), state);
2517 this._update.installDate = (new Date()).getTime();
2518 this._update.statusText = updateStrings.GetStringFromName("installPending");
2519 } else {
2520 LOG("Downloader", "onStopRequest: download verification failed");
2521 state = STATE_DOWNLOAD_FAILED;
2523 var brandStrings = sbs.createBundle(URI_BRAND_PROPERTIES);
2524 var brandShortName = brandStrings.GetStringFromName("brandShortName");
2525 this._update.statusText = updateStrings.formatStringFromName("verificationError",
2526 [brandShortName], 1);
2527 // TODO: use more informative error code here
2528 status = Cr.NS_ERROR_UNEXPECTED;
2530 // Yes, this code is a string.
2531 const vfCode = "verification_failed";
2532 var message = getStatusTextFromCode(vfCode, vfCode);
2533 this._update.statusText = message;
2535 if (this._update.isCompleteUpdate)
2536 deleteActiveUpdate = true;
2538 // Destroy the updates directory, since we're done with it.
2539 cleanUpUpdatesDir();
2542 else if (status != NS_BINDING_ABORTED &&
2543 status != NS_ERROR_ABORT) {
2544 LOG("Downloader", "onStopRequest: Non-verification failure");
2545 // Some sort of other failure, log this in the |statusText| property
2546 state = STATE_DOWNLOAD_FAILED;
2548 // XXXben - if |request| (The Incremental Download) provided a means
2549 // for accessing the http channel we could do more here.
2551 const NS_BINDING_FAILED = 2152398849;
2552 this._update.statusText = getStatusTextFromCode(status,
2553 NS_BINDING_FAILED);
2555 // Destroy the updates directory, since we're done with it.
2556 cleanUpUpdatesDir();
2558 deleteActiveUpdate = true;
2560 LOG("Downloader", "Setting state to: " + state);
2561 this._patch.state = state;
2562 var um = Cc["@mozilla.org/updates/update-manager;1"].
2563 getService(Ci.nsIUpdateManager);
2564 if (deleteActiveUpdate) {
2565 this._update.installDate = (new Date()).getTime();
2566 um.activeUpdate = null;
2568 um.saveUpdates();
2570 var listenerCount = this._listeners.length;
2571 for (var i = 0; i < listenerCount; ++i)
2572 this._listeners[i].onStopRequest(request, context, status);
2574 this._request = null;
2576 if (state == STATE_DOWNLOAD_FAILED) {
2577 if (!this._update.isCompleteUpdate) {
2578 var allFailed = true;
2580 // If we were downloading a patch and the patch verification phase
2581 // failed, log this and then commence downloading the complete update.
2582 LOG("Downloader", "onStopRequest: Verification of patch failed, downloading complete update");
2583 this._update.isCompleteUpdate = true;
2584 var status = this.downloadUpdate(this._update);
2586 if (status == STATE_NONE) {
2587 cleanupActiveUpdate();
2588 } else {
2589 allFailed = false;
2591 // This will reset the |.state| property on this._update if a new
2592 // download initiates.
2595 // if we still fail after trying a complete download, give up completely
2596 if (allFailed) {
2597 // In all other failure cases, i.e. we're S.O.L. - no more failing over
2598 // ...
2600 // If this was ever a foreground download, and now there is no UI active
2601 // (e.g. because the user closed the download window) and there was an
2602 // error, we must notify now. Otherwise we can keep the failure to
2603 // ourselves since the user won't be expecting it.
2604 try {
2605 this._update.QueryInterface(Ci.nsIWritablePropertyBag);
2606 var fgdl = this._update.getProperty("foregroundDownload");
2608 catch (e) {
2611 if (fgdl == "true") {
2612 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
2613 createInstance(Ci.nsIUpdatePrompt);
2614 this._update.QueryInterface(Ci.nsIWritablePropertyBag);
2615 this._update.setProperty("downloadFailed", "true");
2616 prompter.showUpdateError(this._update);
2620 // the complete download succeeded or total failure was handled, so exit
2621 return;
2624 // Do this after *everything* else, since it will likely cause the app
2625 // to shut down.
2626 if (shouldShowPrompt) {
2627 // Notify the user that an update has been downloaded and is ready for
2628 // installation (i.e. that they should restart the application). We do
2629 // not notify on failed update attempts.
2630 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
2631 createInstance(Ci.nsIUpdatePrompt);
2632 prompter.showUpdateDownloaded(this._update, true);
2637 * See nsIInterfaceRequestor.idl
2639 getInterface: function(iid) {
2640 // The network request may require proxy authentication, so provide the
2641 // default nsIAuthPrompt if requested.
2642 if (iid.equals(Ci.nsIAuthPrompt)) {
2643 var prompt = Cc["@mozilla.org/network/default-auth-prompt;1"].
2644 createInstance();
2645 return prompt.QueryInterface(iid);
2647 throw Cr.NS_ERROR_NO_INTERFACE;
2650 QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
2651 Ci.nsIProgressEventSink,
2652 Ci.nsIInterfaceRequestor])
2656 * A manager for update check timers. Manages timers that fire over long
2657 * periods of time (e.g. days, weeks).
2658 * @constructor
2660 function TimerManager() {
2661 getObserverService().addObserver(this, "xpcom-shutdown", false);
2663 this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
2664 var timerInterval = getPref("getIntPref", PREF_APP_UPDATE_TIMER, 600000);
2665 this._timer.initWithCallback(this, timerInterval,
2666 Ci.nsITimer.TYPE_REPEATING_SLACK);
2668 TimerManager.prototype = {
2670 * See nsIObserver.idl
2672 observe: function(subject, topic, data) {
2673 if (topic == "xpcom-shutdown") {
2674 getObserverService().removeObserver(this, "xpcom-shutdown");
2676 // Release everything we hold onto.
2677 for (var timerID in this._timers)
2678 delete this._timers[timerID];
2679 this._timer = null;
2680 this._timers = null;
2685 * The Checker Timer
2687 _timer: null,
2690 * The set of registered timers.
2692 _timers: { },
2695 * Called when the checking timer fires.
2696 * @param timer
2697 * The checking timer that fired.
2699 notify: function(timer) {
2700 for (var timerID in this._timers) {
2701 var timerData = this._timers[timerID];
2702 var lastUpdateTime = timerData.lastUpdateTime;
2703 var now = Math.round(Date.now() / 1000);
2705 // Fudge the lastUpdateTime by some random increment of the update
2706 // check interval (e.g. some random slice of 10 minutes) so that when
2707 // the time comes to check, we offset each client request by a random
2708 // amount so they don't all hit at once. app.update.timer is in milliseconds,
2709 // whereas app.update.lastUpdateTime is in seconds
2710 var timerInterval = getPref("getIntPref", PREF_APP_UPDATE_TIMER, 600000);
2711 lastUpdateTime += Math.round(Math.random() * timerInterval / 1000);
2713 if ((now - lastUpdateTime) > timerData.interval &&
2714 timerData.callback instanceof Ci.nsITimerCallback) {
2715 timerData.callback.notify(timer);
2716 timerData.lastUpdateTime = now;
2717 var preference = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
2718 gPref.setIntPref(preference, now);
2724 * See nsIUpdateService.idl
2726 registerTimer: function(id, callback, interval) {
2727 var preference = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
2728 var now = Math.round(Date.now() / 1000);
2729 var lastUpdateTime = null;
2730 if (gPref.prefHasUserValue(preference)) {
2731 lastUpdateTime = gPref.getIntPref(preference);
2732 } else {
2733 gPref.setIntPref(preference, now);
2734 lastUpdateTime = now;
2736 this._timers[id] = { callback : callback,
2737 interval : interval,
2738 lastUpdateTime : lastUpdateTime };
2741 classDescription: "Timer Manager",
2742 contractID: "@mozilla.org/updates/timer-manager;1",
2743 classID: Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"),
2744 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateTimerManager,
2745 Ci.nsITimerCallback,
2746 Ci.nsIObserver])
2750 * UpdatePrompt
2751 * An object which can prompt the user with information about updates, request
2752 * action, etc. Embedding clients can override this component with one that
2753 * invokes a native front end.
2754 * @constructor
2756 function UpdatePrompt() {
2758 UpdatePrompt.prototype = {
2760 * See nsIUpdateService.idl
2762 checkForUpdates: function() {
2763 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, "Update:Wizard",
2764 null, null);
2768 * See nsIUpdateService.idl
2770 showUpdateAvailable: function(update) {
2771 if (!this._enabled)
2772 return;
2773 var bundle = this._updateBundle;
2774 var stringsPrefix = "updateAvailable_" + update.type + ".";
2775 var title = bundle.formatStringFromName(stringsPrefix + "title",
2776 [update.name], 1);
2777 var text = bundle.GetStringFromName(stringsPrefix + "text");
2778 var imageUrl = "";
2779 this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
2780 "Update:Wizard", "updatesavailable", update,
2781 title, text, imageUrl);
2785 * See nsIUpdateService.idl
2787 showUpdateDownloaded: function(update, background) {
2788 if (background) {
2789 if (!this._enabled)
2790 return;
2791 var bundle = this._updateBundle;
2792 var stringsPrefix = "updateDownloaded_" + update.type + ".";
2793 var title = bundle.formatStringFromName(stringsPrefix + "title",
2794 [update.name], 1);
2795 var text = bundle.GetStringFromName(stringsPrefix + "text");
2796 var imageUrl = "";
2797 this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
2798 "Update:Wizard", "finishedBackground", update,
2799 title, text, imageUrl);
2800 } else {
2801 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
2802 "Update:Wizard", "finishedBackground", update);
2807 * See nsIUpdateService.idl
2809 showUpdateInstalled: function(update) {
2810 var showUpdateInstalledUI = getPref("getBoolPref",
2811 PREF_APP_UPDATE_SHOW_INSTALLED_UI, true);
2812 if (this._enabled && showUpdateInstalledUI) {
2813 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, "Update:Wizard",
2814 "installed", update);
2819 * See nsIUpdateService.idl
2821 showUpdateError: function(update) {
2822 if (this._enabled) {
2823 // In some cases, we want to just show a simple alert dialog:
2824 if (update.state == STATE_FAILED && update.errorCode == WRITE_ERROR) {
2825 var updateBundle = this._updateBundle;
2826 var title = updateBundle.GetStringFromName("updaterIOErrorTitle");
2827 var text = updateBundle.formatStringFromName("updaterIOErrorMsg",
2828 [gApp.name, gApp.name], 2);
2829 var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
2830 getService(Ci.nsIWindowWatcher);
2831 ww.getNewPrompter(null).alert(title, text);
2832 } else {
2833 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, "Update:Wizard",
2834 "errors", update);
2840 * See nsIUpdateService.idl
2842 showUpdateHistory: function(parent) {
2843 this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal,dialog=yes", "Update:History",
2844 null, null);
2848 * Whether or not we are enabled (i.e. not in Silent mode)
2850 get _enabled() {
2851 return !getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false);
2854 get _updateBundle() {
2855 return Cc["@mozilla.org/intl/stringbundle;1"].
2856 getService(Ci.nsIStringBundleService).
2857 createBundle(URI_UPDATES_PROPERTIES);
2861 * Initiate a less obtrusive UI, starting with a non-modal notification alert
2862 * @param parent
2863 * A parent window, can be null
2864 * @param uri
2865 * The URI string of the dialog to show
2866 * @param name
2867 * The Window Name of the dialog to show, in case it is already open
2868 * and can merely be focused
2869 * @param page
2870 * The page of the wizard to be displayed, if one is already open.
2871 * @param update
2872 * An update to pass to the UI in the window arguments.
2873 * Can be null
2874 * @param title
2875 * The title for the notification alert.
2876 * @param text
2877 * The contents of the notification alert.
2878 * @param imageUrl
2879 * A URL identifying the image to put in the notification alert.
2881 _showUnobtrusiveUI: function(parent, uri, features, name, page, update,
2882 title, text, imageUrl) {
2883 var observer = {
2884 updatePrompt: this,
2885 service: null,
2886 timer: null,
2887 notify: function () {
2888 // the user hasn't restarted yet => prompt when idle
2889 this.service.removeObserver(this, "quit-application");
2890 this.updatePrompt._showUIWhenIdle(parent, uri, features, name, page, update);
2892 observe: function (aSubject, aTopic, aData) {
2893 switch (aTopic) {
2894 case "alertclickcallback":
2895 this.updatePrompt._showUI(parent, uri, features, name, page, update);
2896 // fall thru
2897 case "quit-application":
2898 this.timer.cancel();
2899 this.service.removeObserver(this, "quit-application");
2900 break;
2905 try {
2906 var notifier = Cc["@mozilla.org/alerts-service;1"].
2907 getService(Ci.nsIAlertsService);
2908 notifier.showAlertNotification(imageUrl, title, text, true, "", observer);
2910 catch (e) {
2911 // Failed to retrieve alerts service, platform unsupported
2912 this._showUIWhenIdle(parent, uri, features, name, page, update);
2913 return;
2916 observer.service = Cc["@mozilla.org/observer-service;1"].
2917 getService(Ci.nsIObserverService);
2918 observer.service.addObserver(observer, "quit-application", false);
2920 // Give the user x seconds to react before showing the big UI
2921 var promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
2922 observer.timer = Cc["@mozilla.org/timer;1"].
2923 createInstance(Ci.nsITimer);
2924 observer.timer.initWithCallback(observer, promptWaitTime * 1000,
2925 observer.timer.TYPE_ONE_SHOT);
2929 * Show the UI when the user was idle
2930 * @param parent
2931 * A parent window, can be null
2932 * @param uri
2933 * The URI string of the dialog to show
2934 * @param name
2935 * The Window Name of the dialog to show, in case it is already open
2936 * and can merely be focused
2937 * @param page
2938 * The page of the wizard to be displayed, if one is already open.
2939 * @param update
2940 * An update to pass to the UI in the window arguments.
2941 * Can be null
2943 _showUIWhenIdle: function(parent, uri, features, name, page, update) {
2944 var idleService = Cc["@mozilla.org/widget/idleservice;1"].
2945 getService(Ci.nsIIdleService);
2947 const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
2948 if (idleService.idleTime / 1000 >= IDLE_TIME) {
2949 this._showUI(parent, uri, features, name, page, update);
2950 } else {
2951 var observerService = Cc["@mozilla.org/observer-service;1"].
2952 getService(Ci.nsIObserverService);
2953 var observer = {
2954 updatePrompt: this,
2955 observe: function (aSubject, aTopic, aData) {
2956 switch (aTopic) {
2957 case "idle":
2958 this.updatePrompt._showUI(parent, uri, features, name, page, update);
2959 // fall thru
2960 case "quit-application":
2961 idleService.removeIdleObserver(this, IDLE_TIME);
2962 observerService.removeObserver(this, "quit-application");
2963 break;
2967 idleService.addIdleObserver(observer, IDLE_TIME);
2968 observerService.addObserver(observer, "quit-application", false);
2973 * Show the Update Checking UI
2974 * @param parent
2975 * A parent window, can be null
2976 * @param uri
2977 * The URI string of the dialog to show
2978 * @param name
2979 * The Window Name of the dialog to show, in case it is already open
2980 * and can merely be focused
2981 * @param page
2982 * The page of the wizard to be displayed, if one is already open.
2983 * @param update
2984 * An update to pass to the UI in the window arguments.
2985 * Can be null
2987 _showUI: function(parent, uri, features, name, page, update) {
2988 var ary = null;
2989 if (update) {
2990 ary = Cc["@mozilla.org/supports-array;1"].
2991 createInstance(Ci.nsISupportsArray);
2992 ary.AppendElement(update);
2995 var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
2996 getService(Ci.nsIWindowMediator);
2997 var win = wm.getMostRecentWindow(name);
2998 if (win) {
2999 if (page && "setCurrentPage" in win)
3000 win.setCurrentPage(page);
3001 win.focus();
3003 else {
3004 var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
3005 if (features)
3006 openFeatures += "," + features;
3007 var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
3008 getService(Ci.nsIWindowWatcher);
3009 ww.openWindow(parent, uri, "", openFeatures, ary);
3013 classDescription: "Update Prompt",
3014 contractID: "@mozilla.org/updates/update-prompt;1",
3015 classID: Components.ID("{27ABA825-35B5-4018-9FDD-F99250A0E722}"),
3016 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt])
3019 function NSGetModule(compMgr, fileSpec)
3020 XPCOMUtils.generateModule([UpdateService, Checker, UpdatePrompt, TimerManager, UpdateManager]);
3023 * Determines whether or there are installed addons which are incompatible
3024 * with this update.
3025 * @param update
3026 * The update to check compatibility against
3027 * @returns true if there are no addons installed that are incompatible with
3028 * the specified update, false otherwise.
3030 function isCompatible(update) {
3031 var em = Cc["@mozilla.org/extensions/manager;1"].
3032 getService(Ci.nsIExtensionManager);
3033 var items = em.getIncompatibleItemList("", update.extensionVersion,
3034 update.platformVersion,
3035 Ci.nsIUpdateItem.TYPE_ANY, false, { });
3036 return items.length == 0;
3040 * Shows a prompt for an update, provided there are no incompatible addons.
3041 * If there are, kick off an update check and see if updates are available
3042 * that will resolve the incompatibilities.
3043 * @param update
3044 * The available update to show
3046 function showPromptIfNoIncompatibilities(update) {
3047 function showPrompt(update) {
3048 LOG("UpdateService", "_selectAndInstallUpdate: need to prompt user before continuing...");
3049 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
3050 createInstance(Ci.nsIUpdatePrompt);
3051 prompter.showUpdateAvailable(update);
3055 * Determines if an addon is compatible with a particular update.
3056 * @param addon
3057 * The addon to check
3058 * @param version
3059 * The extensionVersion of the update to check for compatibility
3060 * against.
3061 * @returns true if the addon is compatible, false otherwise
3063 function addonIsCompatible(addon, version) {
3064 var vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
3065 getService(Ci.nsIVersionComparator);
3066 return (vc.compare(version, addon.minAppVersion) >= 0) &&
3067 (vc.compare(version, addon.maxAppVersion) <= 0);
3071 * An object implementing nsIAddonUpdateCheckListener that looks for
3072 * available updates to addons and if updates are found that will make the
3073 * user's installed addon set compatible with the update, suppresses the
3074 * prompt that would otherwise be shown.
3075 * @param addons
3076 * An array of incompatible addons that are installed.
3077 * @constructor
3079 function Listener(addons) {
3080 this._addons = addons;
3082 Listener.prototype = {
3083 _addons: null,
3086 * See nsIUpdateService.idl
3088 onUpdateStarted: function() {
3090 onUpdateEnded: function() {
3091 // There are still incompatibilities, even after an extension update
3092 // check to see if there were newer, compatible versions available, so
3093 // we have to prompt.
3095 // PREF_APP_UPDATE_INCOMPATIBLE_MODE controls the mode in which we
3096 // handle incompatibilities:
3097 // UPDATE_CHECK_NEWVERSION We count both VersionInfo updates _and_
3098 // NewerVersion updates against the list of incompatible addons
3099 // installed - i.e. if Foo 1.2 is installed and it is incompatible
3100 // with the update, and we find Foo 2.0 which is but which is not
3101 // yet downloaded or installed, then we do NOT prompt because the
3102 // user can download Foo 2.0 when they restart after the update
3103 // during the mismatch checking UI. This is the default, since it
3104 // suppresses most prompt dialogs.
3105 // UPDATE_CHECK_COMPATIBILITY We count only VersionInfo updates
3106 // against the list of incompatible addons installed - i.e. if the
3107 // situation above with Foo 1.2 and available update to 2.0
3108 // applies, we DO show the prompt since a download operation will
3109 // be required after the update. This is not the default and is
3110 // supplied only as a hidden option for those that want it.
3111 var mode = getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE,
3112 Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION);
3113 if ((mode == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION
3114 && this._addons.length) || !isCompatible(update))
3115 showPrompt(update);
3117 onAddonUpdateStarted: function(addon) {
3119 onAddonUpdateEnded: function(addon, status) {
3120 if (status != Ci.nsIAddonUpdateCheckListener.STATUS_UPDATE)
3121 return;
3123 var reqVersion = addon.targetAppID == TOOLKIT_ID ?
3124 update.platformVersion :
3125 update.extensionVersion;
3126 if (!addonIsCompatible(addon, reqVersion))
3127 return;
3129 for (var i = 0; i < this._addons.length; ++i) {
3130 if (this._addons[i] == addon) {
3131 this._addons.splice(i, 1);
3132 break;
3137 QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonUpdateCheckListener])
3140 if (!isCompatible(update)) {
3141 var em = Cc["@mozilla.org/extensions/manager;1"].
3142 getService(Ci.nsIExtensionManager);
3143 var items = em.getIncompatibleItemList("", update.extensionVersion,
3144 update.platformVersion,
3145 Ci.nsIUpdateItem.TYPE_ANY, false,
3146 { });
3148 var listener = new Listener(items);
3149 // See documentation on |mode| above.
3150 var mode = getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE,
3151 Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION);
3152 em.update([], 0, mode, listener);
3154 else
3155 showPrompt(update);