Bug 908461 - Protocol deprecation indicators for Sync: client support. r=gps

This commit is contained in:
Richard Newman 2013-09-03 16:11:46 -07:00
parent 1c78cee3c2
commit 8d12973cd0
9 changed files with 312 additions and 15 deletions

View File

@ -2,8 +2,10 @@
# 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/.
// gSyncUI handles updating the tools menu
// gSyncUI handles updating the tools menu and displaying notifications.
let gSyncUI = {
DEFAULT_EOL_URL: "https://www.mozilla.org/firefox/?utm_source=synceol",
_obs: ["weave:service:sync:start",
"weave:service:sync:delayed",
"weave:service:quota:remaining",
@ -16,11 +18,14 @@ let gSyncUI = {
"weave:ui:sync:error",
"weave:ui:sync:finish",
"weave:ui:clear-error",
"weave:eol",
],
_unloaded: false,
init: function SUI_init() {
init: function () {
Cu.import("resource://services-common/stringbundle.js");
// Proceed to set up the UI if Sync has already started up.
// Otherwise we'll do it when Sync is firing up.
let xps = Components.classes["@mozilla.org/weave/service;1"]
@ -208,6 +213,41 @@ let gSyncUI = {
Weave.Notifications.replaceTitle(notification);
},
_getAppName: function () {
try {
let syncStrings = new StringBundle("chrome://browser/locale/sync.properties");
return syncStrings.getFormattedString("sync.defaultAccountApplication", [brandName]);
} catch (ex) {}
let brand = new StringBundle("chrome://branding/locale/brand.properties");
return brand.get("brandShortName");
},
onEOLNotice: function (data) {
let code = data.code;
let kind = (code == "hard-eol") ? "error" : "warning";
let url = data.url || gSyncUI.DEFAULT_EOL_URL;
let title = this._stringBundle.GetStringFromName(kind + ".sync.eol.label");
let description = this._stringBundle.formatStringFromName(kind + ".sync.eol.description",
[this._getAppName()],
1);
let buttons = [];
buttons.push(new Weave.NotificationButton(
this._stringBundle.GetStringFromName("sync.eol.learnMore.label"),
this._stringBundle.GetStringFromName("sync.eol.learnMore.accesskey"),
function() {
window.openUILinkIn(url, "tab");
return true;
}
));
let priority = (kind == "error") ? Weave.Notifications.PRIORITY_WARNING :
Weave.Notifications.PRIORITY_INFO;
let notification = new Weave.Notification(title, description, null, priority, buttons);
Weave.Notifications.replaceTitle(notification);
},
openServerStatus: function () {
let statusURL = Services.prefs.getCharPref("services.sync.statusURL");
window.openUILinkIn(statusURL, "tab");
@ -405,6 +445,13 @@ let gSyncUI = {
return;
}
// Unwrap, just like Svc.Obs, but without pulling in that dependency.
if (subject && typeof subject == "object" &&
("wrappedJSObject" in subject) &&
("observersModuleSubjectWrapper" in subject.wrappedJSObject)) {
subject = subject.wrappedJSObject.object;
}
switch (topic) {
case "weave:service:sync:start":
this.onActivityStart();
@ -448,6 +495,9 @@ let gSyncUI = {
case "weave:ui:clear-error":
this.clearError();
break;
case "weave:eol":
this.onEOLNotice(subject);
break;
}
},

View File

@ -40,3 +40,11 @@ error.sync.quota.label = Server Quota Exceeded
error.sync.quota.description = Sync failed because it exceeded the server quota. Please review which data to sync.
error.sync.viewQuotaButton.label = View Quota
error.sync.viewQuotaButton.accesskey = V
warning.sync.eol.label = Service Shutting Down
# %1: the app name (Firefox)
warning.sync.eol.description = Your Firefox Sync service is shutting down soon. Upgrade %1$S to keep syncing.
error.sync.eol.label = Service Unavailable
# %1: the app name (Firefox)
error.sync.eol.description = Your Firefox Sync service is no longer available. You need to upgrade %1$S to keep syncing.
sync.eol.learnMore.label = Learn more
sync.eol.learnMore.accesskey = L

View File

@ -40,6 +40,7 @@ SyncScheduler.prototype = {
this.idleInterval = Svc.Prefs.get("scheduler.idleInterval") * 1000;
this.activeInterval = Svc.Prefs.get("scheduler.activeInterval") * 1000;
this.immediateInterval = Svc.Prefs.get("scheduler.immediateInterval") * 1000;
this.eolInterval = Svc.Prefs.get("scheduler.eolInterval") * 1000;
// A user is non-idle on startup by default.
this.idle = false;
@ -238,11 +239,18 @@ SyncScheduler.prototype = {
},
adjustSyncInterval: function adjustSyncInterval() {
if (Status.eol) {
this._log.debug("Server status is EOL; using eolInterval.");
this.syncInterval = this.eolInterval;
return;
}
if (this.numClients <= 1) {
this._log.trace("Adjusting syncInterval to singleDeviceInterval.");
this.syncInterval = this.singleDeviceInterval;
return;
}
// Only MULTI_DEVICE clients will enter this if statement
// since SINGLE_USER clients will be handled above.
if (this.idle) {
@ -474,6 +482,7 @@ this.ErrorHandler = function ErrorHandler(service) {
this.init();
}
ErrorHandler.prototype = {
MINIMUM_ALERT_INTERVAL_MSEC: 604800000, // One week.
/**
* Flag that turns on error reporting for all errors, incl. network errors.
@ -767,12 +776,97 @@ ErrorHandler.prototype = {
[Status.login, Status.sync].indexOf(LOGIN_FAILED_NETWORK_ERROR) == -1);
},
get currentAlertMode() {
return Svc.Prefs.get("errorhandler.alert.mode");
},
set currentAlertMode(str) {
return Svc.Prefs.set("errorhandler.alert.mode", str);
},
get earliestNextAlert() {
return Svc.Prefs.get("errorhandler.alert.earliestNext", 0) * 1000;
},
set earliestNextAlert(msec) {
return Svc.Prefs.set("errorhandler.alert.earliestNext", msec / 1000);
},
clearServerAlerts: function () {
// If we have any outstanding alerts, apparently they're no longer relevant.
Svc.Prefs.resetBranch("errorhandler.alert");
},
/**
* X-Weave-Alert headers can include a JSON object:
*
* {
* "code": // One of "hard-eol", "soft-eol".
* "url": // For "Learn more" link.
* "message": // Logged in Sync logs.
* }
*/
handleServerAlert: function (xwa) {
if (!xwa.code) {
this._log.warn("Got structured X-Weave-Alert, but no alert code.");
return;
}
switch (xwa.code) {
// Gently and occasionally notify the user that this service will be
// shutting down.
case "soft-eol":
// Fall through.
// Tell the user that this service has shut down, and drop our syncing
// frequency dramatically.
case "hard-eol":
// Note that both of these alerts should be subservient to future "sign
// in with your Firefox Account" storage alerts.
if ((this.currentAlertMode != xwa.code) ||
(this.earliestNextAlert < Date.now())) {
Utils.nextTick(function() {
Svc.Obs.notify("weave:eol", xwa);
}, this);
this._log.error("X-Weave-Alert: " + xwa.code + ": " + xwa.message);
this.earliestNextAlert = Date.now() + this.MINIMUM_ALERT_INTERVAL_MSEC;
this.currentAlertMode = xwa.code;
}
break;
default:
this._log.debug("Got unexpected X-Weave-Alert code: " + xwa.code);
}
},
/**
* Handle HTTP response results or exceptions and set the appropriate
* Status.* bits.
*
* This method also looks for "side-channel" warnings.
*/
checkServerError: function checkServerError(resp) {
checkServerError: function (resp) {
switch (resp.status) {
case 200:
case 404:
case 513:
let xwa = resp.headers['x-weave-alert'];
// Only process machine-readable alerts.
if (!xwa || !xwa.startsWith("{")) {
this.clearServerAlerts();
return;
}
try {
xwa = JSON.parse(xwa);
} catch (ex) {
this._log.warn("Malformed X-Weave-Alert from server: " + xwa);
return;
}
this.handleServerAlert(xwa);
break;
case 400:
if (resp == RESPONSE_OVER_QUOTA) {
Status.sync = OVER_QUOTA;

View File

@ -506,9 +506,10 @@ Sync11Service.prototype = {
},
/**
* Perform the info fetch as part of a login or key fetch.
* Perform the info fetch as part of a login or key fetch, or
* inside engine sync.
*/
_fetchInfo: function _fetchInfo(url) {
_fetchInfo: function (url) {
let infoURL = url || this.infoURL;
this._log.trace("In _fetchInfo: " + infoURL);
@ -519,9 +520,11 @@ Sync11Service.prototype = {
this.errorHandler.checkServerError(ex);
throw ex;
}
// Always check for errors; this is also where we look for X-Weave-Alert.
this.errorHandler.checkServerError(info);
if (!info.success) {
this.errorHandler.checkServerError(info);
throw "aborting sync, failed to get collections";
throw "Aborting sync: failed to get collections.";
}
return info;
},

View File

@ -57,6 +57,15 @@ this.Status = {
this.service = code == SYNC_SUCCEEDED ? STATUS_OK : SYNC_FAILED;
},
get eol() {
let modePref = PREFS_BRANCH + "errorhandler.alert.mode";
try {
return Services.prefs.getCharPref(modePref) == "hard-eol";
} catch (ex) {
return false;
}
},
get engines() {
return this._engines;
},

View File

@ -13,6 +13,7 @@ pref("services.sync.syncKeyHelpURL", "https://services.mozilla.com/help/synckey"
pref("services.sync.lastversion", "firstrun");
pref("services.sync.sendVersionInfo", true);
pref("services.sync.scheduler.eolInterval", 604800); // 1 week
pref("services.sync.scheduler.singleDeviceInterval", 86400); // 1 day
pref("services.sync.scheduler.idleInterval", 3600); // 1 hour
pref("services.sync.scheduler.activeInterval", 600); // 10 minutes

View File

@ -852,7 +852,7 @@ SyncServer.prototype = {
// TODO: verify if this is spec-compliant.
if (req.method != "DELETE") {
respond(405, "Method Not Allowed", "[]", {"Allow": "DELETE"});
return;
return undefined;
}
// Delete all collections and track the timestamp for the response.
@ -860,7 +860,7 @@ SyncServer.prototype = {
// Return timestamp and OK for deletion.
respond(200, "OK", JSON.stringify(timestamp));
return;
return undefined;
}
let match = this.storageRE.exec(rest);
@ -875,11 +875,11 @@ SyncServer.prototype = {
if (!coll) {
if (wboID) {
respond(404, "Not found", "Not found");
return;
return undefined;
}
// *cries inside*: Bug 687299.
respond(200, "OK", "[]");
return;
return undefined;
}
if (!wboID) {
return coll.collectionHandler(req, resp);
@ -887,7 +887,7 @@ SyncServer.prototype = {
let wbo = coll.wbo(wboID);
if (!wbo) {
respond(404, "Not found", "Not found");
return;
return undefined;
}
return wbo.handler()(req, resp);
@ -895,7 +895,7 @@ SyncServer.prototype = {
case "DELETE":
if (!coll) {
respond(200, "OK", "{}");
return;
return undefined;
}
if (wboID) {
let wbo = coll.wbo(wboID);
@ -904,7 +904,7 @@ SyncServer.prototype = {
this.callback.onItemDeleted(username, collection, wboID);
}
respond(200, "OK", "{}");
return;
return undefined;
}
coll.collectionHandler(req, resp);
@ -935,7 +935,7 @@ SyncServer.prototype = {
for (let i = 0; i < deleted.length; ++i) {
this.callback.onItemDeleted(username, collection, deleted[i]);
}
return;
return undefined;
case "POST":
case "PUT":
if (!coll) {

View File

@ -0,0 +1,131 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/status.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/fakeservices.js");
Cu.import("resource://testing-common/services/sync/utils.js");
function baseHandler(eolCode, request, response, statusCode, status, body) {
let alertBody = {
code: eolCode,
message: "Service is EOLed.",
url: "http://getfirefox.com",
};
response.setHeader("X-Weave-Timestamp", "" + new_timestamp(), false);
response.setHeader("X-Weave-Alert", "" + JSON.stringify(alertBody), false);
response.setStatusLine(request.httpVersion, statusCode, status);
response.bodyOutputStream.write(body, body.length);
}
function handler513(request, response) {
let statusCode = 513;
let status = "Upgrade Required";
let body = "{}";
baseHandler("hard-eol", request, response, statusCode, status, body);
}
function handler200(eolCode) {
return function (request, response) {
let statusCode = 200;
let status = "OK";
let body = "{\"meta\": 123456789010}";
baseHandler(eolCode, request, response, statusCode, status, body);
};
}
function sync_httpd_setup(infoHandler) {
let handlers = {
"/1.1/johndoe/info/collections": infoHandler,
};
return httpd_setup(handlers);
}
function setUp(server) {
setBasicCredentials("johndoe", "ilovejane", "aabcdeabcdeabcdeabcdeabcde");
Service.serverURL = server.baseURI + "/";
Service.clusterURL = server.baseURI + "/";
new FakeCryptoService();
}
function run_test() {
run_next_test();
}
function do_check_soft_eol(eh, start) {
// We subtract 1000 because the stored value is in second precision.
do_check_true(eh.earliestNextAlert >= (start + eh.MINIMUM_ALERT_INTERVAL_MSEC - 1000));
do_check_eq("soft-eol", eh.currentAlertMode);
}
function do_check_hard_eol(eh, start) {
// We subtract 1000 because the stored value is in second precision.
do_check_true(eh.earliestNextAlert >= (start + eh.MINIMUM_ALERT_INTERVAL_MSEC - 1000));
do_check_eq("hard-eol", eh.currentAlertMode);
do_check_true(Status.eol);
}
add_test(function test_200_hard() {
let eh = Service.errorHandler;
let start = Date.now();
let server = sync_httpd_setup(handler200("hard-eol"));
setUp(server);
let obs = function (subject, topic, data) {
Svc.Obs.remove("weave:eol", obs);
do_check_eq("hard-eol", subject.code);
do_check_hard_eol(eh, start);
do_check_eq(Service.scheduler.eolInterval, Service.scheduler.syncInterval);
eh.clearServerAlerts();
server.stop(run_next_test);
};
Svc.Obs.add("weave:eol", obs);
Service._fetchInfo();
Service.scheduler.adjustSyncInterval(); // As if we failed or succeeded in syncing.
});
add_test(function test_513_hard() {
let eh = Service.errorHandler;
let start = Date.now();
let server = sync_httpd_setup(handler513);
setUp(server);
let obs = function (subject, topic, data) {
Svc.Obs.remove("weave:eol", obs);
do_check_eq("hard-eol", subject.code);
do_check_hard_eol(eh, start);
do_check_eq(Service.scheduler.eolInterval, Service.scheduler.syncInterval);
eh.clearServerAlerts();
server.stop(run_next_test);
};
Svc.Obs.add("weave:eol", obs);
try {
Service._fetchInfo();
Service.scheduler.adjustSyncInterval(); // As if we failed or succeeded in syncing.
} catch (ex) {
// Because fetchInfo will fail on a 513.
}
});
add_test(function test_200_soft() {
let eh = Service.errorHandler;
let start = Date.now();
let server = sync_httpd_setup(handler200("soft-eol"));
setUp(server);
let obs = function (subject, topic, data) {
Svc.Obs.remove("weave:eol", obs);
do_check_eq("soft-eol", subject.code);
do_check_soft_eol(eh, start);
do_check_eq(Service.scheduler.singleDeviceInterval, Service.scheduler.syncInterval);
eh.clearServerAlerts();
server.stop(run_next_test);
};
Svc.Obs.add("weave:eol", obs);
Service._fetchInfo();
Service.scheduler.adjustSyncInterval(); // As if we failed or succeeded in syncing.
});

View File

@ -100,6 +100,7 @@ skip-if = os == "android"
[test_errorhandler_sync_checkServerError.js]
# Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini)
skip-if = os == "android"
[test_errorhandler_eol.js]
[test_hmac_error.js]
[test_interval_triggers.js]
[test_node_reassignment.js]