Merge services-central with mozilla-central

This commit is contained in:
Philipp von Weitershausen 2011-11-02 15:00:49 -07:00
commit d69fb6bf5a
40 changed files with 2033 additions and 314 deletions

View File

@ -369,6 +369,14 @@ body[dir=rtl] #restorePreviousSession::before {
bottom: 2%;
}
#syncLinksContainer {
padding-top: 1em;
}
.sync-link {
padding: 1em;
}
@media all and (max-height: 370px) {
#bottomSection {
visibility: hidden;

View File

@ -102,6 +102,10 @@
<div id="aboutMozilla">
<a href="http://www.mozilla.com/about/">&abouthome.aboutMozilla;</a>
</div>
<div id="syncLinksContainer">
<a href="javascript:void(0);" class="sync-link" id="setupSyncLink">&abouthome.syncSetup.label;</a>
<a href="javascript:void(0);" class="sync-link" id="pairDeviceLink">&abouthome.pairDevice.label;</a>
</div>
</div>
</body>
</html>

View File

@ -211,6 +211,24 @@ let gSyncUI = {
this.clearError(title);
},
// Set visibility of "Setup Sync" link
showSetupSyncAboutHome: function SUI_showSetupSyncAboutHome(toShow) {
let browsers = gBrowser.browsers;
for (let i = 0; i < browsers.length; i++) {
let b = browsers[i];
if ("about:home" == b.currentURI.spec) {
b.contentDocument.getElementById("setupSyncLink").hidden = !toShow;
}
}
},
onSetupComplete: function SUI_onSetupComplete() {
// Remove "setup sync" link in about:home if it is open.
this.showSetupSyncAboutHome(false);
onLoginFinish();
},
onLoginError: function SUI_onLoginError() {
// if login fails, any other notifications are essentially moot
Weave.Notifications.removeAll();
@ -255,6 +273,8 @@ let gSyncUI = {
onStartOver: function SUI_onStartOver() {
this.clearError();
// Make "setup sync" link visible in about:home if it is open.
this.showSetupSyncAboutHome(true);
},
onQuotaNotice: function onQuotaNotice(subject, data) {
@ -291,16 +311,40 @@ let gSyncUI = {
//XXXzpao should be part of syncCommon.js - which we might want to make a module...
// To be fixed in a followup (bug 583366)
openSetup: function SUI_openSetup() {
/**
* Invoke the Sync setup wizard.
*
* @param wizardType
* Indicates type of wizard to launch:
* null -- regular set up wizard
* "pair" -- pair a device first
* "reset" -- reset sync
*/
openSetup: function SUI_openSetup(wizardType) {
let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
if (win)
win.focus();
else {
window.openDialog("chrome://browser/content/syncSetup.xul",
"weaveSetup", "centerscreen,chrome,resizable=no");
"weaveSetup", "centerscreen,chrome,resizable=no",
wizardType);
}
},
openAddDevice: function () {
if (!Weave.Utils.ensureMPUnlocked())
return;
let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
if (win)
win.focus();
else
window.openDialog("chrome://browser/content/syncAddDevice.xul",
"syncAddDevice", "centerscreen,chrome,resizable=no");
},
openQuotaDialog: function SUI_openQuotaDialog() {
let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
if (win)
@ -462,7 +506,7 @@ let gSyncUI = {
this.onQuotaNotice();
break;
case "weave:service:setup-complete":
this.onLoginFinish();
this.onSetupComplete();
break;
case "weave:service:login:start":
this.onActivityStart();

View File

@ -2675,6 +2675,10 @@ function BrowserOnAboutPageLoad(document) {
getService(Components.interfaces.nsISessionStore);
if (!ss.canRestoreLastSession)
document.getElementById("sessionRestoreContainer").hidden = true;
// Sync-related links
if (Services.prefs.prefHasUserValue("services.sync.username")) {
document.getElementById("setupSyncLink").hidden = true;
}
}
}
@ -2683,7 +2687,9 @@ function BrowserOnAboutPageLoad(document) {
*/
function BrowserOnClick(event) {
// Don't trust synthetic events
if (!event.isTrusted || event.target.localName != "button")
if (!event.isTrusted ||
(event.target.localName != "button" &&
event.target.className != "sync-link"))
return;
var ot = event.originalTarget;
@ -2813,6 +2819,16 @@ function BrowserOnClick(event) {
ss.restoreLastSession();
errorDoc.getElementById("sessionRestoreContainer").hidden = true;
}
else if (ot == errorDoc.getElementById("pairDeviceLink")) {
if (Services.prefs.prefHasUserValue("services.sync.username")) {
gSyncUI.openAddDevice();
} else {
gSyncUI.openSetup("pair");
}
}
else if (ot == errorDoc.getElementById("setupSyncLink")) {
gSyncUI.openSetup(null);
}
}
}

View File

@ -44,12 +44,14 @@ let gProgressBar;
let gCounter = 0;
function onLoad(event) {
Services.obs.addObserver(increaseProgressBar, "weave:engine:sync:finish", false);
Services.obs.addObserver(increaseProgressBar, "weave:engine:sync:error", false);
Services.obs.addObserver(onEngineSync, "weave:engine:sync:finish", false);
Services.obs.addObserver(onEngineSync, "weave:engine:sync:error", false);
Services.obs.addObserver(onServiceSync, "weave:service:sync:finish", false);
Services.obs.addObserver(onServiceSync, "weave:service:sync:error", false);
gProgressBar = document.getElementById('uploadProgressBar');
if (Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
gProgressBar.max = Weave.Engines.getEnabled().length;
gProgressBar.style.display = "inline";
}
else {
@ -58,15 +60,46 @@ function onLoad(event) {
}
function onUnload(event) {
Services.obs.removeObserver(increaseProgressBar, "weave:engine:sync:finish");
Services.obs.removeObserver(increaseProgressBar, "weave:engine:sync:error");
cleanUpObservers();
}
function increaseProgressBar(){
function cleanUpObservers() {
try {
Services.obs.removeObserver(onEngineSync, "weave:engine:sync:finish", false);
Services.obs.removeObserver(onEngineSync, "weave:engine:sync:error", false);
Services.obs.removeObserver(onServiceSync, "weave:service:sync:finish", false);
Services.obs.removeObserver(onServiceSync, "weave:service:sync:error", false);
}
catch (e) {
// may be double called by unload & exit. Ignore.
}
}
function onEngineSync(subject, topic, data) {
// The Clients engine syncs first. At this point we don't necessarily know
// yet how many engines will be enabled, so we'll ignore the Clients engine
// and evaluate how many engines are enabled when the first "real" engine
// syncs.
if (data == "clients") {
return;
}
if (!gCounter &&
Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
gProgressBar.max = Weave.Engines.getEnabled().length;
}
gCounter += 1;
gProgressBar.setAttribute("value", gCounter);
}
function onServiceSync(subject, topic, data) {
// To address the case where 0 engines are synced, we will fill the
// progress bar so the user knows that the sync has finished.
gProgressBar.setAttribute("value", gProgressBar.max);
cleanUpObservers();
}
function closeTab() {
window.close();
}

View File

@ -10,6 +10,9 @@ registerCleanupFunction(function() {
try {
Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
} catch (ex) {}
try {
Services.prefs.clearUserPref("services.sync.username");
} catch (ex) {}
});
let gTests = [
@ -114,6 +117,116 @@ let gTests = [
}
},
{
desc: "Check sync links visibility before and after Sync setup",
setup: function ()
{
try {
Services.prefs.clearUserPref("services.sync.username");
} catch (ex) {}
Services.obs.notifyObservers(null, "weave:service:ready", null);
},
run: function ()
{
let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
let pairLink = doc.getElementById("pairDeviceLink");
let setupLink = doc.getElementById("setupSyncLink");
ok(pairLink, "Found 'Pair Device' link");
ok(setupLink, "Found 'Set Up Sync' link");
ok(!pairLink.hidden, "'Pair' link is visible before setup");
ok(!setupLink.hidden, "'Set Up' link is visible before setup");
Services.obs.notifyObservers(null, "weave:service:setup-complete", null);
executeSoon(function () {
setupLink = doc.getElementById("setupSyncLink");
ok(setupLink.hidden, "'Set Up' link is hidden after setup");
ok(!pairLink.hidden, "'Pair' link is visible after setup");
executeSoon(runNextTest);
});
}
},
{
desc: "Check sync links visibility before and after Sync unlink",
setup: function ()
{
Services.prefs.setCharPref("services.sync.username", "someuser@domain.com");
Services.obs.notifyObservers(null, "weave:service:ready", null);
},
run: function ()
{
let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
let pairLink = doc.getElementById("pairDeviceLink");
let setupLink = doc.getElementById("setupSyncLink");
ok(!pairLink.hidden, "'Pair' link is visible before unlink");
ok(setupLink.hidden, "'Set Up' link is hidden before unlink");
Services.obs.notifyObservers(null, "weave:service:start-over", null);
executeSoon(function () {
setupLink = doc.getElementById("setupSyncLink");
ok(!setupLink.hidden, "'Set Up' link is visible after unlink");
ok(!pairLink.hidden, "'Pair' link is visible after unlink");
executeSoon(runNextTest);
});
}
},
{
desc: "Check Pair Device link opens correct dialog with Sync account ",
setup: function ()
{
Services.prefs.setCharPref("services.sync.username", "someuser@domain.com");
Services.obs.notifyObservers(null, "weave:service:ready", null);
},
run: function ()
{
expectDialogWindow("Sync:AddDevice");
let browser = gBrowser.selectedTab.linkedBrowser;
let button = browser.contentDocument.getElementById("pairDeviceLink");
EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow);
}
},
{
desc: "Check Pair Device link opens correct dialog without Sync account",
setup: function ()
{
try {
Services.prefs.clearUserPref("services.sync.username");
} catch (ex) {}
Services.obs.notifyObservers(null, "weave:service:ready", null);
},
run: function ()
{
expectDialogWindow("Weave:AccountSetup");
let browser = gBrowser.selectedTab.linkedBrowser;
let button = browser.contentDocument.getElementById("pairDeviceLink");
EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow);
}
},
{
desc: "Check Sync Setup link opens correct dialog (without Sync account)",
setup: function ()
{
try {
Services.prefs.clearUserPref("services.sync.username");
} catch (ex) {}
Services.obs.notifyObservers(null, "weave:service:ready", null);
},
run: function ()
{
expectDialogWindow("Weave:AccountSetup");
let browser = gBrowser.selectedTab.linkedBrowser;
let button = browser.contentDocument.getElementById("setupSyncLink");
EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow);
}
},
];
function test()
@ -159,6 +272,22 @@ function runNextTest()
}
}
function expectDialogWindow(expectedDialog) {
Services.ww.registerNotification(function onWindow(subject, topic) {
let win = subject.QueryInterface(Components.interfaces.nsIDOMWindow);
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad, false);
let wintype = win.document.documentElement.getAttribute("windowtype");
if (topic == "domwindowopened" && wintype == expectedDialog) {
Services.ww.unregisterNotification(onWindow);
// Clean up dialog.
win.close();
executeSoon(runNextTest);
}
}, false);
});
}
function getStorage()
{
let aboutHomeURI = Services.io.newURI("moz-safe-about:home", null, null);

View File

@ -18,3 +18,6 @@
text in <a/> will be linked to the featured add-ons on addons.mozilla.org
-->
<!ENTITY abouthome.defaultSnippet2.v1 "It's easy to customize your Firefox exactly the way you want it. <a>Choose from thousands of add-ons</a>.">
<!ENTITY abouthome.syncSetup.label "Set Up Sync">
<!ENTITY abouthome.pairDevice.label "Pair a Device">

View File

@ -96,6 +96,7 @@
</div>
<div id="sync-setup">
<a id="syncSetupSync" href="#" onclick="openSetupSyncWizard();">&aboutHome.setupSync;</a>
<a id="syncPairDevice" href="#" onclick="openPairDeviceWizard();">&aboutHome.syncPairDevice;</a>
</div>
</div>
</div>
@ -433,8 +434,10 @@
function initSetupSync() {
let services = getChromeWin().Services;
if (services.prefs.prefHasUserValue("services.sync.username")) {
document.getElementById("syncSetupSync").style.display = "none";
}
syncConnected();
} else {
syncDisconnected();
}
services.obs.addObserver(syncConnected, "weave:service:setup-complete", false);
services.obs.addObserver(syncDisconnected, "weave:service:start-over", false);
}
@ -447,10 +450,12 @@
function syncConnected() {
document.getElementById("syncSetupSync").style.display = "none";
document.getElementById("syncPairDevice").style.display = "inline";
}
function syncDisconnected() {
document.getElementById("syncSetupSync").style.display = "inline";
document.getElementById("syncPairDevice").style.display = "none";
}
function openSetupSyncWizard() {
@ -458,6 +463,11 @@
chromeWin.WeaveGlue.open();
}
function openPairDeviceWizard() {
let chromeWin = getChromeWin();
chromeWin.SyncPairDevice.open();
}
function initLightbox() {
let prefs = getChromeWin().Services.prefs;
let channel = prefs.getCharPref("app.update.channel");

View File

@ -113,6 +113,7 @@ XPCOMUtils.defineLazyGetter(this, "CommonUI", function() {
["MasterPasswordUI", "chrome://browser/content/MasterPasswordUI.js"],
#ifdef MOZ_SERVICES_SYNC
["WeaveGlue", "chrome://browser/content/sync.js"],
["SyncPairDevice", "chrome://browser/content/sync.js"],
#endif
["WebappsUI", "chrome://browser/content/WebappsUI.js"],
["SSLExceptions", "chrome://browser/content/exceptions.js"],

View File

@ -24,6 +24,7 @@
- Brad Lassey <blassey@mozilla.com>
- Mark Finkle <mfinkle@mozila.com>
- Matt Brubeck <mbrubeck@mozila.com>
- Allison Naaktgeboren <ally@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
@ -487,6 +488,7 @@
<button label="&sync.connect;" oncommand="WeaveGlue.tryConnect();" />
</setting>
<setting id="sync-connected" class="setting-group" title="&sync.connected;" type="control" collapsed="true">
<button id="sync-pairdevice" label="&sync.pair.title;" oncommand="SyncPairDevice.open();" />
<button id="sync-details" label="&sync.details;" type="checkbox" autocheck="false" checked="false" oncommand="WeaveGlue.showDetails();" />
</setting>
<setting id="sync-sync" class="setting-subgroup" type="control" collapsed="true">
@ -593,7 +595,15 @@
</hbox>
</vbox>
<vbox id="syncsetup-waiting" class="syncsetup-page" flex="1" hidden="true">
<description class="syncsetup-desc syncsetup-center" flex="1">&sync.setup.waiting;</description>
<progressmeter id="syncsetup-progressbar" mode="undetermined"/>
<vbox id="syncsetup-waiting-top" align="center" flex="1">
<description id="syncsetup-waiting-desc" class="syncsetup-desc syncsetup-center" flex="1">&sync.setup.waiting2;</description>
<description id="syncsetup-waitingdownload-desc" class="syncsetup-desc syncsetup-center" hidden="true" flex="1">&sync.setup.waitingdownload;</description>
</vbox>
<hbox class="prompt-buttons" pack="center" align="end">
<button id="syncsetup-waiting-cancel" class="prompt-button" oncommand="WeaveGlue.close();">&sync.setup.cancel;</button>
<button id="syncsetup-waiting-close" class="prompt-button" hidden="true" oncommand="WeaveGlue.close();">&sync.setup.close;</button>
</hbox>
</vbox>
<vbox id="syncsetup-fallback" class="syncsetup-page" flex="1" hidden="true">
<scrollbox class="prompt-message" orient="vertical" flex="1">
@ -618,6 +628,32 @@
</vbox>
</dialog>
</box>
<box id="syncpair-container" class="perm-modal-block" hidden="true">
<dialog id="syncpair-dialog" class="content-dialog" flex="1">
<hbox class="prompt-title">
<description>&sync.pair.title;</description>
</hbox>
<separator class="prompt-line"/>
<vbox id="syncpair-simple" class="syncsetup-page" flex="1">
<scrollbox id="sync-message" class="prompt-message" orient="vertical" flex="1">
<description class="syncsetup-desc syncsetup-center" flex="1">&sync.pair.description;</description>
<description class="syncsetup-center link" flex="1" onclick="SyncPairDevice.close(); WeaveGlue.openTutorial();">&sync.setup.tutorial;</description>
<separator/>
<vbox align="center" flex="1">
<textbox id="syncpair-code1" class="syncsetup-code" oninput="SyncPairDevice.onTextBoxInput(this);"/>
<textbox id="syncpair-code2" class="syncsetup-code" oninput="SyncPairDevice.onTextBoxInput(this);"/>
<textbox id="syncpair-code3" class="syncsetup-code" oninput="SyncPairDevice.onTextBoxInput(this);"/>
</vbox>
<separator flex="1"/>
</scrollbox>
<hbox class="prompt-buttons" pack="center">
<button class="prompt-button" oncommand="SyncPairDevice.close();">&sync.setup.cancel;</button>
<button id="syncpair-connectbutton" class="prompt-button" disabled="true" oncommand="SyncPairDevice.connect();">&sync.setup.connect;</button>
</hbox>
</vbox>
</dialog>
</box>
#endif
<arrowbox id="search-engines-popup" hidden="true" offset="18" flex="1" type="dialog">

View File

@ -21,6 +21,7 @@
* Mark Finkle <mfinkle@mozila.com>
* Matt Brubeck <mbrubeck@mozila.com>
* Jono DiCarlo <jdicarlo@mozilla.com>
* Allison Naaktgeboren <ally@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
@ -38,9 +39,14 @@
let WeaveGlue = {
setupData: null,
_boundOnEngineSync: null, // Needed to unhook the observers in close().
_boundOnServiceSync: null,
jpake: null,
_bundle: null,
_loginError: false,
_progressBar: null,
_progressValue: 0,
_progressMax: null,
init: function init() {
if (this._bundle)
@ -64,6 +70,9 @@ let WeaveGlue = {
} else if (Weave.Status.login != Weave.LOGIN_FAILED_NO_USERNAME) {
this.loadSetupData();
}
this._boundOnEngineSync = this.onEngineSync.bind(this);
this._boundOnServiceSync = this.onServiceSync.bind(this);
this._progressBar = document.getElementById("syncsetup-progressbar");
},
abortEasySetup: function abortEasySetup() {
@ -128,7 +137,16 @@ let WeaveGlue = {
onComplete: function onComplete(aCredentials) {
self.jpake = null;
self.close();
self._progressBar.mode = "determined";
document.getElementById("syncsetup-waiting-desc").hidden = true;
document.getElementById("syncsetup-waiting-cancel").hidden = true;
document.getElementById("syncsetup-waitingdownload-desc").hidden = false;
document.getElementById("syncsetup-waiting-close").hidden = false;
Services.obs.addObserver(self._boundOnEngineSync, "weave:engine:sync:finish", false);
Services.obs.addObserver(self._boundOnEngineSync, "weave:engine:sync:error", false);
Services.obs.addObserver(self._boundOnServiceSync, "weave:service:sync:finish", false);
Services.obs.addObserver(self._boundOnServiceSync, "weave:service:sync:error", false);
self.setupData = aCredentials;
self.connect();
},
@ -180,6 +198,7 @@ let WeaveGlue = {
this._resetScrollPosition();
document.getElementById("syncsetup-simple").hidden = true;
document.getElementById("syncsetup-waiting").hidden = true;
document.getElementById("syncsetup-fallback").hidden = false;
// Push the current setup data into the UI
@ -204,7 +223,37 @@ let WeaveGlue = {
this.canConnect();
},
onEngineSync: function onEngineSync(subject, topic, data) {
// The Clients engine syncs first. At this point we don't necessarily know
// yet how many engines will be enabled, so we'll ignore the Clients engine
// and evaluate how many engines are enabled when the first "real" engine
// syncs.
if (data == 'clients') {
return;
}
if (this._progressMax == null) {
this._progressMax = Weave.Engines.getEnabled().length;
this._progressBar.max = this._progressMax;
}
this._progressValue += 1;
this._progressBar.setAttribute("value", this._progressValue);
},
onServiceSync: function onServiceSync() {
this.close();
},
close: function close() {
try {
Services.obs.removeObserver(this._boundOnEngineSync, "weave:engine:sync:finish");
Services.obs.removeObserver(this._boundOnEngineSync, "weave:engine:sync:error");
Services.obs.removeObserver(this._boundOnServiceSync, "weave:service:sync:finish");
Services.obs.removeObserver(this._boundOnServiceSync, "weave:service:sync:error");
}
catch(e) {
// Observers weren't registered because we never got as far as onComplete.
}
if (this.jpake)
this.abortEasySetup();
@ -226,6 +275,15 @@ let WeaveGlue = {
this._elements.usecustomserver.checked = false;
this._elements.customserver.disabled = true;
this._elements.customserver.value = "";
document.getElementById("syncsetup-waiting-desc").hidden = false;
document.getElementById("syncsetup-waiting-cancel").hidden = false;
document.getElementById("syncsetup-waitingdownload-desc").hidden = true;
document.getElementById("syncsetup-waiting-close").hidden = true;
this._progressMax = null;
this._progressValue = 0;
this._progressBar.max = 0;
this._progressBar.value = 0;
this._progressBar.mode = "undetermined";
// Close the connect UI
document.getElementById("syncsetup-container").hidden = true;
@ -372,7 +430,7 @@ let WeaveGlue = {
elements[id] = document.getElementById("syncsetup-" + id);
});
let settingids = ["device", "connect", "connected", "disconnect", "sync", "details"];
let settingids = ["device", "connect", "connected", "disconnect", "sync", "details", "pairdevice"];
settingids.forEach(function(id) {
elements[id] = document.getElementById("sync-" + id);
});
@ -397,6 +455,7 @@ let WeaveGlue = {
let device = this._elements.device;
let disconnect = this._elements.disconnect;
let sync = this._elements.sync;
let pairdevice = this._elements.pairdevice;
// Show what went wrong with login if necessary
if (aTopic == "weave:ui:login:error") {
@ -541,3 +600,95 @@ let WeaveGlue = {
this.setupData.serverURL = serverURL;
}
};
const PIN_PART_LENGTH = 4;
let SyncPairDevice = {
jpake: null,
open: function open() {
this.code1.setAttribute("maxlength", PIN_PART_LENGTH);
this.code2.setAttribute("maxlength", PIN_PART_LENGTH);
this.code3.setAttribute("maxlength", PIN_PART_LENGTH);
this.nextFocusEl = {code1: this.code2,
code2: this.code3,
code3: this.connectbutton};
document.getElementById("syncpair-container").hidden = false;
BrowserUI.pushDialog(this);
this.code1.focus();
// Kick off a sync. That way the server will have the most recent data from
// this computer and it will show up immediately on the new device.
Weave.SyncScheduler.scheduleNextSync(0);
},
close: function close() {
this.code1.value = this.code2.value = this.code3.value = "";
this.code1.disabled = this.code2.disabled = this.code3.disabled = false;
this.connectbutton.disabled = true;
if (this.jpake) {
this.jpake.abort();
this.jpake = null;
}
document.getElementById("syncpair-container").hidden = true;
BrowserUI.popDialog();
},
onTextBoxInput: function onTextBoxInput(textbox) {
if (textbox && textbox.value.length == PIN_PART_LENGTH) {
let name = textbox.id.split("-")[1];
this.nextFocusEl[name].focus();
}
this.connectbutton.disabled =
!(this.code1.value.length == PIN_PART_LENGTH &&
this.code2.value.length == PIN_PART_LENGTH &&
this.code3.value.length == PIN_PART_LENGTH);
},
connect: function connect() {
let self = this;
let jpake = this.jpake = new Weave.JPAKEClient({
onPaired: function onPaired() {
let credentials = {account: Weave.Service.account,
password: Weave.Service.password,
synckey: Weave.Service.passphrase,
serverURL: Weave.Service.serverURL};
jpake.sendAndComplete(credentials);
},
onComplete: function onComplete() {
self.jpake = null;
self.close();
// Schedule a Sync for soonish to fetch the data uploaded by the
// device with which we just paired.
Weave.SyncScheduler.scheduleNextSync(Weave.SyncScheduler.activeInterval);
},
onAbort: function onAbort(error) {
self.jpake = null;
// Aborted by user, ignore.
if (error == Weave.JPAKE_ERROR_USERABORT) {
return;
}
self.code1.value = self.code2.value = self.code3.value = "";
self.code1.disabled = self.code2.disabled = self.code3.disabled = false;
self.code1.focus();
}
});
this.code1.disabled = this.code2.disabled = this.code3.disabled = true;
this.connectbutton.disabled = true;
let pin = this.code1.value + this.code2.value + this.code3.value;
let expectDelay = false;
jpake.pairWithPIN(pin, expectDelay);
}
};
["code1", "code2", "code3", "connectbutton"].forEach(function (id) {
XPCOMUtils.defineLazyGetter(SyncPairDevice, id, function() {
return document.getElementById("syncpair-" + id);
});
});

View File

@ -19,4 +19,13 @@
(aboutHome.forAndroid): Second line of a multi-line button. Treat as a subtitle.
-->
<!ENTITY aboutHome.forAndroid "for Android">
<!ENTITY aboutHome.setupSync "Set Up Sync">
<!-- LOCALIZATION NOTE:
(aboutHome.setupSync): This string should match the desktop
equivalent, in particular concerning syncBrand.fullName.label.
-->
<!ENTITY aboutHome.setupSync "Set Up Sync">
<!-- LOCALIZATION NOTE:
(aboutHome.syncPairDevice): This string should match the desktop
equivalent, in particular concerning syncBrand.fullName.label.
-->
<!ENTITY aboutHome.syncPairDevice "Pair a Device">

View File

@ -19,4 +19,9 @@
<!ENTITY sync.setup.connect "Connect">
<!ENTITY sync.setup.cancel "Cancel">
<!ENTITY sync.setup.tutorial "Show me how">
<!ENTITY sync.setup.waiting "Pairing in progress…">
<!ENTITY sync.setup.waiting2 "Waiting for other device…">
<!ENTITY sync.pair.title "Pair a Device">
<!ENTITY sync.pair.description "To activate your new device, select &#x0022;Set Up Sync&#x0022; on the device.">
<!ENTITY sync.setup.close "Close">
<!ENTITY sync.setup.waitingdownload "Your data is now being downloaded in the background. You can close this window at any time.">

View File

@ -267,7 +267,8 @@ body[dir="rtl"] {
text-align: center;
}
#syncSetupSync {
#syncSetupSync,
#syncPairDevice {
text-decoration: underline;
color: blue;
}

View File

@ -1534,7 +1534,11 @@ setting {
}
#syncsetup-waiting {
padding: 10em 0;
padding: 2em 0 0 0;
}
#syncsetup-waiting-top {
padding: 1em;
}
/* content scrollbars */

View File

@ -724,7 +724,7 @@ SyncEngine.prototype = {
// Delete any existing data and reupload on bad version or missing meta.
// No crypto component here...? We could regenerate per-collection keys...
if (needsWipe) {
this.wipeServer(true);
this.wipeServer();
}
// Save objects that need to be uploaded in this._modified. We also save
@ -1248,7 +1248,10 @@ SyncEngine.prototype = {
},
wipeServer: function wipeServer() {
new Resource(this.engineURL).delete();
let response = new Resource(this.engineURL).delete();
if (response.status != 200 && response.status != 404) {
throw response;
}
this._resetClient();
},

View File

@ -22,6 +22,7 @@
* Marina Samuel <msamuel@mozilla.com>
* Philipp von Weitershausen <philipp@weitershausen.de>
* Chenxia Liu <liuche@mozilla.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
@ -55,6 +56,12 @@ Cu.import("resource://services-sync/main.js"); // So we can get to Service fo
let SyncScheduler = {
_log: Log4Moz.repository.getLogger("Sync.SyncScheduler"),
_fatalLoginStatus: [LOGIN_FAILED_NO_USERNAME,
LOGIN_FAILED_NO_PASSWORD,
LOGIN_FAILED_NO_PASSPHRASE,
LOGIN_FAILED_INVALID_PASSPHRASE,
LOGIN_FAILED_LOGIN_REJECTED],
/**
* The nsITimer object that schedules the next sync. See scheduleNextSync().
*/
@ -116,6 +123,7 @@ let SyncScheduler = {
},
observe: function observe(subject, topic, data) {
this._log.trace("Handling " + topic);
switch(topic) {
case "weave:engine:score:updated":
if (Status.login == LOGIN_SUCCEEDED) {
@ -163,7 +171,7 @@ let SyncScheduler = {
}
break;
case "weave:engine:sync:error":
// subject is the exception thrown by an engine's sync() method
// `subject` is the exception thrown by an engine's sync() method.
let exception = subject;
if (exception.status >= 500 && exception.status <= 504) {
this.requiresBackoff = true;
@ -172,12 +180,16 @@ let SyncScheduler = {
case "weave:service:login:error":
this.clearSyncTriggers();
// Try again later, just as if we threw an error... only without the
// error count.
if (Status.login == MASTER_PASSWORD_LOCKED) {
// Try again later, just as if we threw an error... only without the
// error count.
this._log.debug("Couldn't log in: master password is locked.");
this._log.trace("Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL");
this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
} else if (this._fatalLoginStatus.indexOf(Status.login) == -1) {
// Not a fatal login error, just an intermittent network or server
// issue. Keep on syncin'.
this.checkSyncStatus();
}
break;
case "weave:service:logout:finish":
@ -449,6 +461,7 @@ let SyncScheduler = {
* Deal with sync errors appropriately
*/
handleSyncError: function handleSyncError() {
this._log.trace("In handleSyncError. Error count: " + this._syncErrors);
this._syncErrors++;
// Do nothing on the first couple of failures, if we're not in
@ -458,6 +471,8 @@ let SyncScheduler = {
this.scheduleNextSync();
return;
}
this._log.debug("Sync error count has exceeded " +
MAX_ERROR_COUNT_BEFORE_BACKOFF + "; enforcing backoff.");
Status.enforceBackoff = true;
}
@ -469,7 +484,8 @@ let SyncScheduler = {
* Remove any timers/observers that might trigger a sync
*/
clearSyncTriggers: function clearSyncTriggers() {
this._log.debug("Clearing sync triggers.");
this._log.debug("Clearing sync triggers and the global score.");
this.globalScore = this.nextSync = 0;
// Clear out any scheduled syncs
if (this.syncTimer)
@ -521,6 +537,7 @@ let ErrorHandler = {
},
observe: function observe(subject, topic, data) {
this._log.trace("Handling " + topic);
switch(topic) {
case "weave:engine:sync:applied":
if (subject.newFailed) {
@ -569,6 +586,19 @@ let ErrorHandler = {
this.dontIgnoreErrors = false;
break;
case "weave:service:sync:finish":
this._log.trace("Status.service is " + Status.service);
// Check both of these status codes: in the event of a failure in one
// engine, Status.service will be SYNC_FAILED_PARTIAL despite
// Status.sync being SYNC_SUCCEEDED.
// *facepalm*
if (Status.sync == SYNC_SUCCEEDED &&
Status.service == STATUS_OK) {
// Great. Let's clear our mid-sync 401 note.
this._log.trace("Clearing lastSyncReassigned.");
Svc.Prefs.reset("lastSyncReassigned");
}
if (Status.service == SYNC_FAILED_PARTIAL) {
this._log.debug("Some engines did not sync correctly.");
this.resetFileLog(Svc.Prefs.get("log.appender.file.logOnError"),
@ -590,7 +620,12 @@ let ErrorHandler = {
},
notifyOnNextTick: function notifyOnNextTick(topic) {
Utils.nextTick(function() Svc.Obs.notify(topic));
Utils.nextTick(function() {
this._log.trace("Notifying " + topic +
". Status.login is " + Status.login +
". Status.sync is " + Status.sync);
Svc.Obs.notify(topic);
}, this);
},
/**
@ -710,6 +745,7 @@ let ErrorHandler = {
shouldReportError: function shouldReportError() {
if (Status.login == MASTER_PASSWORD_LOCKED) {
this._log.trace("shouldReportError: false (master password locked).");
return false;
}
@ -721,8 +757,18 @@ let ErrorHandler = {
if (lastSync && ((Date.now() - Date.parse(lastSync)) >
Svc.Prefs.get("errorhandler.networkFailureReportTimeout") * 1000)) {
Status.sync = PROLONGED_SYNC_FAILURE;
this._log.trace("shouldReportError: true (prolonged sync failure).");
return true;
}
// We got a 401 mid-sync. Wait for the next sync before actually handling
// an error. This assumes that we'll get a 401 again on a login fetch in
// order to report the error.
if (!Weave.Service.clusterURL) {
this._log.trace("shouldReportError: false (no cluster URL; " +
"possible node reassignment).");
return false;
}
return ([Status.login, Status.sync].indexOf(SERVER_MAINTENANCE) == -1 &&
[Status.login, Status.sync].indexOf(LOGIN_FAILED_NETWORK_ERROR) == -1);
@ -742,7 +788,24 @@ let ErrorHandler = {
case 401:
Weave.Service.logout();
Status.login = LOGIN_FAILED_LOGIN_REJECTED;
this._log.info("Got 401 response; resetting clusterURL.");
Svc.Prefs.reset("clusterURL");
let delay = 0;
if (Svc.Prefs.get("lastSyncReassigned")) {
// We got a 401 in the middle of the previous sync, and we just got
// another. Login must have succeeded in order for us to get here, so
// the password should be correct.
// This is likely to be an intermittent server issue, so back off and
// give it time to recover.
this._log.warn("Last sync also failed for 401. Delaying next sync.");
delay = MINIMUM_BACKOFF_INTERVAL;
} else {
this._log.debug("New mid-sync 401 failure. Making a note.");
Svc.Prefs.set("lastSyncReassigned", true);
}
this._log.info("Attempting to schedule another sync.");
SyncScheduler.scheduleNextSync(delay);
break;
case 500:

View File

@ -270,7 +270,7 @@ AsyncResource.prototype = {
_doRequest: function _doRequest(action, data, callback) {
this._log.trace("In _doRequest.");
this._callback = callback;
let channel = this._channel = this._createRequest();
let channel = this._createRequest();
if ("undefined" != typeof(data))
this._data = data;
@ -303,7 +303,7 @@ AsyncResource.prototype = {
channel.asyncOpen(listener, null);
},
_onComplete: function _onComplete(error, data) {
_onComplete: function _onComplete(error, data, channel) {
this._log.trace("In _onComplete. Error is " + error + ".");
if (error) {
@ -312,7 +312,6 @@ AsyncResource.prototype = {
}
this._data = data;
let channel = this._channel;
let action = channel.requestMethod;
this._log.trace("Channel: " + channel);
@ -539,69 +538,29 @@ ChannelListener.prototype = {
// Clear the abort timer now that the channel is done.
this.abortTimer.clear();
/**
* Shim to help investigate Bug 672878.
* We seem to be seeing a situation in which onStopRequest is being called
* with a success code, but no mResponseHead yet set (a failure condition).
* One possibility, according to bzbarsky, is that the channel status and
* the status argument don't match up. This block will log that situation.
*
* Fetching channel.status should not throw, but if it does requestStatus
* will be NS_ERROR_UNEXPECTED, which will cause this method to report
* failure.
*/
let requestStatus = Cr.NS_ERROR_UNEXPECTED;
let statusSuccess = Components.isSuccessCode(status);
try {
// From nsIRequest.
requestStatus = channel.status;
this._log.trace("Request status is " + requestStatus);
} catch (ex) {
this._log.warn("Got exception " + Utils.exceptionStr(ex) +
" fetching channel.status.");
}
if (statusSuccess && (status != requestStatus)) {
this._log.error("Request status " + requestStatus +
" does not match status arg " + status);
try {
channel.responseStatus;
} catch (ex) {
this._log.error("... and we got " + Utils.exceptionStr(ex) +
" retrieving responseStatus.");
}
}
let requestStatusSuccess = Components.isSuccessCode(requestStatus);
let uri = channel && channel.URI && channel.URI.spec || "<unknown>";
this._log.trace("Channel for " + channel.requestMethod + " " + uri + ": " +
"isSuccessCode(" + status + ")? " + statusSuccess + ", " +
"isSuccessCode(" + requestStatus + ")? " +
requestStatusSuccess);
"isSuccessCode(" + status + ")? " + statusSuccess);
if (this._data == '') {
this._data = null;
}
// Check both the nsIRequest status and the status we were provided.
// If either is unsuccessful, don't take the success branch!
//
// Pass back 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 (!statusSuccess || !requestStatusSuccess) {
// Pick the code that caused us to fail.
let code = statusSuccess ? requestStatus : status;
let message = Components.Exception("", code).name;
let error = Components.Exception(message, code);
this._onComplete(error);
if (!statusSuccess) {
let message = Components.Exception("", status).name;
let error = Components.Exception(message, status);
this._onComplete(error, undefined, channel);
return;
}
this._log.trace("Channel: flags = " + channel.loadFlags +
", URI = " + uri +
", HTTP success? " + channel.requestSucceeded);
this._onComplete(null, this._data);
this._onComplete(null, this._data, channel);
},
onDataAvailable: function Channel_onDataAvail(req, cb, stream, off, count) {

View File

@ -370,6 +370,9 @@ RESTRequest.prototype = {
/*** nsIStreamListener ***/
onStartRequest: function onStartRequest(channel) {
// Update the channel in case we got redirected.
this.channel = channel;
if (this.status == this.ABORTED) {
this._log.trace("Not proceeding with onStartRequest, request was aborted.");
return;
@ -394,6 +397,9 @@ RESTRequest.prototype = {
},
onStopRequest: function onStopRequest(channel, context, statusCode) {
// Update the channel in case we got redirected.
this.channel = channel;
if (this.timeoutTimer) {
// Clear the abort timer now that the channel is done.
this.timeoutTimer.clear();
@ -406,42 +412,7 @@ RESTRequest.prototype = {
}
this.status = this.COMPLETED;
/**
* Shim to help investigate Bug 672878.
* We seem to be seeing a situation in which onStopRequest is being called
* with a success code, but no mResponseHead yet set (a failure condition).
* One possibility, according to bzbarsky, is that the channel status and
* the status argument don't match up. This block will log that situation.
*
* Fetching channel.status should not throw, but if it does requestStatus
* will be NS_ERROR_UNEXPECTED, which will cause this method to report
* failure.
*
* This code parallels nearly identical shimming in resource.js.
*/
let requestStatus = Cr.NS_ERROR_UNEXPECTED;
let statusSuccess = Components.isSuccessCode(statusCode);
try {
// From nsIRequest.
requestStatus = channel.status;
this._log.trace("Request status is " + requestStatus);
} catch (ex) {
this._log.warn("Got exception " + Utils.exceptionStr(ex) +
" fetching channel.status.");
}
if (statusSuccess && (statusCode != requestStatus)) {
this._log.error("Request status " + requestStatus +
" does not match status arg " + statusCode);
try {
channel.responseStatus;
} catch (ex) {
this._log.error("... and we got " + Utils.exceptionStr(ex) +
" retrieving responseStatus.");
}
}
let requestStatusSuccess = Components.isSuccessCode(requestStatus);
let uri = channel && channel.URI && channel.URI.spec || "<unknown>";
this._log.trace("Channel for " + channel.requestMethod + " " + uri +
" returned status code " + statusCode);
@ -449,7 +420,7 @@ RESTRequest.prototype = {
// 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 (!statusSuccess || !requestStatusSuccess) {
if (!statusSuccess) {
let message = Components.Exception("", statusCode).name;
let error = Components.Exception(message, statusCode);
this.onComplete(error);

View File

@ -523,8 +523,10 @@ WeaveSvc.prototype = {
return this.serverURL;
case 0:
case 200:
if (node == "null")
if (node == "null") {
node = null;
}
this._log.trace("_findCluster successfully returning " + node);
return node;
default:
ErrorHandler.checkServerError(node);
@ -702,8 +704,11 @@ WeaveSvc.prototype = {
return true;
}
} catch (e) {
} catch (ex) {
// This means no keys are present, or there's a network error.
this._log.debug("Failed to fetch and verify keys: "
+ Utils.exceptionStr(ex));
ErrorHandler.checkServerError(ex);
return false;
}
},
@ -731,9 +736,9 @@ WeaveSvc.prototype = {
}
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
// 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");
@ -826,6 +831,7 @@ WeaveSvc.prototype = {
let uploadRes = wbo.upload(this.cryptoKeysURL);
if (uploadRes.status != 200) {
this._log.warn("Got status " + uploadRes.status + " uploading new keys. What to do? Throw!");
ErrorHandler.checkServerError(uploadRes);
throw new Error("Unable to upload symmetric keys.");
}
this._log.info("Got status " + uploadRes.status + " uploading keys.");
@ -915,6 +921,7 @@ WeaveSvc.prototype = {
}))(),
startOver: function() {
this._log.trace("Invoking Service.startOver.");
Svc.Obs.notify("weave:engine:stop-tracking");
Status.resetSync();
@ -1367,7 +1374,14 @@ WeaveSvc.prototype = {
}
// Update engines because it might change what we sync.
this._updateEnabledEngines();
try {
this._updateEnabledEngines();
} catch (ex) {
this._log.debug("Updating enabled engines failed: " +
Utils.exceptionStr(ex));
ErrorHandler.checkServerError(ex);
throw ex;
}
try {
for each (let engine in Engines.getEnabled()) {
@ -1480,22 +1494,18 @@ WeaveSvc.prototype = {
_syncEngine: function WeaveSvc__syncEngine(engine) {
try {
engine.sync();
return true;
}
catch(e) {
// Maybe a 401, cluster update perhaps needed?
if (e.status == 401) {
// Log out and clear the cluster URL pref. That will make us perform
// cluster detection and password check on next sync, which handles
// both causes of 401s; in either case, we won't proceed with this
// sync so return false, but kick off a sync for next time.
this.logout();
Svc.Prefs.reset("clusterURL");
Utils.nextTick(this.sync, this);
// Maybe a 401, cluster update perhaps needed?
// We rely on ErrorHandler observing the sync failure notification to
// schedule another sync and clear node assignment values.
// Here we simply want to muffle the exception and return an
// appropriate value.
return false;
}
return true;
}
return true;
},
/**
@ -1555,22 +1565,34 @@ WeaveSvc.prototype = {
CollectionKeys.clear();
this.upgradeSyncKey(this.syncID);
// Wipe the server.
let wipeTimestamp = this.wipeServer();
// Upload a new meta/global record.
let meta = new WBORecord("meta", "global");
meta.payload.syncID = this.syncID;
meta.payload.storageVersion = STORAGE_VERSION;
meta.isNew = true;
this._log.debug("New metadata record: " + JSON.stringify(meta.payload));
let resp = new Resource(this.metaURL).put(meta);
if (!resp.success)
let res = new Resource(this.metaURL);
// It would be good to set the X-If-Unmodified-Since header to `timestamp`
// for this PUT to ensure at least some level of transactionality.
// Unfortunately, the servers don't support it after a wipe right now
// (bug 693893), so we're going to defer this until bug 692700.
let resp = res.put(meta);
if (!resp.success) {
// If we got into a race condition, we'll abort the sync this way, too.
// That's fine. We'll just wait till the next sync. The client that we're
// racing is probably busy uploading stuff right now anyway.
throw resp;
}
Records.set(this.metaURL, meta);
// Wipe everything we know about except meta because we just uploaded it
let collections = [Clients].concat(Engines.getAll()).map(function(engine) {
return engine.name;
});
this.wipeServer(collections);
// Generate, upload, and download new keys. Do this last so we don't wipe
// them...
@ -1581,36 +1603,51 @@ WeaveSvc.prototype = {
* Wipe user data from the server.
*
* @param collections [optional]
* Array of collections to wipe. If not given, all collections are wiped.
* Array of collections to wipe. If not given, all collections are
* wiped by issuing a DELETE request for `storageURL`.
*
* @param includeKeys [optional]
* If true, keys/pubkey and keys/privkey are deleted from the server.
* This is false by default, which will cause the usual upgrade paths
* to leave those keys on the server. This is to solve Bug 614737: old
* clients check for keys *before* checking storage versions.
*
* Note that this parameter only has an effect if `collections` is not
* passed. If you explicitly pass a list of collections, they will be
* processed regardless of the value of `includeKeys`.
* @return the server's timestamp of the (last) DELETE.
*/
wipeServer: function wipeServer(collections, includeKeyPairs)
wipeServer: function wipeServer(collections)
this._notify("wipe-server", "", function() {
let response;
if (!collections) {
collections = [];
let info = new Resource(this.infoURL).get();
for (let name in info.obj) {
if (includeKeyPairs || (name != "keys"))
collections.push(name);
// Strip the trailing slash.
let res = new Resource(this.storageURL.slice(0, -1));
res.setHeader("X-Confirm-Delete", "1");
try {
response = res.delete();
} catch (ex) {
this._log.debug("Failed to wipe server: " + Utils.exceptionStr(ex));
throw ex;
}
if (response.status != 200 && response.status != 404) {
this._log.debug("Aborting wipeServer. Server responded with " +
response.status + " response for " + this.storageURL);
throw response;
}
return response.headers["x-weave-timestamp"];
}
let timestamp;
for each (let name in collections) {
let url = this.storageURL + name;
let response = new Resource(url).delete();
try {
response = new Resource(url).delete();
} catch (ex) {
this._log.debug("Failed to wipe '" + name + "' collection: " +
Utils.exceptionStr(ex));
throw ex;
}
if (response.status != 200 && response.status != 404) {
throw "Aborting wipeServer. Server responded with "
+ response.status + " response for " + url;
this._log.debug("Aborting wipeServer. Server responded with " +
response.status + " response for " + url);
throw response;
}
if ("x-weave-timestamp" in response.headers) {
timestamp = response.headers["x-weave-timestamp"];
}
}
return timestamp;
})(),
/**
@ -1620,7 +1657,7 @@ WeaveSvc.prototype = {
* Array of engine names to wipe. If not given, all engines are used.
*/
wipeClient: function WeaveSvc_wipeClient(engines)
this._catch(this._notify("wipe-client", "", function() {
this._notify("wipe-client", "", function() {
// If we don't have any engines, reset the service and wipe all engines
if (!engines) {
// Clear out any service data
@ -1639,7 +1676,7 @@ WeaveSvc.prototype = {
// Save the password/passphrase just in-case they aren't restored by sync
this.persistLogin();
}))(),
})(),
/**
* Wipe all remote user data by wiping the server then telling each remote
@ -1648,8 +1685,8 @@ WeaveSvc.prototype = {
* @param engines [optional]
* Array of engine names to wipe. If not given, all engines are used.
*/
wipeRemote: function WeaveSvc_wipeRemote(engines)
this._catch(this._notify("wipe-remote", "", function() {
wipeRemote: function wipeRemote(engines) {
try {
// Make sure stuff gets uploaded.
this.resetClient(engines);
@ -1667,7 +1704,11 @@ WeaveSvc.prototype = {
// Make sure the changed clients get updated.
Clients.sync();
}))(),
} catch (ex) {
ErrorHandler.checkServerError(ex);
throw ex;
}
},
/**
* Reset local service information like logs, sync times, caches.
@ -1678,7 +1719,6 @@ WeaveSvc.prototype = {
// Pretend we've never synced to the server and drop cached data
this.syncID = "";
Svc.Prefs.reset("lastSync");
Records.clearCache();
}))(),

View File

@ -1092,6 +1092,9 @@ let Utils = {
// If Master Password is enabled and locked, present a dialog to unlock it.
// Return whether the system is unlocked.
ensureMPUnlocked: function ensureMPUnlocked() {
if (!Utils.mpLocked()) {
return true;
}
let sdr = Cc["@mozilla.org/security/sdr;1"]
.getService(Ci.nsISecretDecoderRing);
try {

View File

@ -17,6 +17,23 @@ let provider = {
};
Services.dirsvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider);
let timer;
function waitForZeroTimer(callback) {
// First wait >100ms (nsITimers can take up to that much time to fire, so
// we can account for the timer in delayedAutoconnect) and then two event
// loop ticks (to account for the Utils.nextTick() in autoConnect).
let ticks = 2;
function wait() {
if (ticks) {
ticks -= 1;
Utils.nextTick(wait);
return;
}
callback();
}
timer = Utils.namedTimer(wait, 150, {}, "timer");
}
btoa = Cu.import("resource://services-sync/log4moz.js").btoa;
function getTestLogger(component) {
return Log4Moz.repository.getLogger("Testing");

View File

@ -1,3 +1,5 @@
const Cm = Components.manager;
// Shared logging for all HTTP server functions.
Cu.import("resource://services-sync/log4moz.js");
const SYNC_HTTP_LOGGER = "Sync.Test.Server";
@ -10,6 +12,17 @@ function new_timestamp() {
return Math.round(Date.now() / 10) / 100;
}
function return_timestamp(request, response, timestamp) {
if (!timestamp) {
timestamp = new_timestamp();
}
let body = "" + timestamp;
response.setHeader("X-Weave-Timestamp", body);
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(body, body.length);
return timestamp;
}
function httpd_setup (handlers) {
let server = new nsHttpServer();
let port = 8080;
@ -608,7 +621,7 @@ SyncServer.prototype = {
* subject to change: see Bug 650435.
*/
timestamp: function timestamp() {
return Math.round(Date.now() / 10) / 100;
return new_timestamp();
},
/**
@ -988,3 +1001,51 @@ function serverForUsers(users, contents, callback) {
server.start();
return server;
}
/**
* Proxy auth helpers.
*/
/**
* Fake a PAC to prompt a channel replacement.
*/
let PACSystemSettings = {
CID: Components.ID("{5645d2c1-d6d8-4091-b117-fe7ee4027db7}"),
contractID: "@mozilla.org/system-proxy-settings;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory,
Ci.nsISystemProxySettings]),
createInstance: function createInstance(outer, iid) {
if (outer) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return this.QueryInterface(iid);
},
lockFactory: function lockFactory(lock) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
// Replace this URI for each test to avoid caching. We want to ensure that
// each test gets a completely fresh setup.
PACURI: null,
getProxyForURI: function getProxyForURI(aURI) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
}
};
function installFakePAC() {
_("Installing fake PAC.");
Cm.nsIComponentRegistrar
.registerFactory(PACSystemSettings.CID,
"Fake system proxy-settings",
PACSystemSettings.contractID,
PACSystemSettings);
}
function uninstallFakePAC() {
_("Uninstalling fake PAC.");
let CID = PACSystemSettings.CID;
Cm.nsIComponentRegistrar.unregisterFactory(CID, PACSystemSettings);
}

View File

@ -20,8 +20,7 @@ const NON_PROLONGED_ERROR_DURATION =
(Svc.Prefs.get('errorhandler.networkFailureReportTimeout') / 2) * 1000;
function setLastSync(lastSyncValue) {
Svc.Prefs.set("lastSync", (new Date(Date.now() -
lastSyncValue)).toString());
Svc.Prefs.set("lastSync", (new Date(Date.now() - lastSyncValue)).toString());
}
function CatapultEngine() {
@ -31,7 +30,9 @@ CatapultEngine.prototype = {
__proto__: SyncEngine.prototype,
exception: null, // tests fill this in
_sync: function _sync() {
throw this.exception;
if (this.exception) {
throw this.exception;
}
}
};
@ -69,7 +70,9 @@ function sync_httpd_setup() {
syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
engines: {clients: {version: Clients.version,
syncID: Clients.syncID}}
syncID: Clients.syncID},
catapult: {version: Engines.get("catapult").version,
syncID: Engines.get("catapult").syncID}}
});
let clientsColl = new ServerCollection({}, true);
@ -79,23 +82,37 @@ function sync_httpd_setup() {
let handler_401 = httpd_handler(401, "Unauthorized");
return httpd_setup({
// Normal server behaviour.
"/1.1/johndoe/storage/meta/global": upd("meta", global.handler()),
"/1.1/johndoe/info/collections": collectionsHelper.handler,
"/1.1/johndoe/storage/crypto/keys":
upd("crypto", (new ServerWBO("keys")).handler()),
"/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()),
// Credentials are wrong or node reallocated.
"/1.1/janedoe/storage/meta/global": handler_401,
"/1.1/janedoe/info/collections": handler_401,
"/maintenance/1.1/johnsmith/info/collections": service_unavailable,
// Maintenance or overloaded (503 + Retry-After) at info/collections.
"/maintenance/1.1/broken.info/info/collections": service_unavailable,
"/maintenance/1.1/janesmith/storage/meta/global": service_unavailable,
"/maintenance/1.1/janesmith/info/collections": collectionsHelper.handler,
// Maintenance or overloaded (503 + Retry-After) at meta/global.
"/maintenance/1.1/broken.meta/storage/meta/global": service_unavailable,
"/maintenance/1.1/broken.meta/info/collections": collectionsHelper.handler,
"/maintenance/1.1/foo/storage/meta/global": upd("meta", global.handler()),
"/maintenance/1.1/foo/info/collections": collectionsHelper.handler,
"/maintenance/1.1/foo/storage/crypto/keys": service_unavailable,
// Maintenance or overloaded (503 + Retry-After) at crypto/keys.
"/maintenance/1.1/broken.keys/storage/meta/global": upd("meta", global.handler()),
"/maintenance/1.1/broken.keys/info/collections": collectionsHelper.handler,
"/maintenance/1.1/broken.keys/storage/crypto/keys": service_unavailable,
// Maintenance or overloaded (503 + Retry-After) at wiping collection.
"/maintenance/1.1/broken.wipe/info/collections": collectionsHelper.handler,
"/maintenance/1.1/broken.wipe/storage/meta/global": upd("meta", global.handler()),
"/maintenance/1.1/broken.wipe/storage/crypto/keys":
upd("crypto", (new ServerWBO("keys")).handler()),
"/maintenance/1.1/broken.wipe/storage": service_unavailable,
"/maintenance/1.1/broken.wipe/storage/clients": upd("clients", clientsColl.handler()),
"/maintenance/1.1/broken.wipe/storage/catapult": service_unavailable
});
}
@ -104,7 +121,10 @@ function setUp() {
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/";
return generateAndUploadKeys();
}
function generateAndUploadKeys() {
generateNewKeys();
let serverKeys = CollectionKeys.asWBO("crypto", "keys");
serverKeys.encrypt(Service.syncKeyBundle);
@ -126,16 +146,34 @@ add_test(function test_401_logout() {
do_check_eq(Status.sync, SYNC_SUCCEEDED);
do_check_true(Service.isLoggedIn);
Svc.Obs.add("weave:service:sync:error", onSyncError);
function onSyncError() {
_("Got weave:service:sync:error in first sync.");
Svc.Obs.remove("weave:service:sync:error", onSyncError);
// Wait for the automatic next sync.
function onLoginError() {
_("Got weave:service:login:error in second sync.");
Svc.Obs.remove("weave:service:login:error", onLoginError);
do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED);
do_check_false(Service.isLoggedIn);
// Clean up.
Utils.nextTick(function () {
Service.startOver();
server.stop(run_next_test);
});
}
Svc.Obs.add("weave:service:login:error", onLoginError);
}
// Make sync fail due to login rejected.
Service.username = "janedoe";
_("Starting first sync.");
Service.sync();
do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED);
do_check_false(Service.isLoggedIn);
// Clean up.
Service.startOver();
server.stop(run_next_test);
_("First sync done.");
});
add_test(function test_credentials_changed_logout() {
@ -178,6 +216,10 @@ add_test(function test_shouldReportError() {
Status.login = MASTER_PASSWORD_LOCKED;
do_check_false(ErrorHandler.shouldReportError());
// Give ourselves a clusterURL so that the temporary 401 no-error situation
// doesn't come into play.
Service.clusterURL = "http://localhost:8080/";
// Test dontIgnoreErrors, non-network, non-prolonged, login error reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
@ -733,9 +775,9 @@ add_test(function test_info_collections_login_server_maintenance_error() {
let server = sync_httpd_setup();
setUp();
Service.username = "broken.info";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "johnsmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -772,9 +814,9 @@ add_test(function test_meta_global_login_server_maintenance_error() {
let server = sync_httpd_setup();
setUp();
Service.username = "broken.meta";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "janesmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -811,8 +853,8 @@ add_test(function test_crypto_keys_login_server_maintenance_error() {
let server = sync_httpd_setup();
setUp();
Service.username = "broken.keys";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "foo";
// Force re-download of keys
CollectionKeys.clear();
@ -878,9 +920,9 @@ add_test(function test_info_collections_login_prolonged_server_maintenance_error
let server = sync_httpd_setup();
setUp();
Service.username = "broken.info";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "johnsmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -910,9 +952,9 @@ add_test(function test_meta_global_login_prolonged_server_maintenance_error(){
let server = sync_httpd_setup();
setUp();
Service.username = "broken.meta";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "janesmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -937,13 +979,13 @@ add_test(function test_meta_global_login_prolonged_server_maintenance_error(){
Service.sync();
});
add_test(function test_crypto_keys_login_prolonged_server_maintenance_error(){
add_test(function test_download_crypto_keys_login_prolonged_server_maintenance_error(){
// Test crypto/keys prolonged server maintenance errors are reported.
let server = sync_httpd_setup();
setUp();
Service.username = "broken.keys";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "foo";
// Force re-download of keys
CollectionKeys.clear();
@ -971,6 +1013,116 @@ add_test(function test_crypto_keys_login_prolonged_server_maintenance_error(){
Service.sync();
});
add_test(function test_upload_crypto_keys_login_prolonged_server_maintenance_error(){
// Test crypto/keys prolonged server maintenance errors are reported.
let server = sync_httpd_setup();
// Start off with an empty account, do not upload a key.
Service.username = "broken.keys";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:login:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
setLastSync(PROLONGED_ERROR_DURATION);
Service.sync();
});
add_test(function test_wipeServer_login_prolonged_server_maintenance_error(){
// Test that we report prolonged server maintenance errors that occur whilst
// wiping the server.
let server = sync_httpd_setup();
// Start off with an empty account, do not upload a key.
Service.username = "broken.wipe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:login:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
setLastSync(PROLONGED_ERROR_DURATION);
Service.sync();
});
add_test(function test_wipeRemote_prolonged_server_maintenance_error(){
// Test that we report prolonged server maintenance errors that occur whilst
// wiping all remote devices.
let server = sync_httpd_setup();
Service.username = "broken.wipe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
generateAndUploadKeys();
let engine = Engines.get("catapult");
engine.exception = null;
engine.enabled = true;
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_eq(Svc.Prefs.get("firstSync"), "wipeRemote");
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
Svc.Prefs.set("firstSync", "wipeRemote");
setLastSync(PROLONGED_ERROR_DURATION);
Service.sync();
});
add_test(function test_sync_syncAndReportErrors_server_maintenance_error() {
// Test server maintenance errors are reported
// when calling syncAndReportErrors.
@ -1004,9 +1156,9 @@ add_test(function test_info_collections_login_syncAndReportErrors_server_mainten
let server = sync_httpd_setup();
setUp();
Service.username = "broken.info";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "johnsmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -1037,9 +1189,9 @@ add_test(function test_meta_global_login_syncAndReportErrors_server_maintenance_
let server = sync_httpd_setup();
setUp();
Service.username = "broken.meta";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "janesmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -1064,14 +1216,14 @@ add_test(function test_meta_global_login_syncAndReportErrors_server_maintenance_
ErrorHandler.syncAndReportErrors();
});
add_test(function test_crypto_keys_login_syncAndReportErrors_server_maintenance_error() {
add_test(function test_download_crypto_keys_login_syncAndReportErrors_server_maintenance_error() {
// Test crypto/keys server maintenance errors are reported
// when calling syncAndReportErrors.
let server = sync_httpd_setup();
setUp();
Service.username = "broken.keys";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "foo";
// Force re-download of keys
CollectionKeys.clear();
@ -1099,6 +1251,117 @@ add_test(function test_crypto_keys_login_syncAndReportErrors_server_maintenance_
ErrorHandler.syncAndReportErrors();
});
add_test(function test_upload_crypto_keys_login_syncAndReportErrors_server_maintenance_error() {
// Test crypto/keys server maintenance errors are reported
// when calling syncAndReportErrors.
let server = sync_httpd_setup();
// Start off with an empty account, do not upload a key.
Service.username = "broken.keys";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:login:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
setLastSync(NON_PROLONGED_ERROR_DURATION);
ErrorHandler.syncAndReportErrors();
});
add_test(function test_wipeServer_login_syncAndReportErrors_server_maintenance_error() {
// Test crypto/keys server maintenance errors are reported
// when calling syncAndReportErrors.
let server = sync_httpd_setup();
// Start off with an empty account, do not upload a key.
Service.username = "broken.wipe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:login:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
setLastSync(NON_PROLONGED_ERROR_DURATION);
ErrorHandler.syncAndReportErrors();
});
add_test(function test_wipeRemote_syncAndReportErrors_server_maintenance_error(){
// Test that we report prolonged server maintenance errors that occur whilst
// wiping all remote devices.
let server = sync_httpd_setup();
Service.username = "broken.wipe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
generateAndUploadKeys();
let engine = Engines.get("catapult");
engine.exception = null;
engine.enabled = true;
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, SERVER_MAINTENANCE);
do_check_eq(Svc.Prefs.get("firstSync"), "wipeRemote");
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
Svc.Prefs.set("firstSync", "wipeRemote");
setLastSync(NON_PROLONGED_ERROR_DURATION);
ErrorHandler.syncAndReportErrors();
});
add_test(function test_sync_syncAndReportErrors_prolonged_server_maintenance_error() {
// Test prolonged server maintenance errors are
// reported when calling syncAndReportErrors.
@ -1132,9 +1395,9 @@ add_test(function test_info_collections_login_syncAndReportErrors_prolonged_serv
let server = sync_httpd_setup();
setUp();
Service.username = "broken.info";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "johnsmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -1165,9 +1428,9 @@ add_test(function test_meta_global_login_syncAndReportErrors_prolonged_server_ma
let server = sync_httpd_setup();
setUp();
Service.username = "broken.meta";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "janesmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -1192,14 +1455,14 @@ add_test(function test_meta_global_login_syncAndReportErrors_prolonged_server_ma
ErrorHandler.syncAndReportErrors();
});
add_test(function test_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() {
add_test(function test_download_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() {
// Test crypto/keys server maintenance errors are reported
// when calling syncAndReportErrors.
let server = sync_httpd_setup();
setUp();
Service.username = "broken.keys";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "foo";
// Force re-download of keys
CollectionKeys.clear();
@ -1227,6 +1490,76 @@ add_test(function test_crypto_keys_login_syncAndReportErrors_prolonged_server_ma
ErrorHandler.syncAndReportErrors();
});
add_test(function test_upload_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() {
// Test crypto/keys server maintenance errors are reported
// when calling syncAndReportErrors.
let server = sync_httpd_setup();
// Start off with an empty account, do not upload a key.
Service.username = "broken.keys";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:login:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
setLastSync(PROLONGED_ERROR_DURATION);
ErrorHandler.syncAndReportErrors();
});
add_test(function test_wipeServer_login_syncAndReportErrors_prolonged_server_maintenance_error() {
// Test crypto/keys server maintenance errors are reported
// when calling syncAndReportErrors.
let server = sync_httpd_setup();
// Start off with an empty account, do not upload a key.
Service.username = "broken.wipe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:login:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
setLastSync(PROLONGED_ERROR_DURATION);
ErrorHandler.syncAndReportErrors();
});
add_test(function test_sync_engine_generic_fail() {
let server = sync_httpd_setup();

View File

@ -0,0 +1,487 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
_("Test that node reassignment responses are respected on all kinds of " +
"requests.");
// Don't sync any engines by default.
Svc.DefaultPrefs.set("registerEngines", "")
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/policies.js");
Cu.import("resource://services-sync/rest.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/status.js");
Cu.import("resource://services-sync/log4moz.js");
function run_test() {
Log4Moz.repository.getLogger("Sync.AsyncResource").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.ErrorHandler").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.Resource").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.RESTRequest").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.SyncScheduler").level = Log4Moz.Level.Trace;
initTestLogging();
Engines.register(RotaryEngine);
// None of the failures in this file should result in a UI error.
function onUIError() {
do_throw("Errors should not be presented in the UI.");
}
Svc.Obs.add("weave:ui:login:error", onUIError);
Svc.Obs.add("weave:ui:sync:error", onUIError);
run_next_test();
}
/**
* Emulate the following Zeus config:
* $draining = data.get($prefix . $host . " draining");
* if ($draining == "drain.") {
* log.warn($log_host_db_status . " migrating=1 (node-reassignment)" .
* $log_suffix);
* http.sendResponse("401 Node reassignment", $content_type,
* '"server request: node reassignment"', "");
* }
*/
const reassignBody = "\"server request: node reassignment\"";
// API-compatible with SyncServer handler. Bind `handler` to something to use
// as a ServerCollection handler.
function handleReassign(handler, req, resp) {
resp.setStatusLine(req.httpVersion, 401, "Node reassignment");
resp.setHeader("Content-Type", "application/json");
resp.bodyOutputStream.write(reassignBody, reassignBody.length);
}
/**
* A node assignment handler.
*/
const newNodeBody = "http://localhost:8080/";
function installNodeHandler(server, next) {
function handleNodeRequest(req, resp) {
_("Client made a request for a node reassignment.");
resp.setStatusLine(req.httpVersion, 200, "OK");
resp.setHeader("Content-Type", "text/plain");
resp.bodyOutputStream.write(newNodeBody, newNodeBody.length);
Utils.nextTick(next);
}
let nodePath = "/user/1.0/johndoe/node/weave";
server.server.registerPathHandler(nodePath, handleNodeRequest);
_("Registered node handler at " + nodePath);
}
function prepareServer() {
Service.username = "johndoe";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.password = "ilovejane";
Service.serverURL = "http://localhost:8080/";
Service.clusterURL = "http://localhost:8080/";
do_check_eq(Service.userAPI, "http://localhost:8080/user/1.0/");
let server = new SyncServer();
server.registerUser("johndoe");
server.start();
return server;
}
function getReassigned() {
try {
return Services.prefs.getBoolPref("services.sync.lastSyncReassigned");
} catch (ex if (ex.result == Cr.NS_ERROR_UNEXPECTED)) {
return false;
} catch (ex) {
do_throw("Got exception retrieving lastSyncReassigned: " +
Utils.exceptionStr(ex));
}
}
/**
* Make a test request to `url`, then watch the result of two syncs
* to ensure that a node request was made.
* Runs `between` between the two. This can be used to undo deliberate failure
* setup, detach observers, etc.
*/
function syncAndExpectNodeReassignment(server, firstNotification, between,
secondNotification, url) {
function onwards() {
let nodeFetched = false;
function onFirstSync() {
_("First sync completed.");
Svc.Obs.remove(firstNotification, onFirstSync);
Svc.Obs.add(secondNotification, onSecondSync);
do_check_eq(Service.clusterURL, "");
// Track whether we fetched node/weave. We want to wait for the second
// sync to finish so that we're cleaned up for the next test, so don't
// run_next_test in the node handler.
nodeFetched = false;
// Verify that the client requests a node reassignment.
// Install a node handler to watch for these requests.
installNodeHandler(server, function () {
nodeFetched = true;
});
// Allow for tests to clean up error conditions.
between();
}
function onSecondSync() {
_("Second sync completed.");
Svc.Obs.remove(secondNotification, onSecondSync);
SyncScheduler.clearSyncTriggers();
// Make absolutely sure that any event listeners are done with their work
// before we proceed.
waitForZeroTimer(function () {
_("Second sync nextTick.");
do_check_true(nodeFetched);
Service.startOver();
server.stop(run_next_test);
});
}
Svc.Obs.add(firstNotification, onFirstSync);
Service.sync();
}
// Make sure that it works!
let request = new RESTRequest(url);
request.get(function () {
do_check_eq(request.response.status, 401);
Utils.nextTick(onwards);
});
}
add_test(function test_momentary_401_engine() {
_("Test a failure for engine URLs that's resolved by reassignment.");
let server = prepareServer();
let john = server.user("johndoe");
_("Enabling the Rotary engine.");
let engine = Engines.get("rotary");
engine.enabled = true;
// We need the server to be correctly set up prior to experimenting. Do this
// through a sync.
let global = {syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
rotary: {version: engine.version,
syncID: engine.syncID}}
john.createCollection("meta").insert("global", global);
_("First sync to prepare server contents.");
Service.sync();
_("Setting up Rotary collection to 401.");
let rotary = john.createCollection("rotary");
let oldHandler = rotary.collectionHandler;
rotary.collectionHandler = handleReassign.bind(this, undefined);
// We want to verify that the clusterURL pref has been cleared after a 401
// inside a sync. Flag the Rotary engine to need syncing.
john.collection("rotary").timestamp += 1000;
function between() {
_("Undoing test changes.");
rotary.collectionHandler = oldHandler;
function onLoginStart() {
// lastSyncReassigned shouldn't be cleared until a sync has succeeded.
_("Ensuring that lastSyncReassigned is still set at next sync start.");
Svc.Obs.remove("weave:service:login:start", onLoginStart);
do_check_true(getReassigned());
}
_("Adding observer that lastSyncReassigned is still set on login.");
Svc.Obs.add("weave:service:login:start", onLoginStart);
}
syncAndExpectNodeReassignment(server,
"weave:service:sync:finish",
between,
"weave:service:sync:finish",
Service.storageURL + "rotary");
});
// This test ends up being a failing fetch *after we're already logged in*.
add_test(function test_momentary_401_info_collections() {
_("Test a failure for info/collections that's resolved by reassignment.");
let server = prepareServer();
_("First sync to prepare server contents.");
Service.sync();
// Return a 401 for info requests, particularly info/collections.
let oldHandler = server.toplevelHandlers.info;
server.toplevelHandlers.info = handleReassign;
function undo() {
_("Undoing test changes.");
server.toplevelHandlers.info = oldHandler;
}
syncAndExpectNodeReassignment(server,
"weave:service:sync:error",
undo,
"weave:service:sync:finish",
Service.infoURL);
});
add_test(function test_momentary_401_storage() {
_("Test a failure for any storage URL, not just engine parts. " +
"Resolved by reassignment.");
let server = prepareServer();
// Return a 401 for all storage requests.
let oldHandler = server.toplevelHandlers.storage;
server.toplevelHandlers.storage = handleReassign;
function undo() {
_("Undoing test changes.");
server.toplevelHandlers.storage = oldHandler;
}
syncAndExpectNodeReassignment(server,
"weave:service:login:error",
undo,
"weave:service:sync:finish",
Service.storageURL + "meta/global");
});
add_test(function test_loop_avoidance_storage() {
_("Test that a repeated failure doesn't result in a sync loop " +
"if node reassignment cannot resolve the failure.");
let server = prepareServer();
// Return a 401 for all storage requests.
let oldHandler = server.toplevelHandlers.storage;
server.toplevelHandlers.storage = handleReassign;
let firstNotification = "weave:service:login:error";
let secondNotification = "weave:service:login:error";
let thirdNotification = "weave:service:sync:finish";
let nodeFetched = false;
// Track the time. We want to make sure the duration between the first and
// second sync is small, and then that the duration between second and third
// is set to be large.
let now;
function onFirstSync() {
_("First sync completed.");
Svc.Obs.remove(firstNotification, onFirstSync);
Svc.Obs.add(secondNotification, onSecondSync);
do_check_eq(Service.clusterURL, "");
// We got a 401 mid-sync, and set the pref accordingly.
do_check_true(Services.prefs.getBoolPref("services.sync.lastSyncReassigned"));
// Track whether we fetched node/weave. We want to wait for the second
// sync to finish so that we're cleaned up for the next test, so don't
// run_next_test in the node handler.
nodeFetched = false;
// Verify that the client requests a node reassignment.
// Install a node handler to watch for these requests.
installNodeHandler(server, function () {
nodeFetched = true;
});
// Update the timestamp.
now = Date.now();
}
function onSecondSync() {
_("Second sync completed.");
Svc.Obs.remove(secondNotification, onSecondSync);
Svc.Obs.add(thirdNotification, onThirdSync);
// This sync occurred within the backoff interval.
let elapsedTime = Date.now() - now;
do_check_true(elapsedTime < MINIMUM_BACKOFF_INTERVAL);
// This pref will be true until a sync completes successfully.
do_check_true(getReassigned());
// The timer will be set for some distant time.
// We store nextSync in prefs, which offers us only limited resolution.
// Include that logic here.
let expectedNextSync = 1000 * Math.floor((now + MINIMUM_BACKOFF_INTERVAL) / 1000);
_("Next sync scheduled for " + SyncScheduler.nextSync);
_("Expected to be slightly greater than " + expectedNextSync);
do_check_true(SyncScheduler.nextSync >= expectedNextSync);
do_check_true(!!SyncScheduler.syncTimer);
// Undo our evil scheme.
server.toplevelHandlers.storage = oldHandler;
// Bring the timer forward to kick off a successful sync, so we can watch
// the pref get cleared.
SyncScheduler.scheduleNextSync(0);
}
function onThirdSync() {
Svc.Obs.remove(thirdNotification, onThirdSync);
// That'll do for now; no more syncs.
SyncScheduler.clearSyncTriggers();
// Make absolutely sure that any event listeners are done with their work
// before we proceed.
waitForZeroTimer(function () {
_("Third sync nextTick.");
do_check_false(getReassigned());
do_check_true(nodeFetched);
Service.startOver();
server.stop(run_next_test);
});
}
Svc.Obs.add(firstNotification, onFirstSync);
now = Date.now();
Service.sync();
});
add_test(function test_loop_avoidance_engine() {
_("Test that a repeated 401 in an engine doesn't result in a sync loop " +
"if node reassignment cannot resolve the failure.");
let server = prepareServer();
let john = server.user("johndoe");
_("Enabling the Rotary engine.");
let engine = Engines.get("rotary");
engine.enabled = true;
// We need the server to be correctly set up prior to experimenting. Do this
// through a sync.
let global = {syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
rotary: {version: engine.version,
syncID: engine.syncID}}
john.createCollection("meta").insert("global", global);
_("First sync to prepare server contents.");
Service.sync();
_("Setting up Rotary collection to 401.");
let rotary = john.createCollection("rotary");
let oldHandler = rotary.collectionHandler;
rotary.collectionHandler = handleReassign.bind(this, undefined);
// Flag the Rotary engine to need syncing.
john.collection("rotary").timestamp += 1000;
function onLoginStart() {
// lastSyncReassigned shouldn't be cleared until a sync has succeeded.
_("Ensuring that lastSyncReassigned is still set at next sync start.");
do_check_true(getReassigned());
}
function beforeSuccessfulSync() {
_("Undoing test changes.");
rotary.collectionHandler = oldHandler;
}
function afterSuccessfulSync() {
Svc.Obs.remove("weave:service:login:start", onLoginStart);
Service.startOver();
server.stop(run_next_test);
}
let firstNotification = "weave:service:sync:finish";
let secondNotification = "weave:service:sync:finish";
let thirdNotification = "weave:service:sync:finish";
let nodeFetched = false;
// Track the time. We want to make sure the duration between the first and
// second sync is small, and then that the duration between second and third
// is set to be large.
let now;
function onFirstSync() {
_("First sync completed.");
Svc.Obs.remove(firstNotification, onFirstSync);
Svc.Obs.add(secondNotification, onSecondSync);
do_check_eq(Service.clusterURL, "");
_("Adding observer that lastSyncReassigned is still set on login.");
Svc.Obs.add("weave:service:login:start", onLoginStart);
// We got a 401 mid-sync, and set the pref accordingly.
do_check_true(Services.prefs.getBoolPref("services.sync.lastSyncReassigned"));
// Track whether we fetched node/weave. We want to wait for the second
// sync to finish so that we're cleaned up for the next test, so don't
// run_next_test in the node handler.
nodeFetched = false;
// Verify that the client requests a node reassignment.
// Install a node handler to watch for these requests.
installNodeHandler(server, function () {
nodeFetched = true;
});
// Update the timestamp.
now = Date.now();
}
function onSecondSync() {
_("Second sync completed.");
Svc.Obs.remove(secondNotification, onSecondSync);
Svc.Obs.add(thirdNotification, onThirdSync);
// This sync occurred within the backoff interval.
let elapsedTime = Date.now() - now;
do_check_true(elapsedTime < MINIMUM_BACKOFF_INTERVAL);
// This pref will be true until a sync completes successfully.
do_check_true(getReassigned());
// The timer will be set for some distant time.
// We store nextSync in prefs, which offers us only limited resolution.
// Include that logic here.
let expectedNextSync = 1000 * Math.floor((now + MINIMUM_BACKOFF_INTERVAL) / 1000);
_("Next sync scheduled for " + SyncScheduler.nextSync);
_("Expected to be slightly greater than " + expectedNextSync);
do_check_true(SyncScheduler.nextSync >= expectedNextSync);
do_check_true(!!SyncScheduler.syncTimer);
// Undo our evil scheme.
beforeSuccessfulSync();
// Bring the timer forward to kick off a successful sync, so we can watch
// the pref get cleared.
SyncScheduler.scheduleNextSync(0);
}
function onThirdSync() {
Svc.Obs.remove(thirdNotification, onThirdSync);
// That'll do for now; no more syncs.
SyncScheduler.clearSyncTriggers();
// Make absolutely sure that any event listeners are done with their work
// before we proceed.
waitForZeroTimer(function () {
_("Third sync nextTick.");
do_check_false(getReassigned());
do_check_true(nodeFetched);
afterSuccessfulSync();
});
}
Svc.Obs.add(firstNotification, onFirstSync);
now = Date.now();
Service.sync();
});

View File

@ -6,9 +6,11 @@ Cu.import("resource://services-sync/util.js");
let logger;
let fetched = false;
function server_open(metadata, response) {
let body;
if (metadata.method == "GET") {
fetched = true;
body = "This path exists";
response.setStatusLine(metadata.httpVersion, 200, "OK");
} else {
@ -40,6 +42,15 @@ function server_404(metadata, response) {
response.bodyOutputStream.write(body, body.length);
}
let pacFetched = false;
function server_pac(metadata, response) {
pacFetched = true;
let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }';
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "application/x-ns-proxy-autoconfig", false);
response.bodyOutputStream.write(body, body.length);
}
let sample_data = {
some: "sample_data",
@ -150,12 +161,26 @@ function run_test() {
"/timestamp": server_timestamp,
"/headers": server_headers,
"/backoff": server_backoff,
"/pac1": server_pac,
"/quota-notice": server_quota_notice,
"/quota-error": server_quota_error
});
Svc.Prefs.set("network.numRetries", 1); // speed up test
// This apparently has to come first in order for our PAC URL to be hit.
// Don't put any other HTTP requests earlier in the file!
_("Testing handling of proxy auth redirection.");
PACSystemSettings.PACURI = "http://localhost:8080/pac1";
installFakePAC();
let proxiedRes = new Resource("http://localhost:8080/open");
let content = proxiedRes.get();
do_check_true(pacFetched);
do_check_true(fetched);
do_check_eq(content, "This path exists");
pacFetched = fetched = false;
uninstallFakePAC();
_("Resource object members");
let res = new Resource("http://localhost:8080/open");
do_check_true(res.uri instanceof Ci.nsIURI);
@ -168,7 +193,7 @@ function run_test() {
do_check_eq(res.data, null);
_("GET a non-password-protected resource");
let content = res.get();
content = res.get();
do_check_eq(content, "This path exists");
do_check_eq(content.status, 200);
do_check_true(content.success);
@ -474,6 +499,5 @@ function run_test() {
let uri2 = Utils.makeURL("http://foo/");
uri2.query = query;
do_check_eq(uri1.query, uri2.query);
server.stop(do_test_finished);
}

View File

@ -6,9 +6,11 @@ Cu.import("resource://services-sync/util.js");
let logger;
let fetched = false;
function server_open(metadata, response) {
let body;
if (metadata.method == "GET") {
fetched = true;
body = "This path exists";
response.setStatusLine(metadata.httpVersion, 200, "OK");
} else {
@ -40,6 +42,15 @@ function server_404(metadata, response) {
response.bodyOutputStream.write(body, body.length);
}
let pacFetched = false;
function server_pac(metadata, response) {
_("Invoked PAC handler.");
pacFetched = true;
let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }';
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "application/x-ns-proxy-autoconfig", false);
response.bodyOutputStream.write(body, body.length);
}
let sample_data = {
some: "sample_data",
@ -154,6 +165,7 @@ function run_test() {
"/timestamp": server_timestamp,
"/headers": server_headers,
"/backoff": server_backoff,
"/pac2": server_pac,
"/quota-notice": server_quota_notice,
"/quota-error": server_quota_error
});
@ -162,9 +174,27 @@ function run_test() {
run_next_test();
}
// This apparently has to come first in order for our PAC URL to be hit.
// Don't put any other HTTP requests earlier in the file!
add_test(function test_proxy_auth_redirect() {
_("Ensure that a proxy auth redirect (which switches out our channel) " +
"doesn't break AsyncResource.");
PACSystemSettings.PACURI = "http://localhost:8080/pac2";
installFakePAC();
let res = new AsyncResource("http://localhost:8080/open");
res.get(function (error, result) {
do_check_true(!error);
do_check_true(pacFetched);
do_check_true(fetched);
do_check_eq("This path exists", result);
pacFetched = fetched = false;
uninstallFakePAC();
run_next_test();
});
});
add_test(function test_members() {
_("Resource object memebers");
_("Resource object members");
let res = new AsyncResource("http://localhost:8080/open");
do_check_true(res.uri instanceof Ci.nsIURI);
do_check_eq(res.uri.spec, "http://localhost:8080/open");

View File

@ -41,6 +41,47 @@ add_test(function test_attributes() {
run_next_test();
});
/**
* Verify that a proxy auth redirect doesn't break us. This has to be the first
* request made in the file!
*/
add_test(function test_proxy_auth_redirect() {
let pacFetched = false;
function pacHandler(metadata, response) {
pacFetched = true;
let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }';
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "application/x-ns-proxy-autoconfig", false);
response.bodyOutputStream.write(body, body.length);
}
let fetched = false;
function original(metadata, response) {
fetched = true;
let body = "TADA!";
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.bodyOutputStream.write(body, body.length);
}
let server = httpd_setup({
"/original": original,
"/pac3": pacHandler
});
PACSystemSettings.PACURI = "http://localhost:8080/pac3";
installFakePAC();
let res = new RESTRequest("http://localhost:8080/original");
res.get(function (error) {
do_check_true(pacFetched);
do_check_true(fetched);
do_check_true(!error);
do_check_true(this.response.success);
do_check_eq("TADA!", this.response.body);
uninstallFakePAC();
server.stop(run_next_test);
});
});
/**
* Demonstrate API short-hand: create a request and dispatch it immediately.
*/

View File

@ -2,8 +2,21 @@ Cu.import("resource://services-sync/main.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/log4moz.js");
function run_test() {
var requestBody;
initTestLogging("Trace");
Log4Moz.repository.getLogger("Sync.AsyncResource").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.Resource").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
run_next_test();
}
add_test(function test_change_password() {
let requestBody;
let server;
function send(statusCode, status, body) {
return function(request, response) {
requestBody = readBytesFromInputStream(request.bodyInputStream);
@ -12,10 +25,7 @@ function run_test() {
};
}
let server;
try {
Weave.Service.serverURL = "http://localhost:8080/";
Weave.Service.username = "johndoe";
Weave.Service.password = "ilovejane";
@ -26,11 +36,10 @@ function run_test() {
do_check_eq(Weave.Service.password, "ilovejane");
_("Let's fire up the server and actually change the password.");
server = httpd_setup({
server = httpd_setup({
"/user/1.0/johndoe/password": send(200, "OK", ""),
"/user/1.0/janedoe/password": send(401, "Unauthorized", "Forbidden!")
});
do_test_pending();
res = Weave.Service.changePassword("ILoveJane83");
do_check_true(res);
@ -60,8 +69,6 @@ function run_test() {
} finally {
Weave.Svc.Prefs.resetBranch("");
Services.logins.removeAllLogins();
if (server) {
server.stop(do_test_finished);
}
server.stop(run_next_test);
}
}
});

View File

@ -51,7 +51,7 @@ function run_test() {
do_check_true(Weave.Service.isLoggedIn);
do_check_eq(Weave.Status.login, Weave.LOGIN_SUCCEEDED);
_("Simulate having changed the password somehwere else.");
_("Simulate having changed the password somewhere else.");
Weave.Service.password = "ilovejosephine";
_("Let's try to sync.");
@ -63,12 +63,18 @@ function run_test() {
_("We're no longer logged in.");
do_check_false(Weave.Service.isLoggedIn);
_("Sync status.");
do_check_eq(Weave.Status.login, Weave.LOGIN_FAILED_LOGIN_REJECTED);
_("Sync status won't have changed yet, because we haven't tried again.");
_("globalScore is reset upon starting a sync.");
do_check_eq(SyncScheduler.globalScore, 0);
_("Our next sync will fail appropriately.");
try {
Weave.Service.sync();
} catch (ex) {
}
do_check_eq(Weave.Status.login, Weave.LOGIN_FAILED_LOGIN_REJECTED);
} finally {
Weave.Svc.Prefs.resetBranch("");
server.stop(do_test_finished);

View File

@ -29,7 +29,28 @@ function run_test() {
let cryptoColl = new ServerCollection({keys: keysWBO});
let metaColl = new ServerCollection({global: meta_global});
do_test_pending();
/**
* Handle the bulk DELETE request sent by wipeServer.
*/
function storageHandler(request, response) {
do_check_eq("DELETE", request.method);
do_check_true(request.hasHeader("X-Confirm-Delete"));
_("Wiping out all collections.");
cryptoColl.delete({});
clients.delete({});
metaColl.delete({});
let ts = new_timestamp();
collectionsHelper.update_collection("crypto", ts);
collectionsHelper.update_collection("clients", ts);
collectionsHelper.update_collection("meta", ts);
return_timestamp(request, response, ts);
}
let server = httpd_setup({
"/1.1/johndoe/storage": storageHandler,
"/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
"/1.1/johndoe/storage/crypto": upd("crypto", cryptoColl.handler()),
"/1.1/johndoe/storage/clients": upd("clients", clients.handler()),

View File

@ -3,9 +3,11 @@ Cu.import("resource://services-sync/engines/clients.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/status.js");
Svc.DefaultPrefs.set("registerEngines", "");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/policies.js");
initTestLogging();
@ -66,13 +68,30 @@ function sync_httpd_setup(handlers) {
function setUp() {
Service.username = "johndoe";
Service.password = "ilovejane";
Service.passphrase = "sekrit";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/";
// So that we can poke at meta/global.
new FakeCryptoService();
// Ensure that the server has valid keys so that logging in will work and not
// result in a server wipe, rendering many of these tests useless.
generateNewKeys();
let serverKeys = CollectionKeys.asWBO("crypto", "keys");
serverKeys.encrypt(Service.syncKeyBundle);
return serverKeys.upload(Service.cryptoKeysURL).success;
}
const PAYLOAD = 42;
function run_test() {
initTestLogging("Trace");
Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.ErrorHandler").level = Log4Moz.Level.Trace;
run_next_test();
}
add_test(function test_newAccount() {
_("Test: New account does not disable locally enabled engines.");
let engine = Engines.get("steam");
@ -89,7 +108,6 @@ add_test(function test_newAccount() {
Service._ignorePrefObserver = false;
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Engine continues to be enabled.");
@ -118,7 +136,6 @@ add_test(function test_enabledLocally() {
engine.enabled = true;
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Meta record now contains the new engine.");
@ -143,6 +160,7 @@ add_test(function test_disabledLocally() {
version: engine.version}}
});
let steamCollection = new ServerWBO("steam", PAYLOAD);
let server = sync_httpd_setup({
"/1.1/johndoe/storage/meta/global": metaWBO.handler(),
"/1.1/johndoe/storage/steam": steamCollection.handler()
@ -157,7 +175,6 @@ add_test(function test_disabledLocally() {
engine.enabled = false;
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Meta record no longer contains engine.");
@ -174,6 +191,50 @@ add_test(function test_disabledLocally() {
}
});
add_test(function test_disabledLocally_wipe503() {
_("Test: Engine is enabled on remote clients and disabled locally");
Service.syncID = "abcdefghij";
let engine = Engines.get("steam");
let metaWBO = new ServerWBO("global", {
syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
engines: {steam: {syncID: engine.syncID,
version: engine.version}}
});
let steamCollection = new ServerWBO("steam", PAYLOAD);
function service_unavailable(request, response) {
let body = "Service Unavailable";
response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
response.setHeader("Retry-After", "23");
response.bodyOutputStream.write(body, body.length);
}
let server = sync_httpd_setup({
"/1.1/johndoe/storage/meta/global": metaWBO.handler(),
"/1.1/johndoe/storage/steam": service_unavailable
});
setUp();
_("Disable engine locally.");
Service._ignorePrefObserver = true;
engine.enabled = true;
Service._ignorePrefObserver = false;
engine.enabled = false;
Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
do_check_eq(Status.sync, SERVER_MAINTENANCE);
Service.startOver();
server.stop(run_next_test);
});
_("Sync.");
ErrorHandler.syncAndReportErrors();
});
add_test(function test_enabledRemotely() {
_("Test: Engine is disabled locally and enabled on a remote client");
Service.syncID = "abcdefghij";
@ -205,7 +266,6 @@ add_test(function test_enabledRemotely() {
do_check_false(engine.enabled);
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Engine is enabled.");
@ -242,7 +302,6 @@ add_test(function test_disabledRemotelyTwoClients() {
Service._ignorePrefObserver = false;
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Disable engine by deleting from meta/global.");
@ -284,7 +343,6 @@ add_test(function test_disabledRemotely() {
Service._ignorePrefObserver = false;
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Engine is not disabled: only one client.");
@ -316,7 +374,6 @@ add_test(function test_dependentEnginesEnabledLocally() {
steamEngine.enabled = true;
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Meta record now contains the new engines.");
@ -348,6 +405,7 @@ add_test(function test_dependentEnginesDisabledLocally() {
let steamCollection = new ServerWBO("steam", PAYLOAD);
let stirlingCollection = new ServerWBO("stirling", PAYLOAD);
let server = sync_httpd_setup({
"/1.1/johndoe/storage/meta/global": metaWBO.handler(),
"/1.1/johndoe/storage/steam": steamCollection.handler(),
@ -365,7 +423,6 @@ add_test(function test_dependentEnginesDisabledLocally() {
do_check_false(stirlingEngine.enabled);
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Meta record no longer contains engines.");
@ -384,7 +441,3 @@ add_test(function test_dependentEnginesDisabledLocally() {
server.stop(run_next_test);
}
});
function run_test() {
run_next_test();
}

View File

@ -13,22 +13,19 @@ FakeCollection.prototype = {
let self = this;
return function(request, response) {
let body = "";
self.timestamp = new_timestamp();
let timestamp = "" + self.timestamp;
if (request.method == "DELETE") {
body = JSON.stringify(Date.now() / 1000);
body = timestamp;
self.deleted = true;
}
response.setHeader("X-Weave-Timestamp", timestamp);
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(body, body.length);
};
}
};
function serviceUnavailable(request, response) {
let body = "Service Unavailable";
response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
response.bodyOutputStream.write(body, body.length);
}
function setUpTestFixtures() {
let cryptoService = new FakeCryptoService();
@ -37,7 +34,13 @@ function setUpTestFixtures() {
Service.passphrase = "aabcdeabcdeabcdeabcdeabcde";
}
function test_withCollectionList_fail() {
function run_test() {
initTestLogging("Trace");
run_next_test();
}
add_test(function test_wipeServer_list_success() {
_("Service.wipeServer() deletes collections given as argument.");
let steam_coll = new FakeCollection();
@ -45,10 +48,42 @@ function test_withCollectionList_fail() {
let server = httpd_setup({
"/1.1/johndoe/storage/steam": steam_coll.handler(),
"/1.1/johndoe/storage/petrol": serviceUnavailable,
"/1.1/johndoe/storage/diesel": diesel_coll.handler(),
"/1.1/johndoe/storage/petrol": httpd_handler(404, "Not Found")
});
try {
setUpTestFixtures();
_("Confirm initial environment.");
do_check_false(steam_coll.deleted);
do_check_false(diesel_coll.deleted);
_("wipeServer() will happily ignore the non-existent collection and use the timestamp of the last DELETE that was successful.");
let timestamp = Service.wipeServer(["steam", "diesel", "petrol"]);
do_check_eq(timestamp, diesel_coll.timestamp);
_("wipeServer stopped deleting after encountering an error with the 'petrol' collection, thus only 'steam' has been deleted.");
do_check_true(steam_coll.deleted);
do_check_true(diesel_coll.deleted);
} finally {
server.stop(run_next_test);
Svc.Prefs.resetBranch("");
}
});
add_test(function test_wipeServer_list_503() {
_("Service.wipeServer() deletes collections given as argument.");
let steam_coll = new FakeCollection();
let diesel_coll = new FakeCollection();
let server = httpd_setup({
"/1.1/johndoe/storage/steam": steam_coll.handler(),
"/1.1/johndoe/storage/petrol": httpd_handler(503, "Service Unavailable"),
"/1.1/johndoe/storage/diesel": diesel_coll.handler()
});
do_test_pending();
try {
setUpTestFixtures();
@ -61,84 +96,126 @@ function test_withCollectionList_fail() {
let error;
try {
Service.wipeServer(["non-existent", "steam", "petrol", "diesel"]);
do_throw("Should have thrown!");
} catch(ex) {
error = ex;
}
_("wipeServer() threw this exception: " + error);
do_check_true(error != undefined);
do_check_eq(error.status, 503);
_("wipeServer stopped deleting after encountering an error with the 'petrol' collection, thus only 'steam' has been deleted.");
do_check_true(steam_coll.deleted);
do_check_false(diesel_coll.deleted);
} finally {
server.stop(do_test_finished);
server.stop(run_next_test);
Svc.Prefs.resetBranch("");
}
}
});
function test_wipeServer_leaves_collections() {
_("Service.wipeServer() deletes everything but keys.");
add_test(function test_wipeServer_all_success() {
_("Service.wipeServer() deletes all the things.");
let steam_coll = new FakeCollection();
let diesel_coll = new FakeCollection();
let keys_coll = new FakeCollection();
function info_collections(request, response) {
let collections = {};
let timestamp = Date.now() / 1000;
if (!steam_coll.deleted)
collections.steam = timestamp
if (!diesel_coll.deleted)
collections.diesel = timestamp;
if (!keys_coll.deleted)
collections.keys = timestamp;
let body = JSON.stringify(collections);
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(body, body.length);
/**
* Handle the bulk DELETE request sent by wipeServer.
*/
let deleted = false;
let serverTimestamp;
function storageHandler(request, response) {
do_check_eq("DELETE", request.method);
do_check_true(request.hasHeader("X-Confirm-Delete"));
deleted = true;
serverTimestamp = return_timestamp(request, response);
}
let server = httpd_setup({
"/1.1/johndoe/storage/steam": steam_coll.handler(),
"/1.1/johndoe/storage/diesel": diesel_coll.handler(),
"/1.1/johndoe/storage/keys": keys_coll.handler(),
"/1.1/johndoe/info/collections": info_collections
"/1.1/johndoe/storage": storageHandler
});
do_test_pending();
setUpTestFixtures();
try {
setUpTestFixtures();
_("Info URL: " + Service.infoURL);
_("Try deletion.");
let returnedTimestamp = Service.wipeServer();
do_check_true(deleted);
do_check_eq(returnedTimestamp, serverTimestamp);
_("Confirm initial environment.");
do_check_false(steam_coll.deleted);
do_check_false(diesel_coll.deleted);
do_check_false(keys_coll.deleted);
_("Collections: " + new Resource(Service.infoURL).get());
_("Try deletion.");
Service.wipeServer();
_("Collections: " + new Resource(Service.infoURL).get());
_("Make sure keys is still present.");
do_check_true(steam_coll.deleted);
do_check_true(diesel_coll.deleted);
do_check_false(keys_coll.deleted);
_("Delete everything.");
Service.wipeServer(null, true);
do_check_true(steam_coll.deleted);
do_check_true(diesel_coll.deleted);
do_check_true(keys_coll.deleted);
} finally {
server.stop(do_test_finished);
Svc.Prefs.resetBranch("");
server.stop(run_next_test);
Svc.Prefs.resetBranch("");
});
add_test(function test_wipeServer_all_404() {
_("Service.wipeServer() accepts a 404.");
/**
* Handle the bulk DELETE request sent by wipeServer. Returns a 404.
*/
let deleted = false;
let serverTimestamp;
function storageHandler(request, response) {
do_check_eq("DELETE", request.method);
do_check_true(request.hasHeader("X-Confirm-Delete"));
deleted = true;
serverTimestamp = new_timestamp();
response.setHeader("X-Weave-Timestamp", "" + serverTimestamp);
response.setStatusLine(request.httpVersion, 404, "Not Found");
}
}
function run_test() {
initTestLogging("Trace");
test_withCollectionList_fail();
test_wipeServer_leaves_collections();
}
let server = httpd_setup({
"/1.1/johndoe/storage": storageHandler
});
setUpTestFixtures();
_("Try deletion.");
let returnedTimestamp = Service.wipeServer();
do_check_true(deleted);
do_check_eq(returnedTimestamp, serverTimestamp);
server.stop(run_next_test);
Svc.Prefs.resetBranch("");
});
add_test(function test_wipeServer_all_503() {
_("Service.wipeServer() throws if it encounters a non-200/404 response.");
/**
* Handle the bulk DELETE request sent by wipeServer. Returns a 503.
*/
function storageHandler(request, response) {
do_check_eq("DELETE", request.method);
do_check_true(request.hasHeader("X-Confirm-Delete"));
response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
}
let server = httpd_setup({
"/1.1/johndoe/storage": storageHandler
});
setUpTestFixtures();
_("Try deletion.");
let error;
try {
Service.wipeServer();
do_throw("Should have thrown!");
} catch (ex) {
error = ex;
}
do_check_eq(error.status, 503);
server.stop(run_next_test);
Svc.Prefs.resetBranch("");
});
add_test(function test_wipeServer_all_connectionRefused() {
_("Service.wipeServer() throws if it encounters a network problem.");
setUpTestFixtures();
_("Try deletion.");
try {
Service.wipeServer();
do_throw("Should have thrown!");
} catch (ex) {
do_check_eq(ex.result, Cr.NS_ERROR_CONNECTION_REFUSED);
}
run_next_test();
Svc.Prefs.resetBranch("");
});

View File

@ -71,23 +71,6 @@ function cleanUpAndGo(server) {
});
}
let timer;
function waitForZeroTimer(callback) {
// First wait >100ms (nsITimers can take up to that much time to fire, so
// we can account for the timer in delayedAutoconnect) and then two event
// loop ticks (to account for the Utils.nextTick() in autoConnect).
let ticks = 2;
function wait() {
if (ticks) {
ticks -= 1;
Utils.nextTick(wait);
return;
}
callback();
}
timer = Utils.namedTimer(wait, 150, {}, "timer");
}
function run_test() {
initTestLogging("Trace");
@ -126,7 +109,7 @@ add_test(function test_prefAttributes() {
do_check_eq(SyncScheduler.syncThreshold, THRESHOLD);
_("'globalScore' corresponds to preference, defaults to zero.");
do_check_eq(Svc.Prefs.get('globalScore'), undefined);
do_check_eq(Svc.Prefs.get('globalScore'), 0);
do_check_eq(SyncScheduler.globalScore, 0);
SyncScheduler.globalScore = SCORE;
do_check_eq(SyncScheduler.globalScore, SCORE);
@ -863,3 +846,80 @@ add_test(function test_sync_503_Retry_After() {
cleanUpAndGo(server);
});
add_test(function test_loginError_recoverable_reschedules() {
_("Verify that a recoverable login error schedules a new sync.");
Service.username = "johndoe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/";
Service.persistLogin();
Status.resetSync(); // reset Status.login
Svc.Obs.add("weave:service:login:error", function onLoginError() {
Svc.Obs.remove("weave:service:login:error", onLoginError);
Utils.nextTick(function aLittleBitAfterLoginError() {
do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
let expectedNextSync = Date.now() + SyncScheduler.syncInterval;
do_check_true(SyncScheduler.nextSync > Date.now());
do_check_true(SyncScheduler.nextSync <= expectedNextSync);
do_check_true(SyncScheduler.syncTimer.delay > 0);
do_check_true(SyncScheduler.syncTimer.delay <= SyncScheduler.syncInterval);
Svc.Obs.remove("weave:service:sync:start", onSyncStart);
cleanUpAndGo();
});
});
// Let's set it up so that a sync is overdue, both in terms of previously
// scheduled syncs and the global score. We still do not expect an immediate
// sync because we just tried (duh).
SyncScheduler.nextSync = Date.now() - 100000;
SyncScheduler.globalScore = SINGLE_USER_THRESHOLD + 1;
function onSyncStart() {
do_throw("Shouldn't have started a sync!");
}
Svc.Obs.add("weave:service:sync:start", onSyncStart);
// Sanity check.
do_check_eq(SyncScheduler.syncTimer, null);
do_check_eq(Status.checkSetup(), STATUS_OK);
do_check_eq(Status.login, LOGIN_SUCCEEDED);
SyncScheduler.scheduleNextSync(0);
});
add_test(function test_loginError_fatal_clearsTriggers() {
_("Verify that a fatal login error clears sync triggers.");
Service.username = "johndoe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/";
Service.persistLogin();
Status.resetSync(); // reset Status.login
let server = httpd_setup({
"/1.1/johndoe/info/collections": httpd_handler(401, "Unauthorized")
});
Svc.Obs.add("weave:service:login:error", function onLoginError() {
Svc.Obs.remove("weave:service:login:error", onLoginError);
Utils.nextTick(function aLittleBitAfterLoginError() {
do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED);
do_check_eq(SyncScheduler.nextSync, 0);
do_check_eq(SyncScheduler.syncTimer, null);
cleanUpAndGo(server);
});
});
// Sanity check.
do_check_eq(SyncScheduler.nextSync, 0);
do_check_eq(SyncScheduler.syncTimer, null);
do_check_eq(Status.checkSetup(), STATUS_OK);
do_check_eq(Status.login, LOGIN_SUCCEEDED);
SyncScheduler.scheduleNextSync(0);
});

View File

@ -48,6 +48,7 @@ skip-if = os == "win" || os == "android"
[test_keys.js]
[test_load_modules.js]
[test_log4moz.js]
[test_node_reassignment.js]
[test_notifications.js]
[test_password_store.js]
[test_password_tracker.js]

View File

@ -126,7 +126,7 @@ var Logger =
var now = new Date()
this.write(now.getFullYear() + "-" + (now.getMonth() < 9 ? '0' : '') +
(now.getMonth() + 1) + "-" +
(now.getDay() < 9 ? '0' : '') + (now.getDay() + 1) + " " +
(now.getDate() < 9 ? '0' : '') + (now.getDate() + 1) + " " +
(now.getHours() < 10 ? '0' : '') + now.getHours() + ":" +
(now.getMinutes() < 10 ? '0' : '') + now.getMinutes() + ":" +
(now.getSeconds() < 10 ? '0' : '') + now.getSeconds() + " " +

View File

@ -140,7 +140,6 @@ var TPS =
Logger.logInfo("sync error; retrying...");
this._syncErrors++;
this._waitingForSync = false;
Weave.Service.logout();
Utils.nextTick(this.RunNextTestAction, this);
}
else if (this._waitingForSync) {
@ -156,7 +155,6 @@ var TPS =
// Wait a second before continuing, otherwise we can get
// 'sync not complete' errors.
Utils.namedTimer(function() {
Weave.Service.logout();
this.FinishAsyncOperation();
}, 1000, this, "postsync");
}

View File

@ -41,7 +41,7 @@ from setuptools import setup, find_packages
version = '0.2.40'
deps = ['pulsebuildmonitor >= 0.2', 'MozillaPulse == .4',
'mozinfo == 0.3.1', 'mozprofile == 0.1a',
'mozinfo == 0.3.1', 'mozprofile == 0.1t',
'mozprocess == 0.1a', 'mozrunner == 3.0a', 'mozregression == 0.3',
'mozautolog >= 0.2.1']

View File

@ -37,7 +37,7 @@
import datetime
def GenerateEmailBody(data, numpassed, numfailed, serverUrl):
def GenerateEmailBody(data, numpassed, numfailed, serverUrl, buildUrl):
now = datetime.datetime.now()
builddate = datetime.datetime.strptime(data['productversion']['buildid'],
@ -46,7 +46,7 @@ def GenerateEmailBody(data, numpassed, numfailed, serverUrl):
row = """
<tr>
<td><a href="http://hg.mozilla.org/services/services-central/file/tip/services/sync/tests/tps/{name}">{name}</a></td>
<td><a href="http://hg.mozilla.org/services/services-central/file/default/services/sync/tests/tps/{name}">{name}</a></td>
<td>{state}</td>
<td>{message}</td>
</tr>
@ -54,7 +54,7 @@ def GenerateEmailBody(data, numpassed, numfailed, serverUrl):
rowWithLog = """
<tr>
<td><a href="http://hg.mozilla.org/services/services-central/services/sync/tests/tps/file/tip/{name}">{name}</a></td>
<td><a href="http://hg.mozilla.org/services/services-central/file/default/services/sync/tests/tps/{name}">{name}</a></td>
<td>{state}</td>
<td>{message} [<a href="{logurl}">view log</a>]</td>
</tr>
@ -72,6 +72,9 @@ def GenerateEmailBody(data, numpassed, numfailed, serverUrl):
state=test['state'],
message=test['message'] if test['message'] else 'None')
firefox_version = data['productversion']['version']
if buildUrl is not None:
firefox_version = "<a href='%s'>%s</a>" % (buildUrl, firefox_version)
body = """
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
@ -169,7 +172,7 @@ def GenerateEmailBody(data, numpassed, numfailed, serverUrl):
</html>
""".format(date=now.ctime(),
firefox_version=data['productversion']['version'],
firefox_version=firefox_version,
firefox_date=builddate.ctime(),
sync_version=data['addonversion']['version'],
sync_type=data['synctype'],

View File

@ -433,7 +433,14 @@ class TPSTestRunner(object):
from tps.emailtemplate import GenerateEmailBody
if body is None:
body = GenerateEmailBody(self.postdata, self.numpassed, self.numfailed, self.config['account']['serverURL'])
buildUrl = None
if self.firefoxRunner and self.firefoxRunner.url:
buildUrl = self.firefoxRunner.url
body = GenerateEmailBody(self.postdata,
self.numpassed,
self.numfailed,
self.config['account']['serverURL'],
self.buildUrl)
subj = "TPS Report: "
if self.numfailed == 0 and self.numpassed > 0: