mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
Bug 908461 - Protocol deprecation indicators for Sync: client support. r=gps
This commit is contained in:
parent
57f3b2d4bb
commit
be2550d749
@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
},
|
||||
|
@ -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;
|
||||
},
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
131
services/sync/tests/unit/test_errorhandler_eol.js
Normal file
131
services/sync/tests/unit/test_errorhandler_eol.js
Normal 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.
|
||||
});
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user