mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1205928 - update the Sync migration code to automatically and unconditionally disconnect from legacy Sync. r=adw
This commit is contained in:
parent
ec9a517995
commit
cb7625912e
@ -10,11 +10,6 @@ var gFxAccounts = {
|
||||
|
||||
_initialized: false,
|
||||
_inCustomizationMode: false,
|
||||
// _expectingNotifyClose is a hack that helps us determine if the
|
||||
// migration notification was closed due to being "dismissed" vs closed
|
||||
// due to one of the migration buttons being clicked. It's ugly and somewhat
|
||||
// fragile, so bug 1119020 exists to help us do this better.
|
||||
_expectingNotifyClose: false,
|
||||
|
||||
get weave() {
|
||||
delete this.weave;
|
||||
@ -36,7 +31,6 @@ var gFxAccounts = {
|
||||
this.FxAccountsCommon.ONLOGIN_NOTIFICATION,
|
||||
this.FxAccountsCommon.ONVERIFIED_NOTIFICATION,
|
||||
this.FxAccountsCommon.ONLOGOUT_NOTIFICATION,
|
||||
"weave:notification:removed",
|
||||
this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
|
||||
];
|
||||
},
|
||||
@ -107,10 +101,6 @@ var gFxAccounts = {
|
||||
gNavToolbox.addEventListener("customizationstarting", this);
|
||||
gNavToolbox.addEventListener("customizationending", this);
|
||||
|
||||
// Request the current Legacy-Sync-to-FxA migration status. We'll be
|
||||
// notified of fxa-migration:state-changed in response if necessary.
|
||||
Services.obs.notifyObservers(null, "fxa-migration:state-request", null);
|
||||
|
||||
EnsureFxAccountsWebChannel();
|
||||
this._initialized = true;
|
||||
|
||||
@ -140,16 +130,6 @@ var gFxAccounts = {
|
||||
case "fxa-migration:state-changed":
|
||||
this.onMigrationStateChanged(data, subject);
|
||||
break;
|
||||
case "weave:notification:removed":
|
||||
// this exists just so we can tell the difference between "box was
|
||||
// closed due to button press" vs "was closed due to click on [x]"
|
||||
let notif = subject.wrappedJSObject.object;
|
||||
if (notif.title == this.SYNC_MIGRATION_NOTIFICATION_TITLE &&
|
||||
!this._expectingNotifyClose) {
|
||||
// it's an [x] on our notification, so record telemetry.
|
||||
this.fxaMigrator.recordTelemetry(this.fxaMigrator.TELEMETRY_DECLINED);
|
||||
}
|
||||
break;
|
||||
case this.FxAccountsCommon.ONPROFILE_IMAGE_CHANGE_NOTIFICATION:
|
||||
this.updateUI();
|
||||
break;
|
||||
@ -176,12 +156,25 @@ var gFxAccounts = {
|
||||
}
|
||||
},
|
||||
|
||||
onMigrationStateChanged: function (newState, email) {
|
||||
this._migrationInfo = !newState ? null : {
|
||||
state: newState,
|
||||
email: email ? email.QueryInterface(Ci.nsISupportsString).data : null,
|
||||
};
|
||||
this.updateUI();
|
||||
onMigrationStateChanged: function () {
|
||||
// Since we nuked most of the migration code, this notification will fire
|
||||
// once after legacy Sync has been disconnected (and should never fire
|
||||
// again)
|
||||
let msg = this.strings.GetStringFromName("autoDisconnectDescription")
|
||||
let signInLabel = this.strings.GetStringFromName("autoDisconnectSignIn.label");
|
||||
let signInAccessKey = this.strings.GetStringFromName("autoDisconnectSignIn.accessKey");
|
||||
let learnMoreLink = this.fxaMigrator.learnMoreLink;
|
||||
let note = new Weave.Notification(
|
||||
undefined, msg, undefined, Weave.Notifications.PRIORITY_WARNING, [
|
||||
new Weave.NotificationButton(signInLabel, signInAccessKey, () => {
|
||||
this.openPreferences();
|
||||
}),
|
||||
], learnMoreLink
|
||||
);
|
||||
note.title = this.SYNC_MIGRATION_NOTIFICATION_TITLE;
|
||||
Weave.Notifications.replaceTitle(note);
|
||||
// ensure the hamburger menu reflects the newly disconnected state.
|
||||
this.updateAppMenuItem();
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
@ -215,17 +208,15 @@ var gFxAccounts = {
|
||||
},
|
||||
|
||||
updateUI: function () {
|
||||
// It's possible someone signed in to FxA after seeing our notification
|
||||
// about "Legacy Sync migration" (which now is actually "Legacy Sync
|
||||
// auto-disconnect") so kill that notification if it still exists.
|
||||
Weave.Notifications.removeAll(this.SYNC_MIGRATION_NOTIFICATION_TITLE);
|
||||
this.updateAppMenuItem();
|
||||
this.updateMigrationNotification();
|
||||
},
|
||||
|
||||
// Note that updateAppMenuItem() returns a Promise that's only used by tests.
|
||||
updateAppMenuItem: function () {
|
||||
if (this._migrationInfo) {
|
||||
this.updateAppMenuItemForMigration();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let profileInfoEnabled = false;
|
||||
try {
|
||||
profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled");
|
||||
@ -233,12 +224,6 @@ var gFxAccounts = {
|
||||
|
||||
// Bail out if FxA is disabled.
|
||||
if (!this.weave.fxAccountsEnabled) {
|
||||
// When migration transitions from needs-verification to the null state,
|
||||
// fxAccountsEnabled is false because migration has not yet finished. In
|
||||
// that case, hide the button. We'll get another notification with a null
|
||||
// state once migration is complete.
|
||||
this.panelUIFooter.hidden = true;
|
||||
this.panelUIFooter.removeAttribute("fxastatus");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@ -358,90 +343,6 @@ var gFxAccounts = {
|
||||
});
|
||||
},
|
||||
|
||||
updateAppMenuItemForMigration: Task.async(function* () {
|
||||
let status = null;
|
||||
let label = null;
|
||||
switch (this._migrationInfo.state) {
|
||||
case this.fxaMigrator.STATE_USER_FXA:
|
||||
status = "migrate-signup";
|
||||
label = this.strings.formatStringFromName("needUserShort",
|
||||
[this.panelUILabel.getAttribute("fxabrandname")], 1);
|
||||
break;
|
||||
case this.fxaMigrator.STATE_USER_FXA_VERIFIED:
|
||||
status = "migrate-verify";
|
||||
label = this.strings.formatStringFromName("needVerifiedUserShort",
|
||||
[this._migrationInfo.email],
|
||||
1);
|
||||
break;
|
||||
}
|
||||
this.panelUILabel.label = label;
|
||||
this.panelUIFooter.setAttribute("fxastatus", status);
|
||||
}),
|
||||
|
||||
updateMigrationNotification: Task.async(function* () {
|
||||
if (!this._migrationInfo) {
|
||||
this._expectingNotifyClose = true;
|
||||
Weave.Notifications.removeAll(this.SYNC_MIGRATION_NOTIFICATION_TITLE);
|
||||
// because this is called even when there is no such notification, we
|
||||
// set _expectingNotifyClose back to false as we may yet create a new
|
||||
// notification (but in general, once we've created a migration
|
||||
// notification once in a session, we don't create one again)
|
||||
this._expectingNotifyClose = false;
|
||||
return;
|
||||
}
|
||||
let note = null;
|
||||
switch (this._migrationInfo.state) {
|
||||
case this.fxaMigrator.STATE_USER_FXA: {
|
||||
// There are 2 cases here - no email address means it is an offer on
|
||||
// the first device (so the user is prompted to create an account).
|
||||
// If there is an email address it is the "join the party" flow, so the
|
||||
// user is prompted to sign in with the address they previously used.
|
||||
let msg, upgradeLabel, upgradeAccessKey, learnMoreLink;
|
||||
if (this._migrationInfo.email) {
|
||||
msg = this.strings.formatStringFromName("signInAfterUpgradeOnOtherDevice.description",
|
||||
[this._migrationInfo.email],
|
||||
1);
|
||||
upgradeLabel = this.strings.GetStringFromName("signInAfterUpgradeOnOtherDevice.label");
|
||||
upgradeAccessKey = this.strings.GetStringFromName("signInAfterUpgradeOnOtherDevice.accessKey");
|
||||
} else {
|
||||
msg = this.strings.GetStringFromName("needUserLong");
|
||||
upgradeLabel = this.strings.GetStringFromName("upgradeToFxA.label");
|
||||
upgradeAccessKey = this.strings.GetStringFromName("upgradeToFxA.accessKey");
|
||||
learnMoreLink = this.fxaMigrator.learnMoreLink;
|
||||
}
|
||||
note = new Weave.Notification(
|
||||
undefined, msg, undefined, Weave.Notifications.PRIORITY_WARNING, [
|
||||
new Weave.NotificationButton(upgradeLabel, upgradeAccessKey, () => {
|
||||
this._expectingNotifyClose = true;
|
||||
this.fxaMigrator.createFxAccount(window);
|
||||
}),
|
||||
], learnMoreLink
|
||||
);
|
||||
break;
|
||||
}
|
||||
case this.fxaMigrator.STATE_USER_FXA_VERIFIED: {
|
||||
let msg =
|
||||
this.strings.formatStringFromName("needVerifiedUserLong",
|
||||
[this._migrationInfo.email], 1);
|
||||
let resendLabel =
|
||||
this.strings.GetStringFromName("resendVerificationEmail.label");
|
||||
let resendAccessKey =
|
||||
this.strings.GetStringFromName("resendVerificationEmail.accessKey");
|
||||
note = new Weave.Notification(
|
||||
undefined, msg, undefined, Weave.Notifications.PRIORITY_INFO, [
|
||||
new Weave.NotificationButton(resendLabel, resendAccessKey, () => {
|
||||
this._expectingNotifyClose = true;
|
||||
this.fxaMigrator.resendVerificationMail();
|
||||
}),
|
||||
]
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
note.title = this.SYNC_MIGRATION_NOTIFICATION_TITLE;
|
||||
Weave.Notifications.replaceTitle(note);
|
||||
}),
|
||||
|
||||
onMenuPanelCommand: function () {
|
||||
|
||||
switch (this.panelUIFooter.getAttribute("fxastatus")) {
|
||||
@ -455,12 +356,6 @@ var gFxAccounts = {
|
||||
this.openSignInAgainPage("menupanel");
|
||||
}
|
||||
break;
|
||||
case "migrate-signup":
|
||||
case "migrate-verify":
|
||||
// The migration flow calls for the menu item to open sync prefs rather
|
||||
// than requesting migration start immediately.
|
||||
this.openPreferences();
|
||||
break;
|
||||
default:
|
||||
this.openPreferences();
|
||||
break;
|
||||
|
@ -12,9 +12,6 @@ XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
|
||||
"resource://gre/modules/FxAccounts.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "fxaMigrator",
|
||||
"resource://services-sync/FxaMigrator.jsm");
|
||||
|
||||
const PAGE_NO_ACCOUNT = 0;
|
||||
const PAGE_HAS_ACCOUNT = 1;
|
||||
const PAGE_NEEDS_UPDATE = 2;
|
||||
@ -122,22 +119,17 @@ var gSyncPane = {
|
||||
FxAccountsCommon.ONLOGIN_NOTIFICATION,
|
||||
FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
|
||||
];
|
||||
let migrateTopic = "fxa-migration:state-changed";
|
||||
|
||||
// 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);
|
||||
// The FxA migration observer is a special case.
|
||||
Weave.Svc.Obs.add(migrateTopic, this.updateMigrationState, this);
|
||||
|
||||
window.addEventListener("unload", function() {
|
||||
topics.forEach(function (topic) {
|
||||
Weave.Svc.Obs.remove(topic, this.updateWeavePrefs, this);
|
||||
}, gSyncPane);
|
||||
Weave.Svc.Obs.remove(migrateTopic, gSyncPane.updateMigrationState, gSyncPane);
|
||||
}, false);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, '_stringBundle', () => {
|
||||
@ -194,7 +186,6 @@ var gSyncPane = {
|
||||
if (learnMoreLink) {
|
||||
learnMoreLink.parentNode.removeChild(learnMoreLink);
|
||||
}
|
||||
document.getElementById("sync-migration-buttons-deck").hidden = true;
|
||||
},
|
||||
|
||||
_setupEventListeners: function() {
|
||||
@ -292,20 +283,6 @@ var gSyncPane = {
|
||||
});
|
||||
setEventListener("tosPP-small-ToS", "click", gSyncPane.openToS);
|
||||
setEventListener("tosPP-small-PP", "click", gSyncPane.openPrivacyPolicy);
|
||||
setEventListener("sync-migrate-upgrade", "click", function () {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
fxaMigrator.createFxAccount(win);
|
||||
});
|
||||
setEventListener("sync-migrate-unlink", "click", function () {
|
||||
gSyncPane.startOverMigration();
|
||||
});
|
||||
setEventListener("sync-migrate-forget", "click", function () {
|
||||
fxaMigrator.forgetFxAccount();
|
||||
});
|
||||
setEventListener("sync-migrate-resend", "click", function () {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
fxaMigrator.resendVerificationMail(win);
|
||||
});
|
||||
setEventListener("fxaSyncComputerName", "keypress", function (e) {
|
||||
if (e.keyCode == KeyEvent.DOM_VK_RETURN) {
|
||||
document.getElementById("fxaSaveChangeDeviceName").click();
|
||||
@ -324,12 +301,6 @@ var gSyncPane = {
|
||||
},
|
||||
|
||||
updateWeavePrefs: function () {
|
||||
// ask the migration module to broadcast its current state (and nothing will
|
||||
// happen if it's not loaded - which is good, as that means no migration
|
||||
// is pending/necessary) - we don't want to suck that module in just to
|
||||
// find there's nothing to do.
|
||||
Services.obs.notifyObservers(null, "fxa-migration:state-request", null);
|
||||
|
||||
let service = Components.classes["@mozilla.org/weave/service;1"]
|
||||
.getService(Components.interfaces.nsISupports)
|
||||
.wrappedJSObject;
|
||||
@ -448,83 +419,6 @@ var gSyncPane = {
|
||||
}
|
||||
},
|
||||
|
||||
updateMigrationState: function(subject, state) {
|
||||
this._closeSyncStatusMessageBox();
|
||||
let selIndex;
|
||||
let sb = this._accountsStringBundle;
|
||||
switch (state) {
|
||||
case fxaMigrator.STATE_USER_FXA: {
|
||||
// There are 2 cases here - no email address means it is an offer on
|
||||
// the first device (so the user is prompted to create an account).
|
||||
// If there is an email address it is the "join the party" flow, so the
|
||||
// user is prompted to sign in with the address they previously used.
|
||||
let email = subject ? subject.QueryInterface(Components.interfaces.nsISupportsString).data : null;
|
||||
let elt = document.getElementById("syncStatusMessageDescription");
|
||||
elt.textContent = email ?
|
||||
sb.formatStringFromName("signInAfterUpgradeOnOtherDevice.description",
|
||||
[email], 1) :
|
||||
sb.GetStringFromName("needUserLong");
|
||||
|
||||
// The "Learn more" link.
|
||||
if (!email) {
|
||||
let learnMoreLink = document.createElement("label");
|
||||
learnMoreLink.id = "learnMoreLink";
|
||||
learnMoreLink.className = "text-link";
|
||||
let { text, href } = fxaMigrator.learnMoreLink;
|
||||
learnMoreLink.setAttribute("value", text);
|
||||
learnMoreLink.href = href;
|
||||
elt.parentNode.insertBefore(learnMoreLink, elt.nextSibling);
|
||||
}
|
||||
|
||||
// The "upgrade" button.
|
||||
let button = document.getElementById("sync-migrate-upgrade");
|
||||
button.setAttribute("label",
|
||||
sb.GetStringFromName(email
|
||||
? "signInAfterUpgradeOnOtherDevice.label"
|
||||
: "upgradeToFxA.label"));
|
||||
button.setAttribute("accesskey",
|
||||
sb.GetStringFromName(email
|
||||
? "signInAfterUpgradeOnOtherDevice.accessKey"
|
||||
: "upgradeToFxA.accessKey"));
|
||||
// The "unlink" button - this is only shown for first migration
|
||||
button = document.getElementById("sync-migrate-unlink");
|
||||
if (email) {
|
||||
button.hidden = true;
|
||||
} else {
|
||||
button.setAttribute("label", sb.GetStringFromName("unlinkMigration.label"));
|
||||
button.setAttribute("accesskey", sb.GetStringFromName("unlinkMigration.accessKey"));
|
||||
}
|
||||
selIndex = 0;
|
||||
break;
|
||||
}
|
||||
case fxaMigrator.STATE_USER_FXA_VERIFIED: {
|
||||
let sb = this._accountsStringBundle;
|
||||
let email = subject.QueryInterface(Components.interfaces.nsISupportsString).data;
|
||||
let label = sb.formatStringFromName("needVerifiedUserLong", [email], 1);
|
||||
let elt = document.getElementById("syncStatusMessageDescription");
|
||||
elt.setAttribute("value", label);
|
||||
// The "resend" button.
|
||||
let button = document.getElementById("sync-migrate-resend");
|
||||
button.setAttribute("label", sb.GetStringFromName("resendVerificationEmail.label"));
|
||||
button.setAttribute("accesskey", sb.GetStringFromName("resendVerificationEmail.accessKey"));
|
||||
// The "forget" button.
|
||||
button = document.getElementById("sync-migrate-forget");
|
||||
button.setAttribute("label", sb.GetStringFromName("forgetMigration.label"));
|
||||
button.setAttribute("accesskey", sb.GetStringFromName("forgetMigration.accessKey"));
|
||||
selIndex = 1;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (state) { // |null| is expected, but everything else is not.
|
||||
Cu.reportError("updateMigrationState has unknown state: " + state);
|
||||
}
|
||||
document.getElementById("sync-migration").hidden = true;
|
||||
return;
|
||||
}
|
||||
document.getElementById("sync-migration-buttons-deck").selectedIndex = selIndex;
|
||||
document.getElementById("syncStatusMessage").setAttribute("message-type", "migration");
|
||||
},
|
||||
|
||||
// Called whenever one of the sync engine preferences is changed.
|
||||
onPreferenceChanged: function() {
|
||||
let prefElts = document.querySelectorAll("#syncEnginePrefs > preference");
|
||||
@ -560,30 +454,6 @@ var gSyncPane = {
|
||||
this.updateWeavePrefs();
|
||||
},
|
||||
|
||||
// When the "Unlink" button in the migration header is selected we display
|
||||
// a slightly different message.
|
||||
startOverMigration: function () {
|
||||
let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
|
||||
Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL +
|
||||
Services.prompt.BUTTON_POS_1_DEFAULT;
|
||||
let sb = this._accountsStringBundle;
|
||||
let buttonChoice =
|
||||
Services.prompt.confirmEx(window,
|
||||
sb.GetStringFromName("unlinkVerificationTitle"),
|
||||
sb.GetStringFromName("unlinkVerificationDescription"),
|
||||
flags,
|
||||
sb.GetStringFromName("unlinkVerificationConfirm"),
|
||||
null, null, null, {});
|
||||
|
||||
// If the user selects cancel, just bail
|
||||
if (buttonChoice == 1)
|
||||
return;
|
||||
|
||||
fxaMigrator.recordTelemetry(fxaMigrator.TELEMETRY_UNLINKED);
|
||||
Weave.Service.startOver();
|
||||
this.updateWeavePrefs();
|
||||
},
|
||||
|
||||
updatePass: function () {
|
||||
if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED)
|
||||
gSyncUtils.changePassword();
|
||||
|
@ -43,18 +43,6 @@
|
||||
<vbox id="syncStatusMessageWrapper">
|
||||
<label id="syncStatusMessageTitle"></label>
|
||||
<description id="syncStatusMessageDescription"></description>
|
||||
<deck id="sync-migration-buttons-deck">
|
||||
<!-- When we are in the "need FxA user" state -->
|
||||
<hbox>
|
||||
<button id="sync-migrate-unlink"/>
|
||||
<button id="sync-migrate-upgrade"/>
|
||||
</hbox>
|
||||
<!-- When we are in the "need the user to be verified" state -->
|
||||
<hbox>
|
||||
<button id="sync-migrate-forget"/>
|
||||
<button id="sync-migrate-resend"/>
|
||||
</hbox>
|
||||
</deck>
|
||||
</vbox>
|
||||
<vbox>
|
||||
<button id="syncStatusMessageClose" class="close-icon"/>
|
||||
|
@ -2,54 +2,16 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# LOCALIZATION NOTE (needUserShort)
|
||||
# %S = Firefox Accounts brand name from syncBrand.dtd
|
||||
needUserShort = %S required for sync
|
||||
needUserLong = We've rebuilt Sync to make it easier for everyone. Please upgrade to a Firefox Account to continue syncing.
|
||||
# autoDisconnectDescription is shown in an info bar when we detect an old
|
||||
# Sync is being used.
|
||||
autoDisconnectDescription = We've rebuilt Sync to make it easier for everyone.
|
||||
|
||||
upgradeToFxA.label = Upgrade
|
||||
upgradeToFxA.accessKey = U
|
||||
|
||||
# LOCALIZATION NOTE (signInAfterUpgradeOnOtherDevice.description)
|
||||
# %S = Email address of user's Firefox Account
|
||||
signInAfterUpgradeOnOtherDevice.description = Sync was upgraded on another device by %S. Resume syncing?
|
||||
signInAfterUpgradeOnOtherDevice.label = Sign In
|
||||
signInAfterUpgradeOnOtherDevice.accessKey = S
|
||||
|
||||
# LOCALIZATION NOTE (needVerifiedUserShort, needVerifiedUserLong)
|
||||
# %S = Email address of user's Firefox Account
|
||||
needVerifiedUserShort = %S not verified
|
||||
needVerifiedUserLong = Please click the verification link in the email sent to %S
|
||||
|
||||
resendVerificationEmail.label = Resend
|
||||
resendVerificationEmail.accessKey = R
|
||||
# autoDisconnectSignIn.label and .accessKey are for buttons when we auto-disconnect
|
||||
autoDisconnectSignIn.label = Sign in to Sync
|
||||
autoDisconnectSignIn.accessKey = S
|
||||
|
||||
# LOCALIZATION NOTE (reconnectDescription) - %S = Email address of user's Firefox Account
|
||||
reconnectDescription = Reconnect %S
|
||||
|
||||
# LOCALIZATION NOTE (verifyDescription) - %S = Email address of user's Firefox Account
|
||||
verifyDescription = Verify %S
|
||||
|
||||
forgetMigration.label = Forget
|
||||
forgetMigration.accessKey = F
|
||||
|
||||
unlinkMigration.label = Unlink Sync
|
||||
unlinkMigration.accessKey = L
|
||||
|
||||
unlinkVerificationTitle = Unlink old version of Sync?
|
||||
unlinkVerificationDescription = If you no longer want to be reminded about upgrading Sync, you can unlink your old Sync account to remove it.
|
||||
unlinkVerificationConfirm = Unlink
|
||||
|
||||
# These strings are used in a dialog we display after the user requests we resend
|
||||
# a verification email.
|
||||
verificationSentTitle = Verification Sent
|
||||
# LOCALIZATION NOTE (verificationSentHeading) - %S = Email address of user's Firefox Account
|
||||
verificationSentHeading = A verification link has been sent to %S
|
||||
verificationSentDescription = Please check your email and click the link to begin syncing.
|
||||
# LOCALIZATION NOTE (verificationSentFull) - %S = Email address of user's Firefox Account
|
||||
verificationSentFull = A verification link has been sent to %S. Please check your email and click the link to begin syncing.
|
||||
|
||||
verificationNotSentTitle = Unable to Send Verification
|
||||
verificationNotSentHeading = We are unable to send a verification mail at this time
|
||||
verificationNotSentDescription = Please try again later.
|
||||
verificationNotSentFull = We are unable to send a verification mail at this time, please try again later.
|
||||
|
@ -4,15 +4,15 @@
|
||||
|
||||
"use strict;"
|
||||
|
||||
// Note that this module used to supervise the step-by-step migration from
|
||||
// a legacy Sync account to a FxA-based Sync account. In bug 1205928, this
|
||||
// changed to automatically disconnect the legacy Sync account.
|
||||
|
||||
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
|
||||
"resource://gre/modules/FxAccounts.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "WeaveService", function() {
|
||||
return Cc["@mozilla.org/weave/service;1"]
|
||||
@ -23,47 +23,13 @@ XPCOMUtils.defineLazyGetter(this, "WeaveService", function() {
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Weave",
|
||||
"resource://services-sync/main.js");
|
||||
|
||||
// FxAccountsCommon.js doesn't use a "namespace", so create one here.
|
||||
var fxAccountsCommon = {};
|
||||
Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
|
||||
|
||||
// We send this notification whenever the "user" migration state changes.
|
||||
// We send this notification when we perform the disconnection. The browser
|
||||
// window will show a one-off notification bar.
|
||||
const OBSERVER_STATE_CHANGE_TOPIC = "fxa-migration:state-changed";
|
||||
// We also send the state notification when we *receive* this. This allows
|
||||
// consumers to avoid loading this module until it receives a notification
|
||||
// from us (which may never happen if there's no migration to do)
|
||||
const OBSERVER_STATE_REQUEST_TOPIC = "fxa-migration:state-request";
|
||||
|
||||
// We send this notification whenever the migration is paused waiting for
|
||||
// something internal to complete.
|
||||
const OBSERVER_INTERNAL_STATE_CHANGE_TOPIC = "fxa-migration:internal-state-changed";
|
||||
|
||||
// We use this notification so Sync's healthreport module can record telemetry
|
||||
// (actually via "health report") for us.
|
||||
const OBSERVER_INTERNAL_TELEMETRY_TOPIC = "fxa-migration:internal-telemetry";
|
||||
|
||||
const OBSERVER_TOPICS = [
|
||||
"xpcom-shutdown",
|
||||
"weave:service:sync:start",
|
||||
"weave:service:sync:finish",
|
||||
"weave:service:sync:error",
|
||||
"weave:eol",
|
||||
OBSERVER_STATE_REQUEST_TOPIC,
|
||||
fxAccountsCommon.ONLOGIN_NOTIFICATION,
|
||||
fxAccountsCommon.ONLOGOUT_NOTIFICATION,
|
||||
fxAccountsCommon.ONVERIFIED_NOTIFICATION,
|
||||
];
|
||||
|
||||
// A list of preference names we write to the migration sentinel. We only
|
||||
// write ones that have a user-set value.
|
||||
const FXA_SENTINEL_PREFS = [
|
||||
"identity.fxaccounts.auth.uri",
|
||||
"identity.fxaccounts.remote.force_auth.uri",
|
||||
"identity.fxaccounts.remote.signup.uri",
|
||||
"identity.fxaccounts.remote.signin.uri",
|
||||
"identity.fxaccounts.settings.uri",
|
||||
// Note that "identity.sync.tokenserver.uri" and "services.sync.tokenServerURI"
|
||||
// have special handing when writing/reading prefs.
|
||||
];
|
||||
|
||||
function Migrator() {
|
||||
@ -72,42 +38,14 @@ function Migrator() {
|
||||
// prefs are set.
|
||||
this.log.level = Log.Level.Debug;
|
||||
|
||||
this._nextUserStatePromise = Promise.resolve();
|
||||
|
||||
for (let topic of OBSERVER_TOPICS) {
|
||||
Services.obs.addObserver(this, topic, false);
|
||||
}
|
||||
// ._state is an optimization so we avoid sending redundant observer
|
||||
// notifications when the state hasn't actually changed.
|
||||
this._state = null;
|
||||
}
|
||||
|
||||
Migrator.prototype = {
|
||||
log: Log.repository.getLogger("Sync.SyncMigration"),
|
||||
|
||||
// What user action is necessary to push the migration forward?
|
||||
// A |null| state means there is nothing to do. Note that a null state implies
|
||||
// either. (a) no migration is necessary or (b) that the migrator module is
|
||||
// waiting for something outside of the user's control - eg, sync to complete,
|
||||
// the migration sentinel to be uploaded, etc. In most cases the wait will be
|
||||
// short, but edge cases (eg, no network, sync bugs that prevent it stopping
|
||||
// until shutdown) may require a significantly longer wait.
|
||||
STATE_USER_FXA: "waiting for user to be signed in to FxA",
|
||||
STATE_USER_FXA_VERIFIED: "waiting for a verified FxA user",
|
||||
|
||||
// What internal state are we at? This is primarily used for FHR reporting so
|
||||
// we can determine why exactly we might be stalled.
|
||||
STATE_INTERNAL_WAITING_SYNC_COMPLETE: "waiting for sync to complete",
|
||||
STATE_INTERNAL_WAITING_WRITE_SENTINEL: "waiting for sentinel to be written",
|
||||
STATE_INTERNAL_WAITING_START_OVER: "waiting for sync to reset itself",
|
||||
STATE_INTERNAL_COMPLETE: "migration complete",
|
||||
|
||||
// Flags for the telemetry we record. The UI will call a helper to record
|
||||
// the fact some UI was interacted with.
|
||||
TELEMETRY_ACCEPTED: "accepted",
|
||||
TELEMETRY_DECLINED: "declined",
|
||||
TELEMETRY_UNLINKED: "unlinked",
|
||||
|
||||
finalize() {
|
||||
for (let topic of OBSERVER_TOPICS) {
|
||||
Services.obs.removeObserver(this, topic);
|
||||
@ -121,428 +59,23 @@ Migrator.prototype = {
|
||||
this.finalize();
|
||||
break;
|
||||
|
||||
case OBSERVER_STATE_REQUEST_TOPIC:
|
||||
// someone has requested the state - send it.
|
||||
this._queueCurrentUserState(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
// some other observer that may affect our state has fired, so update.
|
||||
this._queueCurrentUserState().then(
|
||||
() => this.log.debug("update state from observer " + topic + " complete")
|
||||
).catch(err => {
|
||||
let msg = "Failed to handle topic " + topic + ": " + err;
|
||||
Cu.reportError(msg);
|
||||
this.log.error(msg);
|
||||
});
|
||||
}
|
||||
},
|
||||
// this notification when configured with legacy Sync means we want to
|
||||
// disconnect
|
||||
if (!WeaveService.fxAccountsEnabled) {
|
||||
this.log.info("Disconnecting from legacy Sync");
|
||||
// Set up an observer for when the disconnection is complete.
|
||||
let observe;
|
||||
Services.obs.addObserver(observe = () => {
|
||||
this.log.info("observed that startOver is complete");
|
||||
Services.obs.removeObserver(observe, "weave:service:start-over:finish");
|
||||
// Send the notification for the UI.
|
||||
Services.obs.notifyObservers(null, OBSERVER_STATE_CHANGE_TOPIC, null);
|
||||
}, "weave:service:start-over:finish", false);
|
||||
|
||||
// Try and move to a state where we are blocked on a user action.
|
||||
// This needs to be restartable, and the states may, in edge-cases, end
|
||||
// up going backwards (eg, user logs out while we are waiting to be told
|
||||
// about verification)
|
||||
// This is called by our observer notifications - so if there is already
|
||||
// a promise in-flight, it's possible we will miss something important - so
|
||||
// we wait for the in-flight one to complete then fire another (ie, this
|
||||
// is effectively a queue of promises)
|
||||
_queueCurrentUserState(forceObserver = false) {
|
||||
return this._nextUserStatePromise = this._nextUserStatePromise.then(
|
||||
() => this._promiseCurrentUserState(forceObserver),
|
||||
err => {
|
||||
let msg = "Failed to determine the current user state: " + err;
|
||||
Cu.reportError(msg);
|
||||
this.log.error(msg);
|
||||
return this._promiseCurrentUserState(forceObserver)
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
_promiseCurrentUserState: Task.async(function* (forceObserver) {
|
||||
this.log.trace("starting _promiseCurrentUserState");
|
||||
let update = (newState, email=null) => {
|
||||
this.log.info("Migration state: '${state}' => '${newState}'",
|
||||
{state: this._state, newState: newState});
|
||||
if (forceObserver || newState !== this._state) {
|
||||
this._state = newState;
|
||||
let subject = Cc["@mozilla.org/supports-string;1"]
|
||||
.createInstance(Ci.nsISupportsString);
|
||||
subject.data = email || "";
|
||||
Services.obs.notifyObservers(subject, OBSERVER_STATE_CHANGE_TOPIC, newState);
|
||||
}
|
||||
return newState;
|
||||
}
|
||||
|
||||
// If we have no sync user, or are already using an FxA account we must
|
||||
// be done.
|
||||
if (WeaveService.fxAccountsEnabled) {
|
||||
// should not be necessary, but if we somehow ended up with FxA enabled
|
||||
// and sync blocked it would be bad - so better safe than sorry.
|
||||
this.log.debug("FxA enabled - there's nothing to do!")
|
||||
this._unblockSync();
|
||||
return update(null);
|
||||
}
|
||||
|
||||
// so we need to migrate - let's see how far along we are.
|
||||
// If sync isn't in EOL mode, then we are still waiting for the server
|
||||
// to offer the migration process - so no user action necessary.
|
||||
let isEOL = false;
|
||||
try {
|
||||
isEOL = !!Services.prefs.getCharPref("services.sync.errorhandler.alert.mode");
|
||||
} catch (e) {}
|
||||
|
||||
if (!isEOL) {
|
||||
return update(null);
|
||||
}
|
||||
|
||||
// So we are in EOL mode - have we a user?
|
||||
let fxauser = yield fxAccounts.getSignedInUser();
|
||||
if (!fxauser) {
|
||||
// See if there is a migration sentinel so we can send the email
|
||||
// address that was used on a different device for this account (ie, if
|
||||
// this is a "join the party" migration rather than the first)
|
||||
let sentinel = yield this._getSyncMigrationSentinel();
|
||||
return update(this.STATE_USER_FXA, sentinel && sentinel.email);
|
||||
}
|
||||
if (!fxauser.verified) {
|
||||
return update(this.STATE_USER_FXA_VERIFIED, fxauser.email);
|
||||
}
|
||||
|
||||
// So we just have housekeeping to do - we aren't blocked on a user, so
|
||||
// reflect that.
|
||||
this.log.info("No next user state - doing some housekeeping");
|
||||
update(null);
|
||||
|
||||
// We need to disable sync from automatically starting,
|
||||
// and if we are currently syncing wait for it to complete.
|
||||
this._blockSync();
|
||||
|
||||
// Are we currently syncing?
|
||||
if (Weave.Service._locked) {
|
||||
// our observers will kick us further along when complete.
|
||||
this.log.info("waiting for sync to complete")
|
||||
Services.obs.notifyObservers(null, OBSERVER_INTERNAL_STATE_CHANGE_TOPIC,
|
||||
this.STATE_INTERNAL_WAITING_SYNC_COMPLETE);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Write the migration sentinel if necessary.
|
||||
Services.obs.notifyObservers(null, OBSERVER_INTERNAL_STATE_CHANGE_TOPIC,
|
||||
this.STATE_INTERNAL_WAITING_WRITE_SENTINEL);
|
||||
yield this._setMigrationSentinelIfNecessary();
|
||||
|
||||
// Get the list of enabled engines to we can restore that state.
|
||||
let enginePrefs = this._getEngineEnabledPrefs();
|
||||
|
||||
// Must be ready to perform the actual migration.
|
||||
this.log.info("Performing final sync migration steps");
|
||||
// Do the actual migration. We setup one observer for when the new identity
|
||||
// is about to be initialized so we can reset some key preferences - but
|
||||
// there's no promise associated with this.
|
||||
let observeStartOverIdentity;
|
||||
Services.obs.addObserver(observeStartOverIdentity = () => {
|
||||
this.log.info("observed that startOver is about to re-initialize the identity");
|
||||
Services.obs.removeObserver(observeStartOverIdentity, "weave:service:start-over:init-identity");
|
||||
// We've now reset all sync prefs - set the engine related prefs back to
|
||||
// what they were.
|
||||
for (let [prefName, prefType, prefVal] of enginePrefs) {
|
||||
this.log.debug("Restoring pref ${prefName} (type=${prefType}) to ${prefVal}",
|
||||
{prefName, prefType, prefVal});
|
||||
switch (prefType) {
|
||||
case Services.prefs.PREF_BOOL:
|
||||
Services.prefs.setBoolPref(prefName, prefVal);
|
||||
break;
|
||||
case Services.prefs.PREF_STRING:
|
||||
Services.prefs.setCharPref(prefName, prefVal);
|
||||
break;
|
||||
default:
|
||||
// _getEngineEnabledPrefs doesn't return any other type...
|
||||
Cu.reportError("unknown engine pref type for " + prefName + ": " + prefType);
|
||||
// Do the disconnection.
|
||||
Weave.Service.startOver();
|
||||
}
|
||||
}
|
||||
}, "weave:service:start-over:init-identity", false);
|
||||
|
||||
// And another observer for the startOver being fully complete - the only
|
||||
// reason for this is so we can wait until everything is fully reset.
|
||||
let startOverComplete = new Promise((resolve, reject) => {
|
||||
let observe;
|
||||
Services.obs.addObserver(observe = () => {
|
||||
this.log.info("observed that startOver is complete");
|
||||
Services.obs.removeObserver(observe, "weave:service:start-over:finish");
|
||||
resolve();
|
||||
}, "weave:service:start-over:finish", false);
|
||||
});
|
||||
|
||||
Weave.Service.startOver();
|
||||
// need to wait for an observer.
|
||||
Services.obs.notifyObservers(null, OBSERVER_INTERNAL_STATE_CHANGE_TOPIC,
|
||||
this.STATE_INTERNAL_WAITING_START_OVER);
|
||||
yield startOverComplete;
|
||||
// observer fired, now kick things off with the FxA user.
|
||||
this.log.info("scheduling initial FxA sync.");
|
||||
// Note we technically don't need to unblockSync as by now all sync prefs
|
||||
// have been reset - but it doesn't hurt.
|
||||
this._unblockSync();
|
||||
Weave.Service.scheduler.scheduleNextSync(0);
|
||||
|
||||
// Tell the front end that migration is now complete -- Sync is now
|
||||
// configured with an FxA user.
|
||||
forceObserver = true;
|
||||
this.log.info("Migration complete");
|
||||
update(null);
|
||||
|
||||
Services.obs.notifyObservers(null, OBSERVER_INTERNAL_STATE_CHANGE_TOPIC,
|
||||
this.STATE_INTERNAL_COMPLETE);
|
||||
return null;
|
||||
}),
|
||||
|
||||
/* Return an object with the preferences we care about */
|
||||
_getSentinelPrefs() {
|
||||
let result = {};
|
||||
for (let pref of FXA_SENTINEL_PREFS) {
|
||||
if (Services.prefs.prefHasUserValue(pref)) {
|
||||
result[pref] = Services.prefs.getCharPref(pref);
|
||||
}
|
||||
}
|
||||
// We used to use services.sync.tokenServerURI as the tokenServer pref but
|
||||
// have since changed to identity.sync.tokenserver.uri. However, clients
|
||||
// using this pref may not have updated - so always write whatever value
|
||||
// is actually being used to the "old" name.
|
||||
let tokenServerValue;
|
||||
for (let pref of ["services.sync.tokenServerURI", "identity.sync.tokenserver.uri"]) {
|
||||
if (Services.prefs.prefHasUserValue(pref)) {
|
||||
tokenServerValue = Services.prefs.getCharPref(pref);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tokenServerValue) {
|
||||
result["services.sync.tokenServerURI"] = tokenServerValue;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/* Apply any preferences we've obtained from the sentinel */
|
||||
_applySentinelPrefs(savedPrefs) {
|
||||
for (let pref of FXA_SENTINEL_PREFS) {
|
||||
if (savedPrefs[pref]) {
|
||||
Services.prefs.setCharPref(pref, savedPrefs[pref]);
|
||||
}
|
||||
}
|
||||
// And special handling for the tokenserver prefs.
|
||||
let tokenServerValue = savedPrefs["services.sync.tokenServerURI"];
|
||||
if (tokenServerValue) {
|
||||
Services.prefs.setCharPref("identity.sync.tokenserver.uri", tokenServerValue);
|
||||
}
|
||||
},
|
||||
|
||||
/* Ask sync to upload the migration sentinel */
|
||||
_setSyncMigrationSentinel: Task.async(function* () {
|
||||
yield WeaveService.whenLoaded();
|
||||
let signedInUser = yield fxAccounts.getSignedInUser();
|
||||
let sentinel = {
|
||||
email: signedInUser.email,
|
||||
uid: signedInUser.uid,
|
||||
verified: signedInUser.verified,
|
||||
prefs: this._getSentinelPrefs(),
|
||||
};
|
||||
yield Weave.Service.setFxAMigrationSentinel(sentinel);
|
||||
}),
|
||||
|
||||
/* Ask sync to upload the migration sentinal if we (or any other linked device)
|
||||
haven't previously written one.
|
||||
*/
|
||||
_setMigrationSentinelIfNecessary: Task.async(function* () {
|
||||
if (!(yield this._getSyncMigrationSentinel())) {
|
||||
this.log.info("writing the migration sentinel");
|
||||
yield this._setSyncMigrationSentinel();
|
||||
}
|
||||
}),
|
||||
|
||||
/* Ask sync to return a migration sentinel if one exists, otherwise return null */
|
||||
_getSyncMigrationSentinel: Task.async(function* () {
|
||||
yield WeaveService.whenLoaded();
|
||||
let sentinel = yield Weave.Service.getFxAMigrationSentinel();
|
||||
this.log.debug("got migration sentinel ${}", sentinel);
|
||||
return sentinel;
|
||||
}),
|
||||
|
||||
_getDefaultAccountName: Task.async(function* (sentinel) {
|
||||
// Requires looking to see if other devices have written a migration
|
||||
// sentinel (eg, see _haveSynchedMigrationSentinel), and if not, see if
|
||||
// the legacy account name appears to be a valid email address (via the
|
||||
// services.sync.account pref), otherwise return null.
|
||||
// NOTE: Sync does all this synchronously via nested event loops, but we
|
||||
// expose a promise to make future migration to an async-sync easier.
|
||||
if (sentinel && sentinel.email) {
|
||||
this.log.info("defaultAccountName found via sentinel: ${}", sentinel.email);
|
||||
return sentinel.email;
|
||||
}
|
||||
// No previous migrations, so check the existing account name.
|
||||
let account = Weave.Service.identity.account;
|
||||
if (account && account.contains("@")) {
|
||||
this.log.info("defaultAccountName found via legacy account name: {}", account);
|
||||
return account;
|
||||
}
|
||||
this.log.info("defaultAccountName could not find an account");
|
||||
return null;
|
||||
}),
|
||||
|
||||
// Prevent sync from automatically starting
|
||||
_blockSync() {
|
||||
Weave.Service.scheduler.blockSync();
|
||||
},
|
||||
|
||||
_unblockSync() {
|
||||
Weave.Service.scheduler.unblockSync();
|
||||
},
|
||||
|
||||
/* Return a list of [prefName, prefType, prefVal] for all engine related
|
||||
preferences.
|
||||
*/
|
||||
_getEngineEnabledPrefs() {
|
||||
let result = [];
|
||||
for (let engine of Weave.Service.engineManager.getAll()) {
|
||||
let prefName = "services.sync.engine." + engine.prefName;
|
||||
let prefVal;
|
||||
try {
|
||||
prefVal = Services.prefs.getBoolPref(prefName);
|
||||
result.push([prefName, Services.prefs.PREF_BOOL, prefVal]);
|
||||
} catch (ex) {} /* just skip this pref */
|
||||
}
|
||||
// and the declined list.
|
||||
try {
|
||||
let prefName = "services.sync.declinedEngines";
|
||||
let prefVal = Services.prefs.getCharPref(prefName);
|
||||
result.push([prefName, Services.prefs.PREF_STRING, prefVal]);
|
||||
} catch (ex) {}
|
||||
return result;
|
||||
},
|
||||
|
||||
/* return true if all engines are enabled, false otherwise. */
|
||||
_allEnginesEnabled() {
|
||||
return Weave.Service.engineManager.getAll().every(e => e.enabled);
|
||||
},
|
||||
|
||||
/*
|
||||
* Some helpers for the UI to try and move to the next state.
|
||||
*/
|
||||
|
||||
// Open a UI for the user to create a Firefox Account. This should only be
|
||||
// called while we are in the STATE_USER_FXA state. When the user completes
|
||||
// the creation we'll see an ONLOGIN_NOTIFICATION notification from FxA and
|
||||
// we'll move to either the STATE_USER_FXA_VERIFIED state or we'll just
|
||||
// complete the migration if they login as an already verified user.
|
||||
createFxAccount: Task.async(function* (win) {
|
||||
let {url, options} = yield this.getFxAccountCreationOptions();
|
||||
win.switchToTabHavingURI(url, true, options);
|
||||
// An FxA observer will fire when the user completes this, which will
|
||||
// cause us to move to the next "user blocked" state and notify via our
|
||||
// observer notification.
|
||||
}),
|
||||
|
||||
// Returns an object with properties "url" and "options", suitable for
|
||||
// opening FxAccounts to create/signin to FxA suitable for the migration
|
||||
// state. The caller of this is responsible for the actual opening of the
|
||||
// page.
|
||||
// This should only be called while we are in the STATE_USER_FXA state. When
|
||||
// the user completes the creation we'll see an ONLOGIN_NOTIFICATION
|
||||
// notification from FxA and we'll move to either the STATE_USER_FXA_VERIFIED
|
||||
// state or we'll just complete the migration if they login as an already
|
||||
// verified user.
|
||||
getFxAccountCreationOptions: Task.async(function* (win) {
|
||||
// warn if we aren't in the expected state - but go ahead anyway!
|
||||
if (this._state != this.STATE_USER_FXA) {
|
||||
this.log.warn("getFxAccountCreationOptions called in an unexpected state: ${}", this._state);
|
||||
}
|
||||
// We need to obtain the sentinel and apply any prefs that might be
|
||||
// specified *before* attempting to setup FxA as the prefs might
|
||||
// specify custom servers etc.
|
||||
let sentinel = yield this._getSyncMigrationSentinel();
|
||||
if (sentinel && sentinel.prefs) {
|
||||
this._applySentinelPrefs(sentinel.prefs);
|
||||
}
|
||||
// If we already have a sentinel then we assume the user has previously
|
||||
// created the specified account, so just ask to sign-in.
|
||||
let action = sentinel ? "signin" : "signup";
|
||||
// See if we can find a default account name to use.
|
||||
let email = yield this._getDefaultAccountName(sentinel);
|
||||
let tail = email ? "&email=" + encodeURIComponent(email) : "";
|
||||
// A special flag so server-side metrics can tell this is part of migration.
|
||||
tail += "&migration=sync11";
|
||||
// We want to ask FxA to offer a "Customize Sync" checkbox iff any engines
|
||||
// are disabled.
|
||||
let customize = !this._allEnginesEnabled();
|
||||
tail += "&customizeSync=" + customize;
|
||||
|
||||
// We assume the caller of this is going to actually use it, so record
|
||||
// telemetry now.
|
||||
this.recordTelemetry(this.TELEMETRY_ACCEPTED);
|
||||
return {
|
||||
url: "about:accounts?action=" + action + tail,
|
||||
options: {ignoreFragment: true, replaceQueryString: true}
|
||||
};
|
||||
}),
|
||||
|
||||
// Ask the FxA servers to re-send a verification mail for the currently
|
||||
// logged in user. This should only be called while we are in the
|
||||
// STATE_USER_FXA_VERIFIED state. When the user clicks on the link in
|
||||
// the mail we should see an ONVERIFIED_NOTIFICATION which will cause us
|
||||
// to complete the migration.
|
||||
resendVerificationMail: Task.async(function * (win) {
|
||||
// warn if we aren't in the expected state - but go ahead anyway!
|
||||
if (this._state != this.STATE_USER_FXA_VERIFIED) {
|
||||
this.log.warn("resendVerificationMail called in an unexpected state: ${}", this._state);
|
||||
}
|
||||
let ok = true;
|
||||
try {
|
||||
yield fxAccounts.resendVerificationEmail();
|
||||
} catch (ex) {
|
||||
this.log.error("Failed to resend verification mail: ${}", ex);
|
||||
ok = false;
|
||||
}
|
||||
this.recordTelemetry(this.TELEMETRY_ACCEPTED);
|
||||
let fxauser = yield fxAccounts.getSignedInUser();
|
||||
let sb = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
|
||||
|
||||
let heading = ok ?
|
||||
sb.formatStringFromName("verificationSentHeading", [fxauser.email], 1) :
|
||||
sb.GetStringFromName("verificationNotSentHeading");
|
||||
let title = sb.GetStringFromName(ok ? "verificationSentTitle" : "verificationNotSentTitle");
|
||||
let description = sb.GetStringFromName(ok ? "verificationSentDescription"
|
||||
: "verificationNotSentDescription");
|
||||
|
||||
let factory = Cc["@mozilla.org/prompter;1"]
|
||||
.getService(Ci.nsIPromptFactory);
|
||||
let prompt = factory.getPrompt(win, Ci.nsIPrompt);
|
||||
let bag = prompt.QueryInterface(Ci.nsIWritablePropertyBag2);
|
||||
bag.setPropertyAsBool("allowTabModal", true);
|
||||
|
||||
prompt.alert(title, heading + "\n\n" + description);
|
||||
}),
|
||||
|
||||
// "forget" about the current Firefox account. This should only be called
|
||||
// while we are in the STATE_USER_FXA_VERIFIED state. After this we will
|
||||
// see an ONLOGOUT_NOTIFICATION, which will cause the migrator to return back
|
||||
// to the STATE_USER_FXA state, from where they can choose a different account.
|
||||
forgetFxAccount: Task.async(function * () {
|
||||
// warn if we aren't in the expected state - but go ahead anyway!
|
||||
if (this._state != this.STATE_USER_FXA_VERIFIED) {
|
||||
this.log.warn("forgetFxAccount called in an unexpected state: ${}", this._state);
|
||||
}
|
||||
return fxAccounts.signOut();
|
||||
}),
|
||||
|
||||
recordTelemetry(flag) {
|
||||
// Note the value is the telemetry field name - but this is an
|
||||
// implementation detail which could be changed later.
|
||||
switch (flag) {
|
||||
case this.TELEMETRY_ACCEPTED:
|
||||
case this.TELEMETRY_UNLINKED:
|
||||
case this.TELEMETRY_DECLINED:
|
||||
Services.obs.notifyObservers(null, OBSERVER_INTERNAL_TELEMETRY_TOPIC, flag);
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unexpected telemetry flag: " + flag);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -63,24 +63,6 @@ SyncDevicesMeasurement1.prototype = Object.freeze({
|
||||
},
|
||||
});
|
||||
|
||||
function SyncMigrationMeasurement1() {
|
||||
Metrics.Measurement.call(this);
|
||||
}
|
||||
|
||||
SyncMigrationMeasurement1.prototype = Object.freeze({
|
||||
__proto__: Metrics.Measurement.prototype,
|
||||
|
||||
name: "migration",
|
||||
version: 1,
|
||||
|
||||
fields: {
|
||||
state: DAILY_LAST_TEXT_FIELD, // last "user" or "internal" state we saw for the day
|
||||
accepted: DAILY_COUNTER_FIELD, // number of times user tried to start migration
|
||||
declined: DAILY_COUNTER_FIELD, // number of times user closed nagging infobar
|
||||
unlinked: DAILY_LAST_NUMERIC_FIELD, // did the user decline and unlink
|
||||
},
|
||||
});
|
||||
|
||||
this.SyncProvider = function () {
|
||||
Metrics.Provider.call(this);
|
||||
};
|
||||
@ -92,16 +74,12 @@ SyncProvider.prototype = Object.freeze({
|
||||
measurementTypes: [
|
||||
SyncDevicesMeasurement1,
|
||||
SyncMeasurement1,
|
||||
SyncMigrationMeasurement1,
|
||||
],
|
||||
|
||||
_OBSERVERS: [
|
||||
"weave:service:sync:start",
|
||||
"weave:service:sync:finish",
|
||||
"weave:service:sync:error",
|
||||
"fxa-migration:state-changed",
|
||||
"fxa-migration:internal-state-changed",
|
||||
"fxa-migration:internal-telemetry",
|
||||
],
|
||||
|
||||
postInit: function () {
|
||||
@ -126,11 +104,6 @@ SyncProvider.prototype = Object.freeze({
|
||||
case "weave:service:sync:finish":
|
||||
case "weave:service:sync:error":
|
||||
return this._observeSync(subject, topic, data);
|
||||
|
||||
case "fxa-migration:state-changed":
|
||||
case "fxa-migration:internal-state-changed":
|
||||
case "fxa-migration:internal-telemetry":
|
||||
return this._observeMigration(subject, topic, data);
|
||||
}
|
||||
Cu.reportError("unexpected topic in sync healthreport provider: " + topic);
|
||||
},
|
||||
@ -162,47 +135,6 @@ SyncProvider.prototype = Object.freeze({
|
||||
});
|
||||
},
|
||||
|
||||
_observeMigration: function(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "fxa-migration:state-changed":
|
||||
case "fxa-migration:internal-state-changed": {
|
||||
// We record both "user" and "internal" states in the same field. This
|
||||
// works for us as user state is always null when there is an internal
|
||||
// state.
|
||||
if (!data) {
|
||||
return; // we don't count the |null| state
|
||||
}
|
||||
let m = this.getMeasurement(SyncMigrationMeasurement1.prototype.name,
|
||||
SyncMigrationMeasurement1.prototype.version);
|
||||
return this.enqueueStorageOperation(function() {
|
||||
return m.setDailyLastText("state", data);
|
||||
});
|
||||
}
|
||||
|
||||
case "fxa-migration:internal-telemetry": {
|
||||
// |data| is our field name.
|
||||
let m = this.getMeasurement(SyncMigrationMeasurement1.prototype.name,
|
||||
SyncMigrationMeasurement1.prototype.version);
|
||||
return this.enqueueStorageOperation(function() {
|
||||
switch (data) {
|
||||
case "accepted":
|
||||
case "declined":
|
||||
return m.incrementDailyCounter(data);
|
||||
case "unlinked":
|
||||
return m.setDailyLastNumeric(data, 1);
|
||||
default:
|
||||
Cu.reportError("Unexpected migration field in sync healthreport provider: " + data);
|
||||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
Cu.reportError("unexpected migration topic in sync healthreport provider: " + topic);
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
collectDailyData: function () {
|
||||
return this.storage.enqueueTransaction(this._populateDailyData.bind(this));
|
||||
},
|
||||
|
@ -517,45 +517,6 @@ SyncScheduler.prototype = {
|
||||
this.syncTimer.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Prevent new syncs from starting. This is used by the FxA migration code
|
||||
* where we can't afford to have a sync start partway through the migration.
|
||||
* To handle the edge-case of a sync starting and not stopping, we store
|
||||
* this state in a pref, so on the next startup we remain blocked (and thus
|
||||
* sync will never start) so the migration can complete.
|
||||
*
|
||||
* As a safety measure, we only block for some period of time, and after
|
||||
* that it will automatically unblock. This ensures that if things go
|
||||
* really pear-shaped and we never end up calling unblockSync() we haven't
|
||||
* completely broken the world.
|
||||
*/
|
||||
blockSync: function(until = null) {
|
||||
if (!until) {
|
||||
until = Date.now() + DEFAULT_BLOCK_PERIOD;
|
||||
}
|
||||
// until is specified in ms, but Prefs can't hold that much
|
||||
Svc.Prefs.set("scheduler.blocked-until", Math.floor(until / 1000));
|
||||
},
|
||||
|
||||
unblockSync: function() {
|
||||
Svc.Prefs.reset("scheduler.blocked-until");
|
||||
// the migration code should be ready to roll, so resume normal operations.
|
||||
this.checkSyncStatus();
|
||||
},
|
||||
|
||||
get isBlocked() {
|
||||
let until = Svc.Prefs.get("scheduler.blocked-until");
|
||||
if (until === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (until <= Math.floor(Date.now() / 1000)) {
|
||||
// we were previously blocked but the time has expired.
|
||||
Svc.Prefs.reset("scheduler.blocked-until");
|
||||
return false;
|
||||
}
|
||||
// we remain blocked.
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
this.ErrorHandler = function ErrorHandler(service) {
|
||||
|
@ -1,37 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
Cu.import("resource://services-sync/main.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
// Simple test for block/unblock.
|
||||
add_task(function *() {
|
||||
Assert.ok(!Weave.Service.scheduler.isBlocked, "sync is not blocked.")
|
||||
Assert.ok(!Svc.Prefs.has("scheduler.blocked-until"), "have no blocked pref");
|
||||
Weave.Service.scheduler.blockSync();
|
||||
|
||||
Assert.ok(Weave.Service.scheduler.isBlocked, "sync is blocked.")
|
||||
Assert.ok(Svc.Prefs.has("scheduler.blocked-until"), "have the blocked pref");
|
||||
|
||||
Weave.Service.scheduler.unblockSync();
|
||||
Assert.ok(!Weave.Service.scheduler.isBlocked, "sync is not blocked.")
|
||||
Assert.ok(!Svc.Prefs.has("scheduler.blocked-until"), "have no blocked pref");
|
||||
|
||||
// now check the "until" functionality.
|
||||
let until = Date.now() + 1000;
|
||||
Weave.Service.scheduler.blockSync(until);
|
||||
Assert.ok(Weave.Service.scheduler.isBlocked, "sync is blocked.")
|
||||
Assert.ok(Svc.Prefs.has("scheduler.blocked-until"), "have the blocked pref");
|
||||
|
||||
// wait for 'until' to pass.
|
||||
yield new Promise((resolve, reject) => {
|
||||
CommonUtils.namedTimer(resolve, 1000, {}, "timer");
|
||||
});
|
||||
|
||||
// should have automagically unblocked and removed the pref.
|
||||
Assert.ok(!Weave.Service.scheduler.isBlocked, "sync is not blocked.")
|
||||
Assert.ok(!Svc.Prefs.has("scheduler.blocked-until"), "have no blocked pref");
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
@ -4,9 +4,6 @@ Services.prefs.setCharPref("identity.fxaccounts.auth.uri", "http://localhost");
|
||||
// Test the FxAMigration module
|
||||
Cu.import("resource://services-sync/FxaMigrator.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccounts.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
Cu.import("resource://services-sync/browserid_identity.js");
|
||||
|
||||
// Set our username pref early so sync initializes with the legacy provider.
|
||||
Services.prefs.setCharPref("services.sync.username", "foo");
|
||||
@ -83,227 +80,31 @@ function configureLegacySync() {
|
||||
return [engine, server];
|
||||
}
|
||||
|
||||
add_task(function *testMigration() {
|
||||
add_task(function *testMigrationUnlinks() {
|
||||
|
||||
// when we do a .startOver we want the new provider.
|
||||
let oldValue = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity");
|
||||
Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", false);
|
||||
|
||||
// disable the addons engine - this engine choice is arbitrary, but we
|
||||
// want to check it remains disabled after migration.
|
||||
Services.prefs.setBoolPref("services.sync.engine.addons", false);
|
||||
|
||||
do_register_cleanup(() => {
|
||||
Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", oldValue)
|
||||
Services.prefs.setBoolPref("services.sync.engine.addons", true);
|
||||
});
|
||||
|
||||
// No sync user - that should report no user-action necessary.
|
||||
Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()), null,
|
||||
"no user state when complete");
|
||||
|
||||
// Arrange for a legacy sync user and manually bump the migrator
|
||||
// Arrange for a legacy sync user.
|
||||
let [engine, server] = configureLegacySync();
|
||||
|
||||
// Check our disabling of the "addons" engine worked, and for good measure,
|
||||
// that the "passwords" engine is enabled.
|
||||
Assert.ok(!Service.engineManager.get("addons").enabled, "addons is disabled");
|
||||
Assert.ok(Service.engineManager.get("passwords").enabled, "passwords is enabled");
|
||||
|
||||
// monkey-patch the migration sentinel code so we know it was called.
|
||||
let haveStartedSentinel = false;
|
||||
let origSetFxAMigrationSentinel = Service.setFxAMigrationSentinel;
|
||||
let promiseSentinelWritten = new Promise((resolve, reject) => {
|
||||
Service.setFxAMigrationSentinel = function(arg) {
|
||||
haveStartedSentinel = true;
|
||||
return origSetFxAMigrationSentinel.call(Service, arg).then(result => {
|
||||
Service.setFxAMigrationSentinel = origSetFxAMigrationSentinel;
|
||||
resolve(result);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// We are now configured for legacy sync, but we aren't in an EOL state yet,
|
||||
// so should still be not waiting for a user.
|
||||
Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()), null,
|
||||
"no user state before server EOL");
|
||||
|
||||
// Start a sync - this will cause an EOL notification which the migrator's
|
||||
// observer will notice.
|
||||
let promise = promiseOneObserver("fxa-migration:state-changed");
|
||||
let promiseMigration = promiseOneObserver("fxa-migration:state-changed");
|
||||
let promiseStartOver = promiseOneObserver("weave:service:start-over:finish");
|
||||
_("Starting sync");
|
||||
Service.sync();
|
||||
_("Finished sync");
|
||||
|
||||
// We should have seen the observer, so be waiting for an FxA user.
|
||||
Assert.equal((yield promise).data, fxaMigrator.STATE_USER_FXA, "now waiting for FxA.")
|
||||
|
||||
// Re-calling our user-state promise should also reflect the same state.
|
||||
Assert.equal((yield fxaMigrator._queueCurrentUserState()),
|
||||
fxaMigrator.STATE_USER_FXA,
|
||||
"still waiting for FxA.");
|
||||
|
||||
// arrange for an unverified FxA user.
|
||||
let config = makeIdentityConfig({username: FXA_USERNAME});
|
||||
let fxa = new FxAccounts({});
|
||||
config.fxaccount.user.email = config.username;
|
||||
delete config.fxaccount.user.verified;
|
||||
// *sob* - shouldn't need this boilerplate
|
||||
fxa.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) {
|
||||
this.cert = {
|
||||
validUntil: fxa.internal.now() + CERT_LIFETIME,
|
||||
cert: "certificate",
|
||||
};
|
||||
return Promise.resolve(this.cert.cert);
|
||||
};
|
||||
|
||||
// As soon as we set the FxA user the observers should fire and magically
|
||||
// transition.
|
||||
promise = promiseOneObserver("fxa-migration:state-changed");
|
||||
fxAccounts.setSignedInUser(config.fxaccount.user);
|
||||
|
||||
let observerInfo = yield promise;
|
||||
Assert.equal(observerInfo.data,
|
||||
fxaMigrator.STATE_USER_FXA_VERIFIED,
|
||||
"now waiting for verification");
|
||||
Assert.ok(observerInfo.subject instanceof Ci.nsISupportsString,
|
||||
"email was passed to observer");
|
||||
Assert.equal(observerInfo.subject.data,
|
||||
FXA_USERNAME,
|
||||
"email passed to observer is correct");
|
||||
|
||||
// should have seen the user set, so state should automatically update.
|
||||
Assert.equal((yield fxaMigrator._queueCurrentUserState()),
|
||||
fxaMigrator.STATE_USER_FXA_VERIFIED,
|
||||
"now waiting for verification");
|
||||
|
||||
// Before we verify the user, fire off a sync that calls us back during
|
||||
// the sync and before it completes - this way we can ensure we do the right
|
||||
// thing in terms of blocking sync and waiting for it to complete.
|
||||
|
||||
let wasWaiting = false;
|
||||
// This is a PITA as sync is pseudo-blocking.
|
||||
engine._syncFinish = function () {
|
||||
// We aren't in a generator here, so use a helper to block on promises.
|
||||
function getState() {
|
||||
let cb = Async.makeSpinningCallback();
|
||||
fxaMigrator._queueCurrentUserState().then(state => cb(null, state));
|
||||
return cb.wait();
|
||||
}
|
||||
// should still be waiting for verification.
|
||||
Assert.equal(getState(), fxaMigrator.STATE_USER_FXA_VERIFIED,
|
||||
"still waiting for verification");
|
||||
|
||||
// arrange for the user to be verified. The fxAccount's mock story is
|
||||
// broken, so go behind its back.
|
||||
config.fxaccount.user.verified = true;
|
||||
fxAccounts.setSignedInUser(config.fxaccount.user);
|
||||
Services.obs.notifyObservers(null, ONVERIFIED_NOTIFICATION, null);
|
||||
|
||||
// spinningly wait for the migrator to catch up - sync is running so
|
||||
// we should be in a 'null' user-state as there is no user-action
|
||||
// necessary.
|
||||
let cb = Async.makeSpinningCallback();
|
||||
promiseOneObserver("fxa-migration:state-changed").then(({ data: state }) => cb(null, state));
|
||||
Assert.equal(cb.wait(), null, "no user action necessary while sync completes.");
|
||||
|
||||
// We must not have started writing the sentinel yet.
|
||||
Assert.ok(!haveStartedSentinel, "haven't written a sentinel yet");
|
||||
|
||||
// sync should be blocked from continuing
|
||||
Assert.ok(Service.scheduler.isBlocked, "sync is blocked.")
|
||||
|
||||
wasWaiting = true;
|
||||
throw ex;
|
||||
};
|
||||
|
||||
_("Starting sync");
|
||||
Service.sync();
|
||||
_("Finished sync");
|
||||
|
||||
// mock sync so we can ensure the final sync is scheduled with the FxA user.
|
||||
// (letting a "normal" sync complete is a PITA without mocking huge amounts
|
||||
// of FxA infra)
|
||||
let promiseFinalSync = new Promise((resolve, reject) => {
|
||||
let oldSync = Service.sync;
|
||||
Service.sync = function() {
|
||||
Service.sync = oldSync;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
Assert.ok(wasWaiting, "everything was good while sync was running.")
|
||||
|
||||
// The migration is now going to run to completion.
|
||||
// sync should still be "blocked"
|
||||
Assert.ok(Service.scheduler.isBlocked, "sync is blocked.");
|
||||
|
||||
// We should see the migration sentinel written and it should return true.
|
||||
Assert.ok((yield promiseSentinelWritten), "wrote the sentinel");
|
||||
|
||||
// And we should see a new sync start
|
||||
yield promiseFinalSync;
|
||||
|
||||
// and we should be configured for FxA
|
||||
let WeaveService = Cc["@mozilla.org/weave/service;1"]
|
||||
.getService(Components.interfaces.nsISupports)
|
||||
.wrappedJSObject;
|
||||
Assert.ok(WeaveService.fxAccountsEnabled, "FxA is enabled");
|
||||
Assert.ok(Service.identity instanceof BrowserIDManager,
|
||||
"sync is configured with the browserid_identity provider.");
|
||||
Assert.equal(Service.identity.username, config.username, "correct user configured")
|
||||
Assert.ok(!Service.scheduler.isBlocked, "sync is not blocked.")
|
||||
// and the user state should remain null.
|
||||
Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()),
|
||||
null,
|
||||
"still no user action necessary");
|
||||
// and our engines should be in the same enabled/disabled state as before.
|
||||
Assert.ok(!Service.engineManager.get("addons").enabled, "addons is still disabled");
|
||||
Assert.ok(Service.engineManager.get("passwords").enabled, "passwords is still enabled");
|
||||
|
||||
// aaaand, we are done - clean up.
|
||||
yield promiseStopServer(server);
|
||||
});
|
||||
|
||||
// Test our tokenServer URL is set correctly given we've changed the prefname
|
||||
// it uses.
|
||||
add_task(function* testTokenServerOldPrefName() {
|
||||
let value = "http://custom-token-server/";
|
||||
// Set the pref we used in the past...
|
||||
Services.prefs.setCharPref("services.sync.tokenServerURI", value);
|
||||
// And make sure the new pref the value will be written to has a different
|
||||
// value.
|
||||
Assert.notEqual(Services.prefs.getCharPref("identity.sync.tokenserver.uri"), value);
|
||||
|
||||
let prefs = fxaMigrator._getSentinelPrefs();
|
||||
Assert.equal(prefs["services.sync.tokenServerURI"], value);
|
||||
// check it applies correctly.
|
||||
Services.prefs.clearUserPref("services.sync.tokenServerURI");
|
||||
Assert.ok(!Services.prefs.prefHasUserValue("services.sync.tokenServerURI"));
|
||||
fxaMigrator._applySentinelPrefs(prefs);
|
||||
// We should have written the pref value to the *new* pref name.
|
||||
Assert.equal(Services.prefs.getCharPref("identity.sync.tokenserver.uri"), value);
|
||||
// And the old pref name should remain untouched.
|
||||
Assert.ok(!Services.prefs.prefHasUserValue("services.sync.tokenServerURI"));
|
||||
});
|
||||
|
||||
add_task(function* testTokenServerNewPrefName() {
|
||||
let value = "http://token-server/";
|
||||
// Set the new pref name we now use.
|
||||
Services.prefs.setCharPref("identity.sync.tokenserver.uri", value);
|
||||
|
||||
let prefs = fxaMigrator._getSentinelPrefs();
|
||||
// It should be written to the sentinel with the *old* pref name.
|
||||
Assert.equal(prefs["services.sync.tokenServerURI"], value);
|
||||
// check it applies correctly.
|
||||
Services.prefs.clearUserPref("services.sync.tokenServerURI");
|
||||
Assert.ok(!Services.prefs.prefHasUserValue("services.sync.tokenServerURI"));
|
||||
fxaMigrator._applySentinelPrefs(prefs);
|
||||
// We should have written the pref value to the new pref name.
|
||||
Assert.equal(Services.prefs.getCharPref("identity.sync.tokenserver.uri"), value);
|
||||
// And the old pref name should remain untouched.
|
||||
Assert.ok(!Services.prefs.prefHasUserValue("services.sync.tokenServerURI"));
|
||||
yield promiseStartOver;
|
||||
yield promiseMigration;
|
||||
// We should have seen the observer and Sync should no longer be configured.
|
||||
Assert.ok(!Services.prefs.prefHasUserValue("services.sync.username"));
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
|
@ -1,150 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test the reading and writing of the sync migration sentinel.
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccounts.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
|
||||
Cu.import("resource://testing-common/services/sync/utils.js");
|
||||
Cu.import("resource://testing-common/services/common/logging.js");
|
||||
|
||||
Cu.import("resource://services-sync/record.js");
|
||||
|
||||
// Set our username pref early so sync initializes with the legacy provider.
|
||||
Services.prefs.setCharPref("services.sync.username", "foo");
|
||||
|
||||
// Now import sync
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
|
||||
const USER = "foo";
|
||||
const PASSPHRASE = "abcdeabcdeabcdeabcdeabcdea";
|
||||
|
||||
function promiseStopServer(server) {
|
||||
return new Promise((resolve, reject) => {
|
||||
server.stop(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
var numServerRequests = 0;
|
||||
|
||||
// Helpers
|
||||
function configureLegacySync() {
|
||||
let contents = {
|
||||
meta: {global: {}},
|
||||
crypto: {},
|
||||
};
|
||||
|
||||
setBasicCredentials(USER, "password", PASSPHRASE);
|
||||
|
||||
numServerRequests = 0;
|
||||
let server = new SyncServer({
|
||||
onRequest: () => {
|
||||
++numServerRequests
|
||||
}
|
||||
});
|
||||
server.registerUser(USER, "password");
|
||||
server.createContents(USER, contents);
|
||||
server.start();
|
||||
|
||||
Service.serverURL = server.baseURI;
|
||||
Service.clusterURL = server.baseURI;
|
||||
Service.identity.username = USER;
|
||||
Service._updateCachedURLs();
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
// Test a simple round-trip of the get/set functions.
|
||||
add_task(function *() {
|
||||
// Arrange for a legacy sync user.
|
||||
let server = configureLegacySync();
|
||||
|
||||
Assert.equal((yield Service.getFxAMigrationSentinel()), null, "no sentinel to start");
|
||||
|
||||
let sentinel = {foo: "bar"};
|
||||
yield Service.setFxAMigrationSentinel(sentinel);
|
||||
|
||||
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got the sentinel back");
|
||||
|
||||
yield promiseStopServer(server);
|
||||
});
|
||||
|
||||
// Test the records are cached by the record manager.
|
||||
add_task(function *() {
|
||||
// Arrange for a legacy sync user.
|
||||
let server = configureLegacySync();
|
||||
Service.login();
|
||||
|
||||
// Reset the request count here as the login would have made some.
|
||||
numServerRequests = 0;
|
||||
|
||||
Assert.equal((yield Service.getFxAMigrationSentinel()), null, "no sentinel to start");
|
||||
Assert.equal(numServerRequests, 1, "first fetch should hit the server");
|
||||
|
||||
let sentinel = {foo: "bar"};
|
||||
yield Service.setFxAMigrationSentinel(sentinel);
|
||||
Assert.equal(numServerRequests, 2, "setting sentinel should hit the server");
|
||||
|
||||
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got the sentinel back");
|
||||
Assert.equal(numServerRequests, 2, "second fetch should not should hit the server");
|
||||
|
||||
// Clobber the caches and ensure we still get the correct value back when we
|
||||
// do hit the server.
|
||||
Service.recordManager.clearCache();
|
||||
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got the sentinel back");
|
||||
Assert.equal(numServerRequests, 3, "should have re-hit the server with empty caches");
|
||||
|
||||
yield promiseStopServer(server);
|
||||
});
|
||||
|
||||
// Test the records are cached by a sync.
|
||||
add_task(function* () {
|
||||
let server = configureLegacySync();
|
||||
|
||||
// A first sync clobbers meta/global due to it being empty, so we first
|
||||
// do a sync which forces a good set of data on the server.
|
||||
Service.sync();
|
||||
|
||||
// Now create a sentinel exists on the server. It's encrypted, so we need to
|
||||
// put an encrypted version.
|
||||
let cryptoWrapper = new CryptoWrapper("meta", "fxa_credentials");
|
||||
let sentinel = {foo: "bar"};
|
||||
cryptoWrapper.cleartext = {
|
||||
id: "fxa_credentials",
|
||||
sentinel: sentinel,
|
||||
deleted: false,
|
||||
}
|
||||
cryptoWrapper.encrypt(Service.identity.syncKeyBundle);
|
||||
let payload = {
|
||||
ciphertext: cryptoWrapper.ciphertext,
|
||||
IV: cryptoWrapper.IV,
|
||||
hmac: cryptoWrapper.hmac,
|
||||
};
|
||||
|
||||
server.createContents(USER, {
|
||||
meta: {fxa_credentials: payload},
|
||||
crypto: {},
|
||||
});
|
||||
|
||||
// Another sync - this will cause the encrypted record to be fetched.
|
||||
Service.sync();
|
||||
// Reset the request count here as the sync will have made many!
|
||||
numServerRequests = 0;
|
||||
|
||||
// Asking for the sentinel should use the copy cached in the record manager.
|
||||
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got it");
|
||||
Assert.equal(numServerRequests, 0, "should not have hit the server");
|
||||
|
||||
// And asking for it again should work (we have to work around the fact the
|
||||
// ciphertext is clobbered on first decrypt...)
|
||||
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got it again");
|
||||
Assert.equal(numServerRequests, 0, "should not have hit the server");
|
||||
|
||||
yield promiseStopServer(server);
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
initTestLogging();
|
||||
run_next_test();
|
||||
}
|
@ -1,155 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/Metrics.jsm", this);
|
||||
Cu.import("resource://gre/modules/Preferences.jsm", this);
|
||||
Cu.import("resource://gre/modules/Promise.jsm", this);
|
||||
Cu.import("resource://services-sync/healthreport.jsm", this);
|
||||
Cu.import("resource://services-sync/FxaMigrator.jsm", this);
|
||||
Cu.import("resource://testing-common/services/common/logging.js", this);
|
||||
Cu.import("resource://testing-common/services/healthreport/utils.jsm", this);
|
||||
|
||||
|
||||
function run_test() {
|
||||
initTestLogging();
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_no_data() {
|
||||
let storage = yield Metrics.Storage("collect");
|
||||
let provider = new SyncProvider();
|
||||
yield provider.init(storage);
|
||||
|
||||
try {
|
||||
// Initially nothing should be configured.
|
||||
let now = new Date();
|
||||
yield provider.collectDailyData();
|
||||
|
||||
let m = provider.getMeasurement("migration", 1);
|
||||
let values = yield m.getValues();
|
||||
Assert.equal(values.days.size, 0);
|
||||
Assert.ok(!values.days.hasDay(now));
|
||||
} finally {
|
||||
yield provider.shutdown();
|
||||
yield storage.close();
|
||||
}
|
||||
});
|
||||
|
||||
function checkCorrectStateRecorded(provider, state) {
|
||||
// Wait for storage to complete.
|
||||
yield m.storage.enqueueOperation(() => {
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
let m = provider.getMeasurement("migration", 1);
|
||||
let values = yield m.getValues();
|
||||
Assert.equal(values.days.size, 1);
|
||||
Assert.ok(values.days.hasDay(now));
|
||||
let day = values.days.getDay(now);
|
||||
|
||||
Assert.ok(day.has("state"));
|
||||
Assert.equal(day.get("state"), state);
|
||||
}
|
||||
|
||||
add_task(function* test_state() {
|
||||
let storage = yield Metrics.Storage("collect");
|
||||
let provider = new SyncProvider();
|
||||
yield provider.init(storage);
|
||||
|
||||
try {
|
||||
// Initially nothing should be configured.
|
||||
let now = new Date();
|
||||
|
||||
// We record both a "user" and "internal" state in the same field.
|
||||
// So simulate a "user" state first.
|
||||
Services.obs.notifyObservers(null, "fxa-migration:state-changed",
|
||||
fxaMigrator.STATE_USER_FXA_VERIFIED);
|
||||
checkCorrectStateRecorded(provider, fxaMigrator.STATE_USER_FXA_VERIFIED);
|
||||
|
||||
// And an internal state.
|
||||
Services.obs.notifyObservers(null, "fxa-migration:internal-state-changed",
|
||||
fxaMigrator.STATE_INTERNAL_WAITING_SYNC_COMPLETE);
|
||||
checkCorrectStateRecorded(provider, fxaMigrator.STATE_INTERNAL_WAITING_SYNC_COMPLETE);
|
||||
} finally {
|
||||
yield provider.shutdown();
|
||||
yield storage.close();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_flags() {
|
||||
let storage = yield Metrics.Storage("collect");
|
||||
let provider = new SyncProvider();
|
||||
yield provider.init(storage);
|
||||
|
||||
try {
|
||||
// Initially nothing should be configured.
|
||||
let now = new Date();
|
||||
|
||||
let m = provider.getMeasurement("migration", 1);
|
||||
|
||||
let record = function*(what) {
|
||||
Services.obs.notifyObservers(null, "fxa-migration:internal-telemetry", what);
|
||||
// Wait for storage to complete.
|
||||
yield m.storage.enqueueOperation(Promise.resolve);
|
||||
let values = yield m.getValues();
|
||||
Assert.equal(values.days.size, 1);
|
||||
return values.days.getDay(now);
|
||||
}
|
||||
|
||||
let values = yield m.getValues();
|
||||
Assert.equal(values.days.size, 1);
|
||||
let day = values.days.getDay(now);
|
||||
Assert.ok(!day.has(fxaMigrator.TELEMETRY_ACCEPTED));
|
||||
Assert.ok(!day.has(fxaMigrator.TELEMETRY_DECLINED));
|
||||
Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED));
|
||||
|
||||
// let's send an unknown value to ensure our error mitigation works.
|
||||
day = yield record("unknown");
|
||||
Assert.ok(!day.has(fxaMigrator.TELEMETRY_ACCEPTED));
|
||||
Assert.ok(!day.has(fxaMigrator.TELEMETRY_DECLINED));
|
||||
Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED));
|
||||
|
||||
// record an fxaMigrator.TELEMETRY_ACCEPTED state.
|
||||
day = yield record(fxaMigrator.TELEMETRY_ACCEPTED);
|
||||
Assert.ok(day.has(fxaMigrator.TELEMETRY_ACCEPTED));
|
||||
Assert.ok(!day.has(fxaMigrator.TELEMETRY_DECLINED));
|
||||
Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED));
|
||||
Assert.equal(day.get(fxaMigrator.TELEMETRY_ACCEPTED), 1);
|
||||
|
||||
// and again - it should get 2.
|
||||
day = yield record(fxaMigrator.TELEMETRY_ACCEPTED);
|
||||
Assert.equal(day.get(fxaMigrator.TELEMETRY_ACCEPTED), 2);
|
||||
|
||||
// record fxaMigrator.TELEMETRY_DECLINED - also a counter.
|
||||
day = yield record(fxaMigrator.TELEMETRY_DECLINED);
|
||||
Assert.ok(day.has(fxaMigrator.TELEMETRY_ACCEPTED));
|
||||
Assert.ok(day.has(fxaMigrator.TELEMETRY_DECLINED));
|
||||
Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED));
|
||||
Assert.equal(day.get(fxaMigrator.TELEMETRY_ACCEPTED), 2);
|
||||
Assert.equal(day.get(fxaMigrator.TELEMETRY_DECLINED), 1);
|
||||
|
||||
day = yield record(fxaMigrator.TELEMETRY_DECLINED);
|
||||
Assert.ok(day.has(fxaMigrator.TELEMETRY_ACCEPTED));
|
||||
Assert.ok(day.has(fxaMigrator.TELEMETRY_DECLINED));
|
||||
Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED));
|
||||
Assert.equal(day.get(fxaMigrator.TELEMETRY_ACCEPTED), 2);
|
||||
Assert.equal(day.get(fxaMigrator.TELEMETRY_DECLINED), 2);
|
||||
|
||||
// and fxaMigrator.TELEMETRY_UNLINKED - this is conceptually a "daily bool".
|
||||
// (ie, it's DAILY_LAST_NUMERIC_FIELD and only ever has |1| written to it)
|
||||
day = yield record(fxaMigrator.TELEMETRY_UNLINKED);
|
||||
Assert.ok(day.has(fxaMigrator.TELEMETRY_ACCEPTED));
|
||||
Assert.ok(day.has(fxaMigrator.TELEMETRY_DECLINED));
|
||||
Assert.ok(day.has(fxaMigrator.TELEMETRY_UNLINKED));
|
||||
Assert.equal(day.get(fxaMigrator.TELEMETRY_UNLINKED), 1);
|
||||
// and doing it again still leaves us with |1|
|
||||
day = yield record(fxaMigrator.TELEMETRY_UNLINKED);
|
||||
Assert.equal(day.get(fxaMigrator.TELEMETRY_UNLINKED), 1);
|
||||
} finally {
|
||||
yield provider.shutdown();
|
||||
yield storage.close();
|
||||
}
|
||||
});
|
@ -177,12 +177,7 @@ skip-if = debug
|
||||
[test_healthreport.js]
|
||||
skip-if = ! healthreport
|
||||
|
||||
[test_healthreport_migration.js]
|
||||
skip-if = ! healthreport
|
||||
|
||||
[test_warn_on_truncated_response.js]
|
||||
|
||||
# FxA migration
|
||||
[test_block_sync.js]
|
||||
[test_fxa_migration.js]
|
||||
[test_fxa_migration_sentinel.js]
|
||||
|
Loading…
Reference in New Issue
Block a user