Bug 302348 - Fix a bunch of client-side update code reliability and bulletproofing issues; see the bug for a full list. r=darin

This commit is contained in:
jwalden%mit.edu 2006-06-27 21:22:36 +00:00
Родитель 067362b93c
Коммит fbacfb72fd
2 изменённых файлов: 113 добавлений и 25 удалений

Просмотреть файл

@ -21,6 +21,7 @@
* Contributor(s): * Contributor(s):
* Ben Goodger <ben@mozilla.org> (Original Author) * Ben Goodger <ben@mozilla.org> (Original Author)
* Asaf Romano <mozilla.mano@sent.com> * Asaf Romano <mozilla.mano@sent.com>
* Jeff Walden <jwalden+code@mit.edu>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or * either the GNU General Public License Version 2 or later (the "GPL"), or
@ -1367,6 +1368,43 @@ var gErrorsPage = {
gUpdates.wiz.getButton("back").disabled = true; gUpdates.wiz.getButton("back").disabled = true;
gUpdates.wiz.getButton("cancel").disabled = true; gUpdates.wiz.getButton("cancel").disabled = true;
gUpdates.wiz.getButton("next").focus(); gUpdates.wiz.getButton("next").focus();
},
/**
* Finish button clicked.
*/
onWizardFinish: function() {
// XXXjwalden COMPLETE AND TOTAL HACK!!!
//
// The problem the following code is working around is this: the update
// service's API for responding to updates is poor. Essentially, all
// the information we can get is that we've started to request a download or
// that the download we've started has finished, with minimal details about
// how it finished which aren't described in the API (and which internally
// are not entirely useful, either -- mostly unuseful nsresults). How
// do you signal the difference between "this download failed" and "all
// downloads failed", particularly if you aim for API compatibility? The
// code in nsApplicationUpdateService only determines the difference *after*
// the current request is stopped, and since the subsequent second call to
// downloadUpdate doesn't start/stop a request, the download listener is
// never notified and whatever code was relying on it just fails without
// notification. The consequence of this is that it's impossible to
// properly remove the download listener.
//
// The code before this patch tried to do the exit after all downloads
// failed but was missing a QueryInterface to work; with it, making sure
// that the download listener is removed in all cases, including in the case
// where the last onStopRequest corresponds to *a* failed download instead
// of to *all* failed downloads, simply means that we have to try to remove
// that listener in the error page spawned by the update service. If there
// were time and API compat weren't a problem, we'd add an onFinish handler
// or something which could signal exactly what happened and not overload
// onStopRequest, but there isn't, so we can't.
//
var updates =
Components.classes["@mozilla.org/updates/update-service;1"].
getService(Components.interfaces.nsIApplicationUpdateService);
updates.removeDownloadListener(gDownloadingPage);
} }
}; };

Просмотреть файл

@ -23,6 +23,7 @@
* Ben Goodger <ben@mozilla.org> (Original Author) * Ben Goodger <ben@mozilla.org> (Original Author)
* Darin Fisher <darin@meer.net> * Darin Fisher <darin@meer.net>
* Ben Turner <bent.mozilla@gmail.com> * Ben Turner <bent.mozilla@gmail.com>
* Jeff Walden <jwalden+code@mit.edu>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or * either the GNU General Public License Version 2 or later (the "GPL"), or
@ -82,7 +83,7 @@ const MODE_TRUNCATE = 0x20;
const PERMS_FILE = 0644; const PERMS_FILE = 0644;
const PERMS_DIRECTORY = 0755; const PERMS_DIRECTORY = 0755;
const STATE_NONE = null; const STATE_NONE = "null";
const STATE_DOWNLOADING = "downloading"; const STATE_DOWNLOADING = "downloading";
const STATE_PENDING = "pending"; const STATE_PENDING = "pending";
const STATE_APPLYING = "applying"; const STATE_APPLYING = "applying";
@ -128,7 +129,7 @@ var gLogEnabled = { };
*/ */
function LOG(module, string) { function LOG(module, string) {
if (module in gLogEnabled) { if (module in gLogEnabled) {
dump("*** " + module + ":" + string + "\n"); dump("*** " + module + ": " + string + "\n");
gConsole.logStringMessage(string); gConsole.logStringMessage(string);
} }
} }
@ -534,6 +535,7 @@ function getObserverService()
* Update Patch * Update Patch
* @param patch * @param patch
* A <patch> element to initialize this object with * A <patch> element to initialize this object with
* @throws if patch has a size of 0
* @constructor * @constructor
*/ */
function UpdatePatch(patch) { function UpdatePatch(patch) {
@ -545,6 +547,12 @@ function UpdatePatch(patch) {
case "selected": case "selected":
this.selected = attr.value == "true"; this.selected = attr.value == "true";
break; break;
case "size":
if (0 == parseInt(attr.value)) {
LOG("UpdatePatch", "0-sized patch!");
throw Components.results.NS_ERROR_ILLEGAL_VALUE;
}
// fall through
default: default:
this[attr.name] = attr.value; this[attr.name] = attr.value;
break; break;
@ -655,6 +663,7 @@ UpdatePatch.prototype = {
* Implements nsIUpdate * Implements nsIUpdate
* @param update * @param update
* An <update> element to initialize this object with * An <update> element to initialize this object with
* @throws if the update contains no patches
* @constructor * @constructor
*/ */
function Update(update) { function Update(update) {
@ -676,8 +685,16 @@ function Update(update) {
continue; continue;
patchElement.QueryInterface(Components.interfaces.nsIDOMElement); patchElement.QueryInterface(Components.interfaces.nsIDOMElement);
this._patches.push(new UpdatePatch(patchElement)); try {
var patch = new UpdatePatch(patchElement);
} catch (e) {
continue;
}
this._patches.push(patch);
} }
if (0 == this._patches.length)
throw Components.results.NS_ERROR_ILLEGAL_VALUE;
for (var i = 0; i < update.attributes.length; ++i) { for (var i = 0; i < update.attributes.length; ++i) {
var attr = update.attributes.item(i); var attr = update.attributes.item(i);
@ -1474,6 +1491,12 @@ UpdateManager.prototype = {
continue; continue;
updateElement.QueryInterface(Components.interfaces.nsIDOMElement); updateElement.QueryInterface(Components.interfaces.nsIDOMElement);
try {
var update = new Update(updateElement);
} catch (e) {
LOG("UpdateManager", "_loadXMLFileIntoArray: invalid update");
continue;
}
result.push(new Update(updateElement)); result.push(new Update(updateElement));
} }
} }
@ -1556,7 +1579,8 @@ UpdateManager.prototype = {
return; return;
if (this._updates) { if (this._updates) {
for (var i = 0; i < this._updates.length; ++i) { for (var i = 0; i < this._updates.length; ++i) {
if (this._updates[i].version == update.version && if (this._updates[i] &&
this._updates[i].version == update.version &&
this._updates[i].buildID == update.buildID) { this._updates[i].buildID == update.buildID) {
// Replace the existing entry with the new value, updating // Replace the existing entry with the new value, updating
// all metadata. // all metadata.
@ -1795,7 +1819,12 @@ Checker.prototype = {
continue; continue;
updateElement.QueryInterface(Components.interfaces.nsIDOMElement); updateElement.QueryInterface(Components.interfaces.nsIDOMElement);
var update = new Update(updateElement); try {
var update = new Update(updateElement);
} catch (e) {
LOG("Checker", "Invalid <update/>, ignoring...");
continue;
}
update.serviceURL = this.updateURL; update.serviceURL = this.updateURL;
update.channel = getUpdateChannel(); update.channel = getUpdateChannel();
updates.push(update); updates.push(update);
@ -2076,8 +2105,13 @@ Downloader.prototype = {
// Restore the updateDir since we may have deleted it. // Restore the updateDir since we may have deleted it.
updateDir = getUpdatesDir(); updateDir = getUpdatesDir();
selectedPatch.selected = true; // if update only contains a partial patch, selectedPatch == null here if
// the partial patch has been attempted and fails and we're trying to get a
// complete patch
if (selectedPatch)
selectedPatch.selected = true;
update.isCompleteUpdate = useComplete; update.isCompleteUpdate = useComplete;
// Reset the Active Update object on the Update Manager and flush the // Reset the Active Update object on the Update Manager and flush the
@ -2338,17 +2372,25 @@ Downloader.prototype = {
if (state == STATE_DOWNLOAD_FAILED) { if (state == STATE_DOWNLOAD_FAILED) {
if (!this._update.isCompleteUpdate) { if (!this._update.isCompleteUpdate) {
var allFailed = true;
// If we were downloading a patch and the patch verification phase // If we were downloading a patch and the patch verification phase
// failed, log this and then commence downloading the complete update. // failed, log this and then commence downloading the complete update.
LOG("Downloader", "onStopRequest: Verification of patch failed, downloading complete update"); LOG("Downloader", "onStopRequest: Verification of patch failed, downloading complete update");
this._update.isCompleteUpdate = true; this._update.isCompleteUpdate = true;
var status = this.downloadUpdate(this._update); var status = this.downloadUpdate(this._update);
if (status == STATE_NONE)
if (status == STATE_NONE) {
cleanupActiveUpdate(); cleanupActiveUpdate();
} else {
allFailed = false;
}
// This will reset the |.state| property on this._update if a new // This will reset the |.state| property on this._update if a new
// download initiates. // download initiates.
} }
else {
// if we still fail after trying a complete download, give up completely
if (allFailed) {
// In all other failure cases, i.e. we're S.O.L. - no more failing over // In all other failure cases, i.e. we're S.O.L. - no more failing over
// ... // ...
@ -2356,12 +2398,13 @@ Downloader.prototype = {
// (e.g. because the user closed the download window) and there was an // (e.g. because the user closed the download window) and there was an
// error, we must notify now. Otherwise we can keep the failure to // error, we must notify now. Otherwise we can keep the failure to
// ourselves since the user won't be expecting it. // ourselves since the user won't be expecting it.
var fgdl = false;
try { try {
fgdl = this._update.getProperty("foregroundDownload"); this._update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
var fgdl = this._update.getProperty("foregroundDownload");
} }
catch (e) { catch (e) {
} }
if (fgdl == "true") { if (fgdl == "true") {
var prompter = var prompter =
Components.classes["@mozilla.org/updates/update-prompt;1"]. Components.classes["@mozilla.org/updates/update-prompt;1"].
@ -2371,6 +2414,9 @@ Downloader.prototype = {
prompter.showUpdateError(this._update); prompter.showUpdateError(this._update);
} }
} }
// the complete download succeeded or total failure was handled, so exit
return;
} }
// Do this after *everything* else, since it will likely cause the app // Do this after *everything* else, since it will likely cause the app
@ -2688,42 +2734,33 @@ var gModule = {
throw Components.results.NS_ERROR_NO_INTERFACE; throw Components.results.NS_ERROR_NO_INTERFACE;
}, },
_makeFactory: #1= function(ctor) {
function ci(outer, iid) {
if (outer != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
return (new ctor()).QueryInterface(iid);
}
return { createInstance: ci };
},
_objects: { _objects: {
service: { CID : Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}"), service: { CID : Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}"),
contractID : "@mozilla.org/updates/update-service;1", contractID : "@mozilla.org/updates/update-service;1",
className : "Update Service", className : "Update Service",
factory : #1#(UpdateService) factory : makeFactory(UpdateService)
}, },
checker: { CID : Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"), checker: { CID : Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"),
contractID : "@mozilla.org/updates/update-checker;1", contractID : "@mozilla.org/updates/update-checker;1",
className : "Update Checker", className : "Update Checker",
factory : #1#(Checker) factory : makeFactory(Checker)
}, },
#ifdef MOZ_XUL_APP #ifdef MOZ_XUL_APP
prompt: { CID : Components.ID("{27ABA825-35B5-4018-9FDD-F99250A0E722}"), prompt: { CID : Components.ID("{27ABA825-35B5-4018-9FDD-F99250A0E722}"),
contractID : "@mozilla.org/updates/update-prompt;1", contractID : "@mozilla.org/updates/update-prompt;1",
className : "Update Prompt", className : "Update Prompt",
factory : #1#(UpdatePrompt) factory : makeFactory(UpdatePrompt)
}, },
#endif #endif
timers: { CID : Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"), timers: { CID : Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"),
contractID : "@mozilla.org/updates/timer-manager;1", contractID : "@mozilla.org/updates/timer-manager;1",
className : "Timer Manager", className : "Timer Manager",
factory : #1#(TimerManager) factory : makeFactory(TimerManager)
}, },
manager: { CID : Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"), manager: { CID : Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
contractID : "@mozilla.org/updates/update-manager;1", contractID : "@mozilla.org/updates/update-manager;1",
className : "Update Manager", className : "Update Manager",
factory : #1#(UpdateManager) factory : makeFactory(UpdateManager)
}, },
}, },
@ -2732,6 +2769,19 @@ var gModule = {
} }
}; };
/**
* Creates a factory for instances of an object created using the passed-in
* constructor.
*/
function makeFactory(ctor) {
function ci(outer, iid) {
if (outer != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
return (new ctor()).QueryInterface(iid);
}
return { createInstance: ci };
}
function NSGetModule(compMgr, fileSpec) { function NSGetModule(compMgr, fileSpec) {
return gModule; return gModule;
} }