Bug 1407116 - Sync now enters the "please reconnect" state if Sync is configured but FxA is not. r=eoger

MozReview-Commit-ID: LuAdYkpe0b7

--HG--
extra : rebase_source : 0fb3ca9ef0912e61354df5ca111a169df3777526
This commit is contained in:
Mark Hammond 2017-12-04 15:48:45 +11:00
parent 734061ef82
commit b5876a8b65
3 changed files with 110 additions and 108 deletions

View File

@ -14,6 +14,9 @@ XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function() {
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
"resource://gre/modules/FxAccounts.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UIState",
"resource://services-sync/UIState.jsm");
const FXA_PAGE_LOGGED_OUT = 0;
const FXA_PAGE_LOGGED_IN = 1;
@ -112,27 +115,13 @@ var gSyncPane = {
},
_init() {
let topics = ["weave:service:login:error",
"weave:service:login:finish",
"weave:service:start-over:finish",
"weave:service:setup-complete",
"weave:service:logout:finish",
FxAccountsCommon.ONVERIFIED_NOTIFICATION,
FxAccountsCommon.ONLOGIN_NOTIFICATION,
FxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION,
FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
];
// Add the observers now and remove them on unload
// XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
// of `this`. Fix in a followup. (bug 583347)
topics.forEach(function(topic) {
Weave.Svc.Obs.add(topic, this.updateWeavePrefs, this);
}, this);
Weave.Svc.Obs.add(UIState.ON_UPDATE, this.updateWeavePrefs, this);
window.addEventListener("unload", function() {
topics.forEach(function(topic) {
Weave.Svc.Obs.remove(topic, this.updateWeavePrefs, this);
}, gSyncPane);
window.addEventListener("unload", () => {
Weave.Svc.Obs.remove(UIState.ON_UPDATE, this.updateWeavePrefs, this);
});
XPCOMUtils.defineLazyGetter(this, "_accountsStringBundle", () => {
@ -149,10 +138,6 @@ var gSyncPane = {
document.getElementById("tosPP-small-ToS").setAttribute("href", Weave.Svc.Prefs.get("fxa.termsURL"));
document.getElementById("tosPP-small-PP").setAttribute("href", Weave.Svc.Prefs.get("fxa.privacyURL"));
fxAccounts.promiseAccountsManageURI(this._getEntryPoint()).then(accountsManageURI => {
document.getElementById("verifiedManage").setAttribute("href", accountsManageURI);
});
fxAccounts.promiseAccountsSignUpURI(this._getEntryPoint()).then(signUpURI => {
document.getElementById("noFxaSignUp").setAttribute("href", signUpURI);
});
@ -263,94 +248,67 @@ var gSyncPane = {
// determine the fxa status...
this._showLoadPage(service);
fxAccounts.getSignedInUser().then(data => {
return fxAccounts.hasLocalSession().then(hasLocalSession => {
return [data, hasLocalSession];
});
}).then(([data, hasLocalSession]) => {
if (!data) {
this.page = FXA_PAGE_LOGGED_OUT;
return false;
}
this.page = FXA_PAGE_LOGGED_IN;
// We are logged in locally, but maybe we are in a state where the
// server rejected our credentials (eg, password changed on the server)
let fxaLoginStatus = document.getElementById("fxaLoginStatus");
let syncReady;
// We need to check error states that need a re-authenticate to resolve
// themselves first.
if (!hasLocalSession || Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
fxaLoginStatus.selectedIndex = FXA_LOGIN_FAILED;
syncReady = false;
} else if (!data.verified) {
fxaLoginStatus.selectedIndex = FXA_LOGIN_UNVERIFIED;
syncReady = false;
// So we think we are logged in, so login problems are next.
// (Although if the Sync identity manager is still initializing, we
// ignore login errors and assume all will eventually be good.)
// LOGIN_FAILED_LOGIN_REJECTED explicitly means "you must log back in".
// All other login failures are assumed to be transient and should go
// away by themselves, so aren't reflected here.
} else {
// We must be golden (or in an error state we expect to magically
// resolve itself)
fxaLoginStatus.selectedIndex = FXA_LOGIN_VERIFIED;
syncReady = true;
}
fxaEmailAddressLabels.forEach((label) => {
label.value = data.email;
});
this._populateComputerName(Weave.Service.clientsEngine.localName);
let engines = document.getElementById("fxaSyncEngines");
for (let checkbox of engines.querySelectorAll("checkbox")) {
checkbox.disabled = !syncReady;
}
document.getElementById("fxaChangeDeviceName").disabled = !syncReady;
let state = UIState.get();
if (state.status == UIState.STATUS_NOT_CONFIGURED) {
this.page = FXA_PAGE_LOGGED_OUT;
return;
}
this.page = FXA_PAGE_LOGGED_IN;
// We are logged in locally, but maybe we are in a state where the
// server rejected our credentials (eg, password changed on the server)
let fxaLoginStatus = document.getElementById("fxaLoginStatus");
let syncReady = false; // Is sync able to actually sync?
// We need to check error states that need a re-authenticate to resolve
// themselves first.
if (state.status == UIState.STATUS_LOGIN_FAILED) {
fxaLoginStatus.selectedIndex = FXA_LOGIN_FAILED;
} else if (state.status == UIState.STATUS_NOT_VERIFIED) {
fxaLoginStatus.selectedIndex = FXA_LOGIN_UNVERIFIED;
} else {
// We must be golden (or in an error state we expect to magically
// resolve itself)
fxaLoginStatus.selectedIndex = FXA_LOGIN_VERIFIED;
syncReady = true;
}
fxaEmailAddressLabels.forEach((label) => {
label.value = state.email;
});
this._populateComputerName(Weave.Service.clientsEngine.localName);
let engines = document.getElementById("fxaSyncEngines");
for (let checkbox of engines.querySelectorAll("checkbox")) {
checkbox.disabled = !syncReady;
}
document.getElementById("fxaChangeDeviceName").disabled = !syncReady;
// Clear the profile image (if any) of the previously logged in account.
document.querySelector("#fxaLoginVerified > .fxaProfileImage").style.removeProperty("list-style-image");
// Clear the profile image (if any) of the previously logged in account.
document.querySelector("#fxaLoginVerified > .fxaProfileImage").style.removeProperty("list-style-image");
// If the account is verified the next promise in the chain will
// fetch profile data.
return data.verified;
}).then(isVerified => {
if (isVerified) {
return fxAccounts.getSignedInUserProfile();
}
return null;
}).then(data => {
let fxaLoginStatus = document.getElementById("fxaLoginStatus");
if (data) {
if (data.displayName) {
fxaLoginStatus.setAttribute("hasName", true);
displayNameLabel.hidden = false;
displayNameLabel.textContent = data.displayName;
} else {
fxaLoginStatus.removeAttribute("hasName");
if (state.displayName) {
fxaLoginStatus.setAttribute("hasName", true);
displayNameLabel.hidden = false;
displayNameLabel.textContent = state.displayName;
} else {
fxaLoginStatus.removeAttribute("hasName");
}
if (state.avatarURL) {
let bgImage = "url(\"" + state.avatarURL + "\")";
let profileImageElement = document.querySelector("#fxaLoginVerified > .fxaProfileImage");
profileImageElement.style.listStyleImage = bgImage;
let img = new Image();
img.onerror = () => {
// Clear the image if it has trouble loading. Since this callback is asynchronous
// we check to make sure the image is still the same before we clear it.
if (profileImageElement.style.listStyleImage === bgImage) {
profileImageElement.style.removeProperty("list-style-image");
}
if (data.avatar) {
let bgImage = "url(\"" + data.avatar + "\")";
let profileImageElement = document.querySelector("#fxaLoginVerified > .fxaProfileImage");
profileImageElement.style.listStyleImage = bgImage;
let img = new Image();
img.onerror = () => {
// Clear the image if it has trouble loading. Since this callback is asynchronous
// we check to make sure the image is still the same before we clear it.
if (profileImageElement.style.listStyleImage === bgImage) {
profileImageElement.style.removeProperty("list-style-image");
}
};
img.src = data.avatar;
}
} else {
fxaLoginStatus.removeAttribute("hasName");
}
}, err => {
FxAccountsCommon.log.error(err);
}).catch(err => {
// If we get here something's really busted
Cu.reportError(String(err));
};
img.src = state.avatarURL;
}
// The "manage account" link embeds the uid, so we need to update this
// if the account state changes.
fxAccounts.promiseAccountsManageURI(this._getEntryPoint()).then(accountsManageURI => {
document.getElementById("verifiedManage").setAttribute("href", accountsManageURI);
});
},
@ -385,7 +343,13 @@ var gSyncPane = {
},
async reSignIn() {
const url = await fxAccounts.promiseAccountsForceSigninURI(this._getEntryPoint());
// There's a bit of an edge-case here - we might be forcing reauth when we've
// lost the FxA account data - in which case we'll not get a URL as the re-auth
// URL embeds account info and the server endpoint complains if we don't
// supply it - So we just use the regular "sign in" URL in that case.
let entryPoint = this._getEntryPoint();
const url = (await fxAccounts.promiseAccountsForceSigninURI(entryPoint)) ||
(await fxAccounts.promiseAccountsSignInURI(entryPoint));
this.replaceTabWithUrl(url);
},

View File

@ -144,7 +144,17 @@ const UIStateInternal = {
async _populateWithUserData(state, userData) {
let status;
if (!userData) {
status = STATUS_NOT_CONFIGURED;
// If Sync thinks it is configured but there's no FxA user, then we
// want to enter the "login failed" state so the user can get
// reconfigured.
let syncUserName = Services.prefs.getStringPref("services.sync.username", "");
if (syncUserName) {
state.email = syncUserName;
status = STATUS_LOGIN_FAILED;
} else {
// everyone agrees nothing is configured.
status = STATUS_NOT_CONFIGURED;
}
} else {
let loginFailed = await this._loginFailed();
if (loginFailed) {

View File

@ -69,6 +69,34 @@ add_task(async function test_refreshState_signedin() {
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_refreshState_syncButNoFxA() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
const now = new Date().toString();
Services.prefs.setStringPref("services.sync.lastSync", now);
Services.prefs.setStringPref("services.sync.username", "test@test.com");
UIStateInternal.syncing = false;
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve(null),
};
let state = await UIState.refresh();
equal(state.status, UIState.STATUS_LOGIN_FAILED);
equal(state.email, "test@test.com");
equal(state.displayName, undefined);
equal(state.avatarURL, undefined);
equal(state.lastSync, undefined); // only set when STATUS_SIGNED_IN.
equal(state.syncing, false);
UIStateInternal.fxAccounts = fxAccountsOrig;
Services.prefs.clearUserPref("services.sync.lastSync");
Services.prefs.clearUserPref("services.sync.username");
});
add_task(async function test_refreshState_signedin_profile_unavailable() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;