Merge places with mozilla-central. a=blockers

This commit is contained in:
Philipp von Weitershausen 2011-02-06 09:40:25 -08:00
commit 924b21db42
22 changed files with 552 additions and 89 deletions

View File

@ -214,7 +214,6 @@ let gSyncUI = {
Weave.Notifications.removeAll(title);
this.updateUI();
this._updateLastSyncTime();
},
onLoginError: function SUI_onLoginError() {
@ -222,7 +221,7 @@ let gSyncUI = {
Weave.Notifications.removeAll();
// if we haven't set up the client, don't show errors
if (this._needsSetup()) {
if (this._needsSetup() || Weave.Service.shouldIgnoreError()) {
this.updateUI();
return;
}
@ -355,6 +354,14 @@ let gSyncUI = {
this.onLoginError();
return;
}
// Ignore network related errors unless we haven't been able to
// sync for a while.
if (Weave.Service.shouldIgnoreError()) {
this.updateUI();
return;
}
let error = Weave.Utils.getErrorString(Weave.Status.sync);
let description =
this._stringBundle.formatStringFromName("error.sync.description", [error], 1);
@ -420,7 +427,6 @@ let gSyncUI = {
}
this.updateUI();
this._updateLastSyncTime();
},
observe: function SUI_observe(subject, topic, data) {

View File

@ -73,6 +73,7 @@ let Change = {
// load some other elements & info from the window
this._dialog = document.getElementById("change-dialog");
this._dialogType = window.arguments[0];
this._duringSetup = window.arguments[1];
this._status = document.getElementById("status");
this._statusIcon = document.getElementById("statusIcon");
this._statusRow = document.getElementById("statusRow");
@ -115,6 +116,9 @@ let Change = {
warningText.textContent = this._str("change.synckey2.warningText");
this._dialog.getButton("finish").label
= this._str("change.synckey.acceptButton");
if (this._duringSetup) {
this._dialog.getButton("finish").disabled = false;
}
}
break;
case "ChangePassword":

View File

@ -23,6 +23,7 @@
* Mike Connor <mconnor@mozilla.com>
* Philipp von Weitershausen <philipp@weitershausen.de>
* Paul OShannessy <paul@oshannessy.com>
* Richard Newman <rnewman@mozilla.com>
*
* 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
@ -144,9 +145,42 @@ var gSyncSetup = {
this.wizard.pageIndex = EXISTING_ACCOUNT_CONNECT_PAGE;
},
resetPassphrase: function resetPassphrase() {
// Apply the existing form fields so that
// Weave.Service.changePassphrase() has the necessary credentials.
Weave.Service.account = document.getElementById("existingAccountName").value;
Weave.Service.password = document.getElementById("existingPassword").value;
// Generate a new passphrase so that Weave.Service.login() will
// actually do something.
let passphrase = Weave.Utils.generatePassphrase();
Weave.Service.passphrase = passphrase;
// Only open the dialog if username + password are actually correct.
Weave.Service.login();
if ([Weave.LOGIN_FAILED_INVALID_PASSPHRASE,
Weave.LOGIN_FAILED_NO_PASSPHRASE,
Weave.LOGIN_SUCCEEDED].indexOf(Weave.Status.login) == -1) {
return;
}
// Hide any errors about the passphrase, we know it's not right.
let feedback = document.getElementById("existingPassphraseFeedbackRow");
feedback.hidden = true;
let el = document.getElementById("existingPassphrase");
el.value = Weave.Utils.hyphenatePassphrase(passphrase);
// changePassphrase() will sync, make sure we set the "firstSync" pref
// according to the user's pref.
Weave.Svc.Prefs.reset("firstSync");
this.setupInitialSync();
gSyncUtils.resetPassphrase(true);
},
onResetPassphrase: function () {
document.getElementById("existingPassphrase").value =
Weave.Utils.hyphenatePassphrase(Weave.Service.passphrase);
this.checkFields();
this.wizard.advance();
},
@ -248,7 +282,8 @@ var gSyncSetup = {
checkAccount: function() {
delete this._checkAccountTimer;
let value = document.getElementById("weaveEmail").value;
let value = Weave.Utils.normalizeAccount(
document.getElementById("weaveEmail").value);
if (!value) {
this.status.email = false;
this.checkFields();
@ -415,7 +450,8 @@ var gSyncSetup = {
feedback.hidden = false;
let password = document.getElementById("weavePassword").value;
let email = document.getElementById("weaveEmail").value;
let email = Weave.Utils.normalizeAccount(
document.getElementById("weaveEmail").value);
let challenge = getField("challenge");
let response = getField("response");
@ -440,7 +476,8 @@ var gSyncSetup = {
this.captchaBrowser.loadURI(Weave.Service.miscAPI + "captcha_html");
break;
case EXISTING_ACCOUNT_LOGIN_PAGE:
Weave.Service.account = document.getElementById("existingAccountName").value;
Weave.Service.account = Weave.Utils.normalizeAccount(
document.getElementById("existingAccountName").value);
Weave.Service.password = document.getElementById("existingPassword").value;
let pp = document.getElementById("existingPassphrase").value;
Weave.Service.passphrase = Weave.Utils.normalizePassphrase(pp);
@ -901,7 +938,6 @@ var gSyncSetup = {
this._setFeedback(element, success, str);
},
onStateChange: function(webProgress, request, stateFlags, status) {
// We're only looking for the end of the frame load
if ((stateFlags & Ci.nsIWebProgressListener.STATE_STOP) == 0)

View File

@ -375,7 +375,7 @@
</label>
<spacer id="passphraseHelpSpacer"/>
<label class="text-link"
onclick="gSyncUtils.resetPassphrase(); return false;">
onclick="gSyncSetup.resetPassphrase(); return false;">
&resetSyncKey.label;
</label>
</description>

View File

@ -67,7 +67,7 @@ let gSyncUtils = {
input.value = Weave.Clients.localName;
},
openChange: function openChange(type) {
openChange: function openChange(type, duringSetup) {
// Just re-show the dialog if it's already open
let openedDialog = Weave.Svc.WinMediator.getMostRecentWindow("Sync:" + type);
if (openedDialog != null) {
@ -78,7 +78,8 @@ let gSyncUtils = {
// Open up the change dialog
let changeXUL = "chrome://browser/content/syncGenericChange.xul";
let changeOpt = "centerscreen,chrome,resizable=no";
Weave.Svc.WinWatcher.activeWindow.openDialog(changeXUL, "", changeOpt, type);
Weave.Svc.WinWatcher.activeWindow.openDialog(changeXUL, "", changeOpt,
type, duringSetup);
},
changePassword: function () {
@ -86,9 +87,9 @@ let gSyncUtils = {
this.openChange("ChangePassword");
},
resetPassphrase: function () {
resetPassphrase: function (duringSetup) {
if (Weave.Utils.ensureMPUnlocked())
this.openChange("ResetPassphrase");
this.openChange("ResetPassphrase", duringSetup);
},
updatePassphrase: function () {

View File

@ -42,6 +42,16 @@ VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
# Definitions used by constants.js.
WEAVE_VERSION=$(shell cat $(topsrcdir)/services/sync/version.txt)
WEAVE_CHANNEL=rel
WEAVE_ID={340c2bbc-ce74-4362-90b5-7c26312808ef}
SYNC_PP_DEFINES = \
-Dweave_version=$(WEAVE_VERSION) \
-Dweave_channel=$(WEAVE_CHANNEL) \
-Dweave_id=$(WEAVE_ID)
DIRS = locales
EXTRA_COMPONENTS = \
@ -51,8 +61,30 @@ EXTRA_COMPONENTS = \
PREF_JS_EXPORTS = $(srcdir)/services-sync.js
# Preprocess constants (by preprocessing everything).
# The 'HERE' idiom avoids a dependency on pushd. We need to do this fiddling in
# order to get relative paths, so we can process services/sync/modules/* into
# modules/services-sync/*.
#
# Note that we find candidates, make directories, then 'copy' files.
libs::
$(PYTHON) $(topsrcdir)/config/nsinstall.py $(srcdir)/modules/* $(FINAL_TARGET)/modules/services-sync
ifndef NO_DIST_INSTALL
$(EXIT_ON_ERROR) \
HERE=$(CURDIR); \
cd $(srcdir)/modules; \
dirs=$$(find * -type d); \
files=$$(find * -type f); \
cd $$HERE; \
for d in $$dirs; do \
$(PYTHON) $(topsrcdir)/config/nsinstall.py -D $(FINAL_TARGET)/modules/services-sync/$$d; \
done; \
for i in $$files; do \
src=$(srcdir)/modules/$$i; \
dest=$(FINAL_TARGET)/modules/services-sync/$$i; \
$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(SYNC_PP_DEFINES) $$src > $$dest ; \
done
endif
ifdef ENABLE_TESTS
DIRS += tests

View File

@ -1,3 +1,4 @@
#filter substitution
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
@ -38,7 +39,7 @@
// Process each item in the "constants hash" to add to "global" and give a name
let EXPORTED_SYMBOLS = [((this[key] = val), key) for ([key, val] in Iterator({
WEAVE_CHANNEL: "@xpi_type@",
WEAVE_CHANNEL: "@weave_channel@",
WEAVE_VERSION: "@weave_version@",
WEAVE_ID: "@weave_id@",
@ -47,7 +48,7 @@ WEAVE_ID: "@weave_id@",
// the per-engine cleartext formats.
STORAGE_VERSION: 5,
UPDATED_DEV_URL: "https://services.mozilla.com/sync/updated/?version=@weave_version@&channel=@xpi_type@",
UPDATED_DEV_URL: "https://services.mozilla.com/sync/updated/?version=@weave_version@&channel=@weave_channel@",
UPDATED_REL_URL: "http://www.mozilla.com/firefox/sync/updated.html",
PREFS_BRANCH: "services.sync.",
@ -76,6 +77,9 @@ MULTI_DESKTOP_SYNC: 60 * 60 * 1000, // 1 hour
MULTI_MOBILE_SYNC: 5 * 60 * 1000, // 5 minutes
PARTIAL_DATA_SYNC: 60 * 1000, // 1 minute
MAX_ERROR_COUNT_BEFORE_BACKOFF: 3,
MAX_IGNORE_ERROR_COUNT: 5,
// HMAC event handling timeout.
// 10 minutes: a compromise between the multi-desktop sync interval
// and the mobile sync interval.
@ -91,6 +95,7 @@ MOBILE_BATCH_SIZE: 50,
// Default batch size for applying incoming records.
DEFAULT_STORE_BATCH_SIZE: 1,
HISTORY_STORE_BATCH_SIZE: 50, // same as MOBILE_BATCH_SIZE
FORMS_STORE_BATCH_SIZE: 50, // same as MOBILE_BATCH_SIZE
// score thresholds for early syncs
SINGLE_USER_THRESHOLD: 1000,

View File

@ -23,6 +23,7 @@
* Jono DiCarlo <jdicarlo@mozilla.org>
* Anant Narayanan <anant@kix.in>
* Philipp von Weitershausen <philipp@weitershausen.de>
* Richard Newman <rnewman@mozilla.com>
*
* 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

View File

@ -44,6 +44,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/log4moz.js");
const FORMS_TTL = 5184000; // 60 days
@ -159,6 +160,8 @@ FormEngine.prototype = {
_storeObj: FormStore,
_trackerObj: FormTracker,
_recordObj: FormRec,
applyIncomingBatchSize: FORMS_STORE_BATCH_SIZE,
get prefName() "history",
_findDupe: function _findDupe(item) {
@ -173,6 +176,12 @@ function FormStore(name) {
FormStore.prototype = {
__proto__: Store.prototype,
applyIncomingBatch: function applyIncomingBatch(records) {
return Utils.runInTransaction(Svc.Form.DBConnection, function() {
return Store.prototype.applyIncomingBatch.call(this, records);
}, this);
},
getAllIDs: function FormStore_getAllIDs() {
let guids = {};
for each (let {name, value} in FormWrapper.getAllEntries())

View File

@ -21,6 +21,7 @@
* Justin Dolske <dolske@mozilla.com>
* Anant Narayanan <anant@kix.in>
* Philipp von Weitershausen <philipp@weitershausen.de>
* Richard Newman <rnewman@mozilla.com>
*
* 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
@ -91,6 +92,9 @@ PasswordEngine.prototype = {
_findDupe: function _findDupe(item) {
let login = this._store._nsLoginInfoFromRecord(item);
if (!login)
return;
let logins = Svc.Login.findLogins({}, login.hostname, login.formSubmitURL,
login.httpRealm);
@ -110,9 +114,20 @@ PasswordStore.prototype = {
__proto__: Store.prototype,
_nsLoginInfoFromRecord: function PasswordStore__nsLoginInfoRec(record) {
if (record.formSubmitURL &&
record.httpRealm) {
this._log.warn("Record " + record.id +
" has both formSubmitURL and httpRealm. Skipping.");
return null;
}
// Passing in "undefined" results in an empty string, which later
// counts as a value. Explicitly `|| null` these fields according to JS
// truthiness. Records with empty strings or null will be unmolested.
function nullUndefined(x) (x == undefined) ? null : x;
let info = new this._nsLoginInfo(record.hostname,
record.formSubmitURL,
record.httpRealm,
nullUndefined(record.formSubmitURL),
nullUndefined(record.httpRealm),
record.username,
record.password,
record.usernameField,
@ -198,8 +213,18 @@ PasswordStore.prototype = {
},
create: function PasswordStore__create(record) {
let login = this._nsLoginInfoFromRecord(record);
if (!login)
return;
this._log.debug("Adding login for " + record.hostname);
Svc.Login.addLogin(this._nsLoginInfoFromRecord(record));
this._log.trace("httpRealm: " + JSON.stringify(login.httpRealm) + "; " +
"formSubmitURL: " + JSON.stringify(login.formSubmitURL));
try {
Svc.Login.addLogin(login);
} catch(ex) {
this._log.debug("Adding record " + record.id +
" resulted in exception " + Utils.exceptionStr(ex));
}
},
remove: function PasswordStore__remove(record) {
@ -223,7 +248,15 @@ PasswordStore.prototype = {
this._log.debug("Updating " + record.hostname);
let newinfo = this._nsLoginInfoFromRecord(record);
Svc.Login.modifyLogin(loginItem, newinfo);
if (!newinfo)
return;
try {
Svc.Login.modifyLogin(loginItem, newinfo);
} catch(ex) {
this._log.debug("Modifying record " + record.id +
" resulted in exception " + Utils.exceptionStr(ex) +
". Not modifying.");
}
},
wipe: function PasswordStore_wipe() {

View File

@ -142,6 +142,9 @@ function AsyncResource(uri) {
AsyncResource.prototype = {
_logName: "Net.Resource",
// Wait 5 minutes before killing a request
ABORT_TIMEOUT: 300000,
// ** {{{ Resource.authenticator }}} **
//
// Getter and setter for the authenticator module
@ -270,7 +273,7 @@ AsyncResource.prototype = {
// Setup a channel listener so that the actual network operation
// is performed asynchronously.
let listener = new ChannelListener(this._onComplete, this._onProgress,
this._log);
this._log, this.ABORT_TIMEOUT);
channel.requestMethod = action;
channel.asyncOpen(listener, null);
},
@ -411,8 +414,10 @@ Resource.prototype = {
try {
return doRequest(action, data, callback);
} catch(ex) {
// Combine the channel stack with this request stack
// Combine the channel stack with this request stack. Need to create
// a new error object for that.
let error = Error(ex.message);
error.result = ex.result;
let chanStack = [];
if (ex.stack)
chanStack = ex.stack.trim().split(/\n/).slice(1);
@ -466,15 +471,14 @@ Resource.prototype = {
//
// This object implements the {{{nsIStreamListener}}} interface
// and is called as the network operation proceeds.
function ChannelListener(onComplete, onProgress, logger) {
function ChannelListener(onComplete, onProgress, logger, timeout) {
this._onComplete = onComplete;
this._onProgress = onProgress;
this._log = logger;
this._timeout = timeout;
this.delayAbort();
}
ChannelListener.prototype = {
// Wait 5 minutes before killing a request
ABORT_TIMEOUT: 300000,
onStartRequest: function Channel_onStartRequest(channel) {
channel.QueryInterface(Ci.nsIHttpChannel);
@ -497,9 +501,13 @@ ChannelListener.prototype = {
if (this._data == '')
this._data = null;
// Throw the failure code name (and stop execution)
// Throw the failure code and stop execution. Use Components.Exception()
// instead of Error() so the exception is QI-able and can be passed across
// XPCOM borders while preserving the status code.
if (!Components.isSuccessCode(status)) {
this._onComplete(Error(Components.Exception("", status).name));
let message = Components.Exception("", status).name;
let error = Components.Exception(message, status);
this._onComplete(error);
return;
}
@ -535,14 +543,15 @@ ChannelListener.prototype = {
* Create or push back the abort timer that kills this request
*/
delayAbort: function delayAbort() {
Utils.delay(this.abortRequest, this.ABORT_TIMEOUT, this, "abortTimer");
Utils.delay(this.abortRequest, this._timeout, this, "abortTimer");
},
abortRequest: function abortRequest() {
// Ignore any callbacks if we happen to get any now
this.onStopRequest = function() {};
this.onDataAvailable = function() {};
this._onComplete(Error("Aborting due to channel inactivity."));
let error = Components.Exception("Aborting due to channel inactivity.",
Cr.NS_ERROR_NET_TIMEOUT);
this._onComplete(error);
}
};

View File

@ -398,6 +398,7 @@ WeaveSvc.prototype = {
Svc.Obs.add("weave:service:setup-complete", this);
Svc.Obs.add("network:offline-status-changed", this);
Svc.Obs.add("weave:service:sync:finish", this);
Svc.Obs.add("weave:service:login:error", this);
Svc.Obs.add("weave:service:sync:error", this);
Svc.Obs.add("weave:service:backoff:interval", this);
Svc.Obs.add("weave:engine:score:updated", this);
@ -542,15 +543,28 @@ WeaveSvc.prototype = {
this._log.trace("Network offline status change: " + data);
this._checkSyncStatus();
break;
case "weave:service:login:error":
if (Status.login == LOGIN_FAILED_NETWORK_ERROR && !Svc.IO.offline) {
this._ignorableErrorCount += 1;
}
break;
case "weave:service:sync:error":
this._handleSyncError();
if (Status.sync == CREDENTIALS_CHANGED) {
this.logout();
switch (Status.sync) {
case LOGIN_FAILED_NETWORK_ERROR:
if (!Svc.IO.offline) {
this._ignorableErrorCount += 1;
}
break;
case CREDENTIALS_CHANGED:
this.logout();
break;
}
break;
case "weave:service:sync:finish":
this._scheduleNextSync();
this._syncErrors = 0;
this._ignorableErrorCount = 0;
break;
case "weave:service:backoff:interval":
let interval = (data + Math.random() * data * 0.25) * 1000; // required backoff + up to 25%
@ -694,18 +708,19 @@ WeaveSvc.prototype = {
/**
* Perform the info fetch as part of a login or key fetch.
*/
_fetchInfo: function _fetchInfo(url, logout) {
_fetchInfo: function _fetchInfo(url) {
let infoURL = url || this.infoURL;
this._log.trace("In _fetchInfo: " + infoURL);
let info = new Resource(infoURL).get();
let info;
try {
info = new Resource(infoURL).get();
} catch (ex) {
this._checkServerError(ex);
throw ex;
}
if (!info.success) {
if (info.status == 401) {
if (logout) {
this.logout();
Status.login = LOGIN_FAILED_LOGIN_REJECTED;
}
}
this._checkServerError(info);
throw "aborting sync, failed to get collections";
}
return info;
@ -830,15 +845,6 @@ WeaveSvc.prototype = {
verifyLogin: function verifyLogin()
this._notify("verify-login", "", function() {
// Make sure we have a cluster to verify against
// this is a little weird, if we don't get a node we pretend
// to succeed, since that probably means we just don't have storage
if (this.clusterURL == "" && !this._setCluster()) {
Status.sync = NO_SYNC_NODE_FOUND;
Svc.Obs.notify("weave:service:sync:delayed");
return true;
}
if (!this.username) {
this._log.warn("No username in verifyLogin.");
Status.login = LOGIN_FAILED_NO_USERNAME;
@ -846,21 +852,30 @@ WeaveSvc.prototype = {
}
// Unlock master password, or return.
// Attaching auth credentials to a request requires access to
// passwords, which means that Resource.get can throw MP-related
// exceptions!
// Try to fetch the passphrase first, while we still have control.
try {
// Fetch collection info on every startup.
// Attaching auth credentials to a request requires access to
// passwords, which means that Resource.get can throw MP-related
// exceptions!
// Try to fetch the passphrase first, while we still have control.
try {
this.passphrase;
} catch (ex) {
this._log.debug("Fetching passphrase threw " + ex +
"; assuming master password locked.");
Status.login = MASTER_PASSWORD_LOCKED;
return false;
this.passphrase;
} catch (ex) {
this._log.debug("Fetching passphrase threw " + ex +
"; assuming master password locked.");
Status.login = MASTER_PASSWORD_LOCKED;
return false;
}
try {
// Make sure we have a cluster to verify against
// this is a little weird, if we don't get a node we pretend
// to succeed, since that probably means we just don't have storage
if (this.clusterURL == "" && !this._setCluster()) {
Status.sync = NO_SYNC_NODE_FOUND;
Svc.Obs.notify("weave:service:sync:delayed");
return true;
}
// Fetch collection info on every startup.
let test = new Resource(this.infoURL).get();
switch (test.status) {
@ -1077,8 +1092,10 @@ WeaveSvc.prototype = {
this._catch(this._lock("service.js: login",
this._notify("login", "", function() {
this._loggedIn = false;
if (Svc.IO.offline)
if (Svc.IO.offline) {
Status.login = LOGIN_FAILED_NETWORK_ERROR;
throw "Application is offline, login should not be called";
}
let initialStatus = this._checkSetup();
if (username)
@ -1616,9 +1633,10 @@ WeaveSvc.prototype = {
_handleSyncError: function WeaveSvc__handleSyncError() {
this._syncErrors++;
// do nothing on the first couple of failures, if we're not in backoff due to 5xx errors
// Do nothing on the first couple of failures, if we're not in
// backoff due to 5xx errors.
if (!Status.enforceBackoff) {
if (this._syncErrors < 3) {
if (this._syncErrors < MAX_ERROR_COUNT_BEFORE_BACKOFF) {
this._scheduleNextSync();
return;
}
@ -1628,6 +1646,12 @@ WeaveSvc.prototype = {
this._scheduleAtInterval();
},
_ignorableErrorCount: 0,
shouldIgnoreError: function shouldIgnoreError() {
return ([Status.login, Status.sync].indexOf(LOGIN_FAILED_NETWORK_ERROR) != -1
&& this._ignorableErrorCount < MAX_IGNORE_ERROR_COUNT);
},
_skipScheduledRetry: function _skipScheduledRetry() {
return [LOGIN_FAILED_INVALID_PASSPHRASE,
LOGIN_FAILED_LOGIN_REJECTED].indexOf(Status.login) == -1;
@ -1674,6 +1698,9 @@ WeaveSvc.prototype = {
// Make sure we should sync or record why we shouldn't
let reason = this._checkSync();
if (reason) {
if (reason == kSyncNetworkOffline) {
Status.sync = LOGIN_FAILED_NETWORK_ERROR;
}
// this is a purposeful abort rather than a failure, so don't set
// any status bits
reason = "Can't sync: " + reason;
@ -1706,7 +1733,7 @@ WeaveSvc.prototype = {
}
// Figure out what the last modified time is for each collection
let info = this._fetchInfo(infoURL, true);
let info = this._fetchInfo(infoURL);
this.globalScore = 0;
// Convert the response to an object and read out the modified times
@ -1974,18 +2001,46 @@ WeaveSvc.prototype = {
},
/**
* Check to see if this is a failure
*
* Handle HTTP response results or exceptions and set the appropriate
* Status.* bits.
*/
_checkServerError: function WeaveSvc__checkServerError(resp) {
if (Utils.checkStatus(resp.status, null, [500, [502, 504]])) {
Status.enforceBackoff = true;
if (resp.status == 503 && resp.headers["retry-after"])
Svc.Obs.notify("weave:service:backoff:interval",
parseInt(resp.headers["retry-after"], 10));
switch (resp.status) {
case 400:
if (resp == RESPONSE_OVER_QUOTA) {
Status.sync = OVER_QUOTA;
}
break;
case 401:
this.logout();
Status.login = LOGIN_FAILED_LOGIN_REJECTED;
break;
case 500:
case 502:
case 503:
case 504:
Status.enforceBackoff = true;
if (resp.status == 503 && resp.headers["retry-after"]) {
Svc.Obs.notify("weave:service:backoff:interval",
parseInt(resp.headers["retry-after"], 10));
}
break;
}
switch (resp.result) {
case Cr.NS_ERROR_UNKNOWN_HOST:
case Cr.NS_ERROR_CONNECTION_REFUSED:
case Cr.NS_ERROR_NET_TIMEOUT:
case Cr.NS_ERROR_NET_RESET:
case Cr.NS_ERROR_NET_INTERRUPT:
case Cr.NS_ERROR_PROXY_CONNECTION_REFUSED:
// The constant says it's about login, but in fact it just
// indicates general network error.
Status.sync = LOGIN_FAILED_NETWORK_ERROR;
break;
}
if (resp.status == 400 && resp == RESPONSE_OVER_QUOTA)
Status.sync = OVER_QUOTA;
},
/**
* Return a value for a backoff interval. Maximum is eight hours, unless

View File

@ -198,7 +198,23 @@ let Utils = {
throw batchEx;
};
},
runInTransaction: function(db, callback, thisObj) {
let hasTransaction = false;
try {
db.beginTransaction();
hasTransaction = true;
} catch(e) { /* om nom nom exceptions */ }
try {
return callback.call(thisObj);
} finally {
if (hasTransaction) {
db.commitTransaction();
}
}
},
createStatement: function createStatement(db, query) {
// Gecko 2.0
if (db.createAsyncStatement)

View File

@ -60,6 +60,7 @@ function test_ID_caching() {
function test_processIncoming_error_orderChildren() {
_("Ensure that _orderChildren() is called even when _processIncoming() throws an error.");
do_test_pending();
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
Svc.Prefs.set("username", "foo");
@ -139,6 +140,7 @@ function test_processIncoming_error_orderChildren() {
function test_restorePromptsReupload() {
_("Ensure that restoring from a backup will reupload all records.");
do_test_pending();
Svc.Prefs.set("username", "foo");
Service.serverURL = "http://localhost:8080/";
Service.clusterURL = "http://localhost:8080/";

View File

@ -1,10 +1,15 @@
_("Make sure the form store follows the Store api and correctly accesses the backend form storage");
Cu.import("resource://services-sync/engines/forms.js");
Cu.import("resource://services-sync/util.js");
function run_test() {
let baseuri = "http://fake/uri/";
let store = new FormEngine()._store;
function applyEnsureNoFailures(records) {
do_check_eq(store.applyIncomingBatch(records).length, 0);
}
_("Remove any existing entries");
store.wipe();
for (let id in store.getAllIDs()) {
@ -12,10 +17,11 @@ function run_test() {
}
_("Add a form entry");
store.create({
applyEnsureNoFailures([{
id: Utils.makeGUID(),
name: "name!!",
value: "value??"
});
}]);
_("Should have 1 entry now");
let id = "";
@ -45,10 +51,11 @@ function run_test() {
}
_("Add another entry");
store.create({
applyEnsureNoFailures([{
id: Utils.makeGUID(),
name: "another",
value: "entry"
});
}]);
id = "";
for (let _id in store.getAllIDs()) {
if (id == "")

View File

@ -0,0 +1,92 @@
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/engines/passwords.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/service.js");
Engines.register(PasswordEngine);
function makeEngine() {
return new PasswordEngine();
}
var syncTesting = new SyncTestingInfrastructure(makeEngine);
function run_test() {
initTestLogging("Trace");
Log4Moz.repository.getLogger("Engine.Passwords").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Store.Passwords").level = Log4Moz.Level.Trace;
do_test_pending();
CollectionKeys.generateNewKeys();
// Verify that password entries with redundant fields don't cause an exception.
const BOGUS_GUID_A = "zzzzzzzzzzzz";
const BOGUS_GUID_B = "yyyyyyyyyyyy";
let payloadA = {id: BOGUS_GUID_A,
hostname: "http://foo.bar.com",
formSubmitURL: "http://foo.bar.com/baz",
httpRealm: "secure",
username: "john",
password: "smith",
usernameField: "username",
passwordField: "password"};
let payloadB = {id: BOGUS_GUID_B,
hostname: "http://foo.baz.com",
formSubmitURL: "http://foo.baz.com/baz",
username: "john",
password: "smith",
usernameField: "username",
passwordField: "password"};
let badWBO = new ServerWBO(BOGUS_GUID_A, encryptPayload(payloadA));
let goodWBO = new ServerWBO(BOGUS_GUID_B, encryptPayload(payloadB));
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
Svc.Prefs.set("username", "foo");
let engine = new PasswordEngine();
let store = engine._store;
let global = new ServerWBO("global",
{engines: {passwords: {version: engine.version,
syncID: engine.syncID}}});
let collection = new ServerCollection({BOGUS_GUID_A: badWBO,
BOGUS_GUID_B: goodWBO});
let server = httpd_setup({
"/1.0/foo/storage/meta/global": global.handler(),
"/1.0/foo/storage/passwords": collection.handler()
});
try {
let err;
try {
engine.sync();
} catch (ex) {
err = ex;
}
// No exception thrown when encountering bad record.
do_check_true(!err);
// Only the good record makes it to Svc.Login.
let badCount = {};
let goodCount = {};
let badLogins = Svc.Login.findLogins(badCount, payloadA.hostname, payloadA.formSubmitURL, payloadA.httpRealm);
let goodLogins = Svc.Login.findLogins(goodCount, payloadB.hostname, payloadB.formSubmitURL, null);
_("Bad: " + JSON.stringify(badLogins));
_("Good: " + JSON.stringify(goodLogins));
_("Count: " + badCount.value + ", " + goodCount.value);
do_check_eq(goodCount.value, 1);
do_check_eq(badCount.value, 0);
do_check_true(!!store.getAllIDs()[BOGUS_GUID_B]);
do_check_true(!store.getAllIDs()[BOGUS_GUID_A]);
}
finally {
store.wipe();
server.stop(do_test_finished);
Svc.Prefs.resetBranch("");
Records.clearCache();
}
}

View File

@ -3,6 +3,7 @@ Cu.import("resource://services-sync/identity.js");
Cu.import("resource://services-sync/log4moz.js");
Cu.import("resource://services-sync/resource.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/ext/Sync.js");
let logger;
@ -189,7 +190,7 @@ function run_test() {
Utils.prefs.setIntPref("network.numRetries", 1); // speed up test
_("Resource object memebers");
_("Resource object members");
let res = new Resource("http://localhost:8080/open");
do_check_true(res.uri instanceof Ci.nsIURI);
do_check_eq(res.uri.spec, "http://localhost:8080/open");
@ -406,6 +407,7 @@ function run_test() {
} catch(ex) {
error = ex;
}
do_check_eq(error.result, Cr.NS_ERROR_CONNECTION_REFUSED);
do_check_eq(error.message, "NS_ERROR_CONNECTION_REFUSED");
do_check_eq(typeof error.stack, "string");
@ -489,6 +491,7 @@ function run_test() {
}
// It throws and logs.
do_check_eq(error.result, Cr.NS_ERROR_MALFORMED_URI);
do_check_eq(error, "Error: NS_ERROR_MALFORMED_URI");
do_check_eq(warnings.pop(),
"Got exception calling onProgress handler during fetch of " +
@ -511,11 +514,23 @@ function run_test() {
}
// It throws and logs.
do_check_eq(error.result, Cr.NS_ERROR_XPC_JS_THREW_STRING);
do_check_eq(error, "Error: NS_ERROR_XPC_JS_THREW_STRING");
do_check_eq(warnings.pop(),
"Got exception calling onProgress handler during fetch of " +
"http://localhost:8080/json");
_("Ensure channel timeouts are thrown appropriately.");
let res19 = new Resource("http://localhost:8080/json");
res19.ABORT_TIMEOUT = 0;
error = undefined;
try {
content = res19.get();
} catch (ex) {
error = ex;
}
do_check_eq(error.result, Cr.NS_ERROR_NET_TIMEOUT);
server.stop(do_test_finished);
}

View File

@ -556,8 +556,8 @@ function run_test() {
let res11 = new AsyncResource("http://localhost:12345/does/not/exist");
res11.get(ensureThrows(function (error, content) {
do_check_neq(error, null);
do_check_eq(error.result, Cr.NS_ERROR_CONNECTION_REFUSED);
do_check_eq(error.message, "NS_ERROR_CONNECTION_REFUSED");
do_check_eq(typeof error.stack, "string");
do_test_finished();
next();
}));
@ -621,7 +621,8 @@ function run_test() {
res14._log.warn = function(msg) { warnings.push(msg) };
res14.get(ensureThrows(function (error, content) {
do_check_eq(error, "Error: NS_ERROR_MALFORMED_URI");
do_check_eq(error.result, Cr.NS_ERROR_MALFORMED_URI);
do_check_eq(error.message, "NS_ERROR_MALFORMED_URI");
do_check_eq(content, null);
do_check_eq(warnings.pop(),
"Got exception calling onProgress handler during fetch of " +
@ -643,7 +644,8 @@ function run_test() {
res15._log.warn = function(msg) { warnings.push(msg) };
res15.get(ensureThrows(function (error, content) {
do_check_eq(error, "Error: NS_ERROR_XPC_JS_THREW_STRING");
do_check_eq(error.result, Cr.NS_ERROR_XPC_JS_THREW_STRING);
do_check_eq(error.message, "NS_ERROR_XPC_JS_THREW_STRING");
do_check_eq(content, null);
do_check_eq(warnings.pop(),
"Got exception calling onProgress handler during fetch of " +
@ -653,6 +655,16 @@ function run_test() {
next();
}));
}, function (next) {
_("Ensure channel timeouts are thrown appropriately.");
let res19 = new AsyncResource("http://localhost:8080/json");
res19.ABORT_TIMEOUT = 0;
res19.get(ensureThrows(function (error, content) {
do_check_eq(error.result, Cr.NS_ERROR_NET_TIMEOUT);
next();
}));
}, function (next) {
// Don't quit test harness before server shuts down.

View File

@ -24,6 +24,18 @@ function run_test() {
let logger = Log4Moz.repository.rootLogger;
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
try {
_("The right bits are set when we're offline.");
Svc.IO.offline = true;
do_check_eq(Service._ignorableErrorCount, 0);
do_check_false(!!Service.login());
do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
do_check_eq(Service._ignorableErrorCount, 0);
Svc.IO.offline = false;
} finally {
Svc.Prefs.resetBranch("");
}
do_test_pending();
let server = httpd_setup({
"/1.0/johndoe/info/collections": login_handler,
@ -111,7 +123,7 @@ function run_test() {
Service.logout();
do_check_false(Service.isLoggedIn);
do_check_false(Svc.Prefs.get("autoconnect"));
/*
* Testing login-on-sync.
*/

View File

@ -69,8 +69,8 @@ function test_backoff500(next) {
Engines.unregister("catapult");
Status.resetBackoff();
Service.startOver();
server.stop(next);
}
server.stop(next);
}
function test_backoff503(next) {
@ -102,8 +102,8 @@ function test_backoff503(next) {
Engines.unregister("catapult");
Status.resetBackoff();
Service.startOver();
server.stop(next);
}
server.stop(next);
}
function test_overQuota(next) {
@ -128,8 +128,101 @@ function test_overQuota(next) {
Engines.unregister("catapult");
Status.resetSync();
Service.startOver();
server.stop(next);
}
server.stop(next);
}
function test_service_networkError(next) {
_("Test: Connection refused error from Service.sync() leads to the right status code.");
setUp();
// Provoke connection refused.
Service.clusterURL = "http://localhost:12345/";
Service._ignorableErrorCount = 0;
try {
do_check_eq(Status.sync, SYNC_SUCCEEDED);
Service._loggedIn = true;
Service.sync();
do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
do_check_eq(Service._ignorableErrorCount, 1);
} finally {
Status.resetSync();
Service.startOver();
}
next();
}
function test_service_offline(next) {
_("Test: Wanting to sync in offline mode leads to the right status code but does not increment the ignorable error count.");
setUp();
Svc.IO.offline = true;
Service._ignorableErrorCount = 0;
try {
do_check_eq(Status.sync, SYNC_SUCCEEDED);
Service._loggedIn = true;
Service.sync();
do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
do_check_eq(Service._ignorableErrorCount, 0);
} finally {
Status.resetSync();
Service.startOver();
}
Svc.IO.offline = false;
next();
}
function test_service_reset_ignorableErrorCount(next) {
_("Test: Successful sync resets the ignorable error count.");
let server = sync_httpd_setup();
setUp();
Service._ignorableErrorCount = 10;
try {
do_check_eq(Status.sync, SYNC_SUCCEEDED);
Service.login();
Service.sync();
do_check_eq(Status.sync, SYNC_SUCCEEDED);
do_check_eq(Service._ignorableErrorCount, 0);
} finally {
Status.resetSync();
Service.startOver();
}
server.stop(next);
}
function test_engine_networkError(next) {
_("Test: Network related exceptions from engine.sync() lead to the right status code.");
let server = sync_httpd_setup();
setUp();
Service._ignorableErrorCount = 0;
Engines.register(CatapultEngine);
let engine = Engines.get("catapult");
engine.enabled = true;
engine.exception = Components.Exception("NS_ERROR_UNKNOWN_HOST",
Cr.NS_ERROR_UNKNOWN_HOST);
try {
do_check_eq(Status.sync, SYNC_SUCCEEDED);
Service.login();
Service.sync();
do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
do_check_eq(Service._ignorableErrorCount, 1);
} finally {
Engines.unregister("catapult");
Status.resetSync();
Service.startOver();
}
server.stop(next);
}
// Slightly misplaced test as it doesn't actually test checkServerError,
@ -156,8 +249,8 @@ function test_engine_applyFailed(next) {
Engines.unregister("catapult");
Status.resetSync();
Service.startOver();
server.stop(next);
}
server.stop(next);
}
function run_test() {
@ -168,6 +261,10 @@ function run_test() {
asyncChainTests(test_backoff500,
test_backoff503,
test_overQuota,
test_service_networkError,
test_service_offline,
test_service_reset_ignorableErrorCount,
test_engine_networkError,
test_engine_applyFailed,
do_test_finished)();
}

View File

@ -49,11 +49,13 @@ function run_test() {
do_check_eq(Status.service, STATUS_OK);
_("Credentials won't check out because we're not configured yet.");
Status.resetSync();
do_check_false(Service.verifyLogin());
do_check_eq(Status.service, CLIENT_NOT_CONFIGURED);
do_check_eq(Status.login, LOGIN_FAILED_NO_USERNAME);
_("Try again with username and password set.");
Status.resetSync();
Service.username = "johndoe";
Service.password = "ilovejane";
do_check_false(Service.verifyLogin());
@ -64,12 +66,14 @@ function run_test() {
do_check_eq(Service.clusterURL, "http://localhost:8080/api/");
_("Success if passphrase is set.");
Status.resetSync();
Service.passphrase = "foo";
do_check_true(Service.verifyLogin());
do_check_eq(Status.service, STATUS_OK);
do_check_eq(Status.login, LOGIN_SUCCEEDED);
_("If verifyLogin() encounters a server error, it flips on the backoff flag and notifies observers on a 503 with Retry-After.");
Status.resetSync();
Service.username = "janedoe";
do_check_false(Status.enforceBackoff);
let backoffInterval;
@ -82,6 +86,20 @@ function run_test() {
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, LOGIN_FAILED_SERVER_ERROR);
_("Ensure a network error when finding the cluster sets the right Status bits.");
Status.resetSync();
Service.serverURL = "http://localhost:12345/";
do_check_false(Service.verifyLogin());
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
_("Ensure a network error when getting the collection info sets the right Status bits.");
Status.resetSync();
Service.clusterURL = "http://localhost:12345/";
do_check_false(Service.verifyLogin());
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
} finally {
Svc.Prefs.resetBranch("");
server.stop(do_test_finished);

View File

@ -0,0 +1 @@
1.7b1pre