Bug 794211: Check for updates after coming online when a check fails because the network is offline. r=bbondy r=rstrong

This commit is contained in:
Marshall Culpepper 2012-10-13 13:06:58 -05:00
parent 8429f9c41d
commit 64fcf6b336
6 changed files with 245 additions and 55 deletions

View File

@ -468,10 +468,6 @@ pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
// Interval at which update manifest is fetched. In units of seconds.
pref("app.update.interval", 86400); // 1 day
// First interval to elapse before checking for update. In units of
// milliseconds. Capped at 10 seconds.
pref("app.update.timerFirstInterval", 3600000); // 1 hour
pref("app.update.timerMinimumDelay", 3600); // 1 hour in seconds
// Don't throttle background updates.
pref("app.update.download.backgroundInterval", 0);

View File

@ -151,6 +151,7 @@ const WRITE_ERROR_SHARING_VIOLATION_NOPID = 48;
const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100;
const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
const NETWORK_ERROR_OFFLINE = 111;
const DOWNLOAD_CHUNK_SIZE = 300000; // bytes
const DOWNLOAD_BACKGROUND_INTERVAL = 600; // seconds
@ -1496,6 +1497,11 @@ UpdateService.prototype = {
*/
_incompatAddonsCount: 0,
/**
* Whether or not the service registered the "online" observer.
*/
_registeredOnlineObserver: false,
/**
* Handle Observer Service notifications
* @param subject
@ -1511,6 +1517,9 @@ UpdateService.prototype = {
// Clean up any extant updates
this._postUpdateProcessing();
break;
case "network:offline-status-changed":
this._offlineStatusChanged(data);
break;
case "xpcom-shutdown":
Services.obs.removeObserver(this, "xpcom-shutdown");
@ -1789,6 +1798,83 @@ UpdateService.prototype = {
}
},
/**
* Register an observer when the network comes online, so we can short-circuit
* the app.update.interval when there isn't connectivity
*/
_registerOnlineObserver: function AUS__registerOnlineObserver() {
if (this._registeredOnlineObserver) {
LOG("UpdateService:_registerOnlineObserver - observer already registered");
return;
}
LOG("UpdateService:_registerOnlineObserver - waiting for the network to " +
"be online, then forcing another check");
Services.obs.addObserver(this, "network:offline-status-changed", false);
this._registeredOnlineObserver = true;
},
/**
* Called from the network:offline-status-changed observer.
*/
_offlineStatusChanged: function AUS__offlineStatusChanged(status) {
if (status !== "online") {
return;
}
Services.obs.removeObserver(this, "network:offline-status-changed");
this._registeredOnlineObserver = false;
LOG("UpdateService:_offlineStatusChanged - network is online, forcing " +
"another background check");
// the background checker is contained in notify
this.notify(null);
},
// nsIUpdateCheckListener
onProgress: function AUS_onProgress(request, position, totalSize) {
},
onCheckComplete: function AUS_onCheckComplete(request, updates, updateCount) {
this._selectAndInstallUpdate(updates);
},
onError: function AUS_onError(request, update) {
LOG("UpdateService:onError - error during background update: " +
update.statusText);
var maxErrors;
var errCount;
if (update.errorCode == NETWORK_ERROR_OFFLINE) {
// Register an online observer to try again
this._registerOnlineObserver();
return;
}
else if (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE) {
errCount = getPref("getIntPref", PREF_APP_UPDATE_CERT_ERRORS, 0);
errCount++;
Services.prefs.setIntPref(PREF_APP_UPDATE_CERT_ERRORS, errCount);
maxErrors = getPref("getIntPref", PREF_APP_UPDATE_CERT_MAXERRORS, 5);
}
else {
update.errorCode = BACKGROUNDCHECK_MULTIPLE_FAILURES;
errCount = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDERRORS, 0);
errCount++;
Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, errCount);
maxErrors = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS,
10);
}
if (errCount >= maxErrors) {
var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
prompter.showUpdateError(update);
}
},
/**
* Notified when a timer fires
* @param timer
@ -1799,55 +1885,7 @@ UpdateService.prototype = {
if (this.isDownloading || this._downloader && this._downloader.patchIsStaged)
return;
var self = this;
var listener = {
/**
* See nsIUpdateService.idl
*/
onProgress: function AUS_notify_onProgress(request, position, totalSize) {
},
/**
* See nsIUpdateService.idl
*/
onCheckComplete: function AUS_notify_onCheckComplete(request, updates,
updateCount) {
self._selectAndInstallUpdate(updates);
},
/**
* See nsIUpdateService.idl
*/
onError: function AUS_notify_onError(request, update) {
LOG("UpdateService:notify:listener - error during background update: " +
update.statusText);
var maxErrors;
var errCount;
if (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE) {
errCount = getPref("getIntPref", PREF_APP_UPDATE_CERT_ERRORS, 0);
errCount++;
Services.prefs.setIntPref(PREF_APP_UPDATE_CERT_ERRORS, errCount);
maxErrors = getPref("getIntPref", PREF_APP_UPDATE_CERT_MAXERRORS, 5);
}
else {
update.errorCode = BACKGROUNDCHECK_MULTIPLE_FAILURES;
errCount = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDERRORS, 0);
errCount++;
Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, errCount);
maxErrors = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS,
10);
}
if (errCount >= maxErrors) {
var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
prompter.showUpdateError(update);
}
}
};
this.backgroundChecker.checkForUpdates(listener, false);
this.backgroundChecker.checkForUpdates(this, false);
},
/**
@ -2307,6 +2345,7 @@ UpdateService.prototype = {
_xpcom_factory: UpdateServiceFactory,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIApplicationUpdateService,
Ci.nsIUpdateCheckListener,
Ci.nsIAddonUpdateCheckListener,
Ci.nsITimerCallback,
Ci.nsIObserver])
@ -2843,6 +2882,11 @@ Checker.prototype = {
// "looks" fine but there was probably an XML error or a bogus file.
var update = new Update(null);
update.statusText = getStatusTextFromCode(status, 200);
if (status == Cr.NS_ERROR_OFFLINE) {
// We use a separate constant here because nsIUpdate.errorCode is signed
update.errorCode = NETWORK_ERROR_OFFLINE;
}
this._callback.onError(request, update);
this._request = null;

View File

@ -91,7 +91,8 @@ XPCOMUtils.defineLazyGetter(this, "gAUS", function test_gAUS() {
return AUS_Cc["@mozilla.org/updates/update-service;1"].
getService(AUS_Ci.nsIApplicationUpdateService).
QueryInterface(AUS_Ci.nsITimerCallback).
QueryInterface(AUS_Ci.nsIObserver);
QueryInterface(AUS_Ci.nsIObserver).
QueryInterface(AUS_Ci.nsIUpdateCheckListener);
});
XPCOMUtils.defineLazyServiceGetter(this, "gUpdateManager",

View File

@ -114,6 +114,9 @@ var gTestserver;
var gXHR;
var gXHRCallback;
var gUpdatePrompt;
var gUpdatePromptCallback;
var gCheckFunc;
var gResponseBody;
var gResponseStatusCode = 200;
@ -1502,6 +1505,7 @@ function overrideXHR(callback) {
gXHR.contractID, gXHR);
}
/**
* Bare bones XMLHttpRequest implementation for testing onprogress, onerror,
* and onload nsIDomEventListener handleEvent.
@ -1566,6 +1570,63 @@ xhr.prototype = {
get wrappedJSObject() { return this; }
};
function overrideUpdatePrompt(callback) {
var registrar = Components.manager.QueryInterface(AUS_Ci.nsIComponentRegistrar);
gUpdatePrompt = new UpdatePrompt();
gUpdatePromptCallback = callback;
registrar.registerFactory(gUpdatePrompt.classID, gUpdatePrompt.classDescription,
gUpdatePrompt.contractID, gUpdatePrompt);
}
function UpdatePrompt() {
var fns = ["checkForUpdates", "showUpdateAvailable", "showUpdateDownloaded",
"showUpdateError", "showUpdateHistory", "showUpdateInstalled"];
fns.forEach(function(promptFn) {
UpdatePrompt.prototype[promptFn] = function() {
if (!gUpdatePromptCallback) {
return;
}
var callback = gUpdatePromptCallback[promptFn];
if (!callback) {
return;
}
callback.apply(gUpdatePromptCallback,
Array.prototype.slice.call(arguments));
}
});
}
UpdatePrompt.prototype = {
flags: AUS_Ci.nsIClassInfo.SINGLETON,
implementationLanguage: AUS_Ci.nsIProgrammingLanguage.JAVASCRIPT,
getHelperForLanguage: function(language) null,
getInterfaces: function(count) {
var interfaces = [AUS_Ci.nsISupports, AUS_Ci.nsIUpdatePrompt];
count.value = interfaces.length;
return interfaces;
},
classDescription: "UpdatePrompt",
contractID: "@mozilla.org/updates/update-prompt;1",
classID: Components.ID("{8c350a15-9b90-4622-93a1-4d320308664b}"),
createInstance: function (outer, aIID) {
if (outer == null)
return gUpdatePrompt.QueryInterface(aIID);
throw AUS_Cr.NS_ERROR_NO_AGGREGATION;
},
QueryInterface: function(aIID) {
if (aIID.equals(AUS_Ci.nsIClassInfo) ||
aIID.equals(AUS_Ci.nsISupports) ||
aIID.equals(AUS_Ci.nsIUpdatePrompt))
return gUpdatePrompt;
throw AUS_Cr.NS_ERROR_NO_INTERFACE;
},
};
/* Update check listener */
const updateCheckListener = {
onProgress: function UCL_onProgress(request, position, totalSize) {
@ -1586,12 +1647,13 @@ const updateCheckListener = {
onError: function UCL_onError(request, update) {
gRequestURL = request.channel.originalURI.spec;
gStatusCode = request.status;
gStatusText = update.statusText;
logTestInfo("url = " + gRequestURL + ", " +
"request.status = " + gStatusCode + ", " +
"update.statusText = " + gStatusText);
// Use a timeout to allow the XHR to complete
do_execute_soon(gCheckFunc);
do_execute_soon(gCheckFunc.bind(null, request, update));
},
QueryInterface: function(aIID) {

View File

@ -0,0 +1,86 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Offline retry test (Bug 794211) */
// Needs to be in sync w/ nsUpdateService.js
const NETWORK_ERROR_OFFLINE = 111;
function run_test() {
do_test_pending();
do_register_cleanup(end_test);
DEBUG_AUS_TEST = true;
logTestInfo("test when an update check fails because the network is " +
"offline that we check again when the network comes online. " +
"(Bug 794211)");
removeUpdateDirsAndFiles();
setUpdateURLOverride();
Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO, false);
overrideXHR(null);
overrideUpdatePrompt(updatePrompt);
standardInit();
do_execute_soon(run_test_pt1);
}
function run_test_pt1() {
gResponseBody = null;
gCheckFunc = check_test_pt1;
gXHRCallback = xhr_pt1;
gUpdateChecker.checkForUpdates(updateCheckListener, true);
}
function xhr_pt1() {
gXHR.status = AUS_Cr.NS_ERROR_OFFLINE;
gXHR.onerror({ target: gXHR });
}
function check_test_pt1(request, update) {
do_check_eq(gStatusCode, AUS_Cr.NS_ERROR_OFFLINE);
do_check_eq(update.errorCode, NETWORK_ERROR_OFFLINE);
// Forward the error to AUS, which should register the online observer
gAUS.onError(request, update);
// Trigger another check by notifying the offline status observer
gXHRCallback = xhr_pt2;
Services.obs.notifyObservers(gAUS, "network:offline-status-changed", "online");
}
var updatePrompt = {
showUpdateAvailable: function(update) {
check_test_pt2(update);
}
};
function xhr_pt2() {
var patches = getLocalPatchString();
var updates = getLocalUpdateString(patches);
var responseBody = getLocalUpdatesXMLString(updates);
gXHR.status = 200;
gXHR.responseText = responseBody;
try {
var parser = AUS_Cc["@mozilla.org/xmlextras/domparser;1"].
createInstance(AUS_Ci.nsIDOMParser);
gXHR.responseXML = parser.parseFromString(responseBody, "application/xml");
}
catch(e) { }
gXHR.onload({ target: gXHR });
}
function check_test_pt2(update) {
// We just verify that there are updates to know the check succeeded.
do_check_neq(update, null);
do_check_eq(update.name, "App Update Test");
do_test_finished();
}
function end_test() {
cleanUp();
}

View File

@ -28,3 +28,4 @@ run-if = os == 'win'
run-if = os == 'linux' || os == 'mac' || os == 'sunos'
[test_bug497578.js]
[test_bug595059.js]
[test_bug794211.js]