This commit is contained in:
Phil Ringnalda 2015-03-28 21:36:22 -07:00
Родитель ef58f7e085 8d01823d19
Коммит 9007f77036
4 изменённых файлов: 100 добавлений и 5 удалений

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

@ -70,7 +70,7 @@ let intervals = {
get initial() this._fixupIntervalPref("initial", 10), // 10 seconds.
// Every interval after the first.
get schedule() this._fixupIntervalPref("schedule", 2 * 60 * 60), // 2 hours
// After an error
// Initial retry after an error (exponentially backed-off to .schedule)
get retry() this._fixupIntervalPref("retry", 2 * 60), // 2 mins
};
@ -111,6 +111,9 @@ InternalScheduler.prototype = {
_timerRunning: false,
// Our sync engine - XXX - maybe just a callback?
_engine: Sync,
// Our current "error backoff" timeout. zero if no error backoff is in
// progress and incremented after successive errors until a max is reached.
_currentErrorBackoff: 0,
// Our state variable and constants.
state: null,
@ -292,6 +295,7 @@ InternalScheduler.prototype = {
this.state = this.STATE_OK;
this._logManager.resetFileLog(this._logManager.REASON_SUCCESS);
Services.obs.notifyObservers(null, "readinglist:sync:finish", null);
this._currentErrorBackoff = 0; // error retry interval is reset on success.
return intervals.schedule;
}).catch(err => {
// This isn't ideal - we really should have _canSync() check this - but
@ -300,6 +304,7 @@ InternalScheduler.prototype = {
if (err.message == fxAccountsCommon.ERROR_NO_ACCOUNT ||
err.message == fxAccountsCommon.ERROR_UNVERIFIED_ACCOUNT) {
// make everything look like success.
this._currentErrorBackoff = 0; // error retry interval is reset on success.
this.log.info("Can't sync due to FxA account state " + err.message);
this.state = this.STATE_OK;
this._logManager.resetFileLog(this._logManager.REASON_SUCCESS);
@ -314,7 +319,10 @@ InternalScheduler.prototype = {
{state: this.state, err});
this._logManager.resetFileLog(this._logManager.REASON_ERROR);
Services.obs.notifyObservers(null, "readinglist:sync:error", null);
return intervals.retry;
// We back-off on error retries until it hits our normally scheduled interval.
this._currentErrorBackoff = this._currentErrorBackoff == 0 ? intervals.retry :
Math.min(intervals.schedule, this._currentErrorBackoff * 2);
return this._currentErrorBackoff;
}).then(nextDelay => {
this._timerRunning = false;
// ensure a new timer is setup for the appropriate next time.

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

@ -174,6 +174,81 @@ add_task(function* testBackoff() {
scheduler.finalize();
});
add_task(function testErrorBackoff() {
// This test can't sanely use the "test scheduler" above, so make one more
// suited.
let rlMock = new ReadingListMock();
let scheduler = createTestableScheduler(rlMock);
scheduler._setTimeout = function(delay) {
// create a timer that fires immediately
return setTimeout(() => scheduler._doSync(), 0);
}
// This does all the work...
function checkBackoffs(expectedSequences) {
let orig_maybeReschedule = scheduler._maybeReschedule;
return new Promise(resolve => {
let isSuccess = true; // ie, first run will put us in "fail" mode.
let expected;
function nextSequence() {
if (expectedSequences.length == 0) {
resolve();
return true; // we are done.
}
// setup the current set of expected results.
expected = expectedSequences.shift()
// and toggle the success status of the engine.
isSuccess = !isSuccess;
if (isSuccess) {
scheduler._engine.start = Promise.resolve;
} else {
scheduler._engine.start = () => {
return Promise.reject(new Error("oh no"))
}
}
return false; // not done.
};
// get the first sequence;
nextSequence();
// and setup the scheduler to check the sequences.
scheduler._maybeReschedule = function(nextDelay) {
let thisExpected = expected.shift();
equal(thisExpected * 1000, nextDelay);
if (expected.length == 0) {
if (nextSequence()) {
// we are done, so do nothing.
return;
}
}
// call the original impl to get the next schedule.
return orig_maybeReschedule.call(scheduler, nextDelay);
}
});
}
prefs.set("schedule", 100);
prefs.set("retry", 5);
// The sequences of timeouts we expect as the Sync error state changes.
let backoffsChecked = checkBackoffs([
// first sequence is in failure mode - expect the timeout to double until 'schedule'
[5, 10, 20, 40, 80, 100, 100],
// Sync just started working - more 'schedule'
[100, 100],
// Just stopped again - error backoff process restarts.
[5, 10],
// Another success and we are back to 'schedule'
[100, 100],
]);
// fire things off.
scheduler.init();
// and wait for completion.
yield backoffsChecked;
scheduler.finalize();
});
function run_test() {
run_next_test();
}

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

@ -460,13 +460,18 @@ var LoginManagerContent = {
if (!usernameField)
log("(form -- no username field found)");
else
log("Username field id/name/value is: ", usernameField.id, " / ",
usernameField.name, " / ", usernameField.value);
// If we're not submitting a form (it's a page load), there are no
// password field values for us to use for identifying fields. So,
// just assume the first password field is the one to be filled in.
if (!isSubmission || pwFields.length == 1)
return [usernameField, pwFields[0].element, null];
if (!isSubmission || pwFields.length == 1) {
var passwordField = pwFields[0].element;
log("Password field id/name is: ", passwordField.id, " / ", passwordField.name);
return [usernameField, passwordField, null];
}
// Try to figure out WTF is in the form based on the password values.
@ -509,6 +514,8 @@ var LoginManagerContent = {
}
}
log("Password field (new) id/name is: ", newPasswordField.id, " / ", newPasswordField.name);
log("Password field (old) id/name is: ", oldPasswordField.id, " / ", oldPasswordField.name);
return [usernameField, newPasswordField, oldPasswordField];
},

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

@ -236,6 +236,11 @@ const DEFAULT_RECIPES = {
"description": "anthem uses a hidden password and username field to disable filling",
"hosts": ["www.anthem.com"],
"passwordSelector": "#LoginContent_txtLoginPass"
},
{
"description": "An ephemeral password-shim field is incorrectly selected as the username field.",
"hosts": ["www.discover.com"],
"usernameSelector": "#login-account"
}
]
};