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