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