Bug 1277026 - Disconnect Sync and show a notification on FxA remote disconnect. r?markh
MozReview-Commit-ID: Hxz1j5QDkfM
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -307,16 +307,19 @@ BrowserGlue.prototype = {
}
break;
case "weave:service:ready":
this._setSyncAutoconnectDelay();
break;
case "fxaccounts:onverified":
this._showSyncStartedDoorhanger();
break;
+ case "fxaccounts:device_disconnected":
+ this._onDeviceDisconnected();
+ break;
case "weave:engine:clients:display-uri":
this._onDisplaySyncURI(subject);
break;
case "session-save":
this._setPrefToSaveSession(true);
subject.QueryInterface(Ci.nsISupportsPRBool);
subject.data = true;
break;
@@ -525,16 +528,17 @@ BrowserGlue.prototype = {
os.addObserver(this, "quit-application-requested", false);
os.addObserver(this, "quit-application-granted", false);
if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
os.addObserver(this, "browser-lastwindow-close-requested", false);
os.addObserver(this, "browser-lastwindow-close-granted", false);
}
os.addObserver(this, "weave:service:ready", false);
os.addObserver(this, "fxaccounts:onverified", false);
+ os.addObserver(this, "fxaccounts:device_disconnected", false);
os.addObserver(this, "weave:engine:clients:display-uri", false);
os.addObserver(this, "session-save", false);
os.addObserver(this, "places-init-complete", false);
this._isPlacesInitObserver = true;
os.addObserver(this, "places-database-locked", false);
this._isPlacesLockedObserver = true;
os.addObserver(this, "distribution-customization-complete", false);
os.addObserver(this, "places-shutdown", false);
@@ -591,16 +595,17 @@ BrowserGlue.prototype = {
os.removeObserver(this, "quit-application-granted");
os.removeObserver(this, "restart-in-safe-mode");
if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
os.removeObserver(this, "browser-lastwindow-close-requested");
os.removeObserver(this, "browser-lastwindow-close-granted");
}
os.removeObserver(this, "weave:service:ready");
os.removeObserver(this, "fxaccounts:onverified");
+ os.removeObserver(this, "fxaccounts:device_disconnected");
os.removeObserver(this, "weave:engine:clients:display-uri");
os.removeObserver(this, "session-save");
if (this._bookmarksBackupIdleTime) {
this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
delete this._bookmarksBackupIdleTime;
}
if (this._isPlacesInitObserver)
os.removeObserver(this, "places-init-complete");
@@ -2499,16 +2504,29 @@ BrowserGlue.prototype = {
// The payload is wrapped weirdly because of how Sync does notifications.
tabbrowser.addTab(data.wrappedJSObject.object.uri);
} catch (ex) {
Cu.reportError("Error displaying tab received by Sync: " + ex);
}
},
+ _onDeviceDisconnected() {
+ let bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
+ let title = bundle.GetStringFromName("deviceDisconnectedNotification.title");
+ let body = bundle.GetStringFromName("deviceDisconnectedNotification.body");
+
+ let clickCallback = (subject, topic, data) => {
+ if (topic != "alertclickcallback")
+ return;
+ this._openPreferences("sync");
+ }
+ AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
+ },
+
_handleFlashHang: function() {
++this._flashHangCount;
if (this._flashHangCount < 2) {
return;
}
// protected mode only applies to win32
if (Services.appinfo.XPCOMABI != "x86-msvc") {
return;
--- a/browser/locales/en-US/chrome/browser/accounts.properties
+++ b/browser/locales/en-US/chrome/browser/accounts.properties
@@ -23,8 +23,13 @@ verificationSentTitle = Verification Sen
verificationSentBody = A verification link has been sent to %S.
verificationNotSentTitle = Unable to Send Verification
verificationNotSentBody = We are unable to send a verification mail at this time, please try again later.
# LOCALIZATION NOTE (syncStartNotification.title, syncStartNotification.body)
# These strings are used in a notification shown after Sync is connected.
syncStartNotification.title = Sync enabled
syncStartNotification.body = Firefox will begin syncing momentarily.
+
+# LOCALIZATION NOTE (deviceDisconnectedNotification.title, deviceDisconnectedNotification.body)
+# These strings are used in a notification shown after Sync was disconnected remotely.
+deviceDisconnectedNotification.title = Sync disconnected
+deviceDisconnectedNotification.body = This computer has been successfully disconnected from Firefox Sync.
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -54,16 +54,17 @@ var publicProperties = [
"promiseAccountsChangeProfileURI",
"promiseAccountsManageURI",
"removeCachedOAuthToken",
"resendVerificationEmail",
"setSignedInUser",
"signOut",
"updateUserAccountData",
"updateDeviceRegistration",
+ "handleDeviceDisconnection",
"whenVerified"
];
// An AccountState object holds all state related to one specific account.
// Only one AccountState is ever "current" in the FxAccountsInternal object -
// whenever a user logs out or logs in, the current AccountState is discarded,
// making it impossible for the wrong state or state data to be accidentally
// used.
@@ -1486,16 +1487,30 @@ FxAccountsInternal.prototype = {
updateDeviceRegistration() {
return this.getSignedInUser().then(signedInUser => {
if (signedInUser) {
return this._registerOrUpdateDevice(signedInUser);
}
}).catch(error => this._logErrorAndResetDeviceRegistrationVersion(error));
},
+ handleDeviceDisconnection(deviceId) {
+ return this.currentAccountState.getUserAccountData()
+ .then(data => data ? data.deviceId : null)
+ .then(localDeviceId => {
+ if (deviceId == localDeviceId) {
+ this.notifyObservers(ON_DEVICE_DISCONNECTED_NOTIFICATION, deviceId);
+ return this.signOut(true);
+ }
+ log.error(
+ "The device ID to disconnect doesn't match with the local device ID.\n"
+ + "Local: " + localDeviceId + ", ID to disconnect: " + deviceId);
+ });
+ },
+
// If you change what we send to the FxA servers during device registration,
// you'll have to bump the DEVICE_REGISTRATION_VERSION number to force older
// devices to re-register when Firefox updates
_registerOrUpdateDevice(signedInUser) {
try {
// Allow tests to skip device registration because:
// 1. It makes remote requests to the auth server.
// 2. _getDeviceName does not work from xpcshell.
--- a/services/fxaccounts/FxAccountsCommon.js
+++ b/services/fxaccounts/FxAccountsCommon.js
@@ -84,16 +84,17 @@ exports.KEY_LIFETIME = 1000 * 3600
exports.POLL_SESSION = 1000 * 60 * 20; // 20 minutes
// Observer notifications.
exports.ONLOGIN_NOTIFICATION = "fxaccounts:onlogin";
exports.ONVERIFIED_NOTIFICATION = "fxaccounts:onverified";
exports.ONLOGOUT_NOTIFICATION = "fxaccounts:onlogout";
// Internal to services/fxaccounts only
exports.ON_FXA_UPDATE_NOTIFICATION = "fxaccounts:update";
+exports.ON_DEVICE_DISCONNECTED_NOTIFICATION = "fxaccounts:device_disconnected";
exports.FXA_PUSH_SCOPE_ACCOUNT_UPDATE = "chrome://fxa-device-update";
exports.ON_PROFILE_CHANGE_NOTIFICATION = "fxaccounts:profilechange";
// UI Requests.
exports.UI_REQUEST_SIGN_IN_FLOW = "signInFlow";
exports.UI_REQUEST_REFRESH_AUTH = "refreshAuthentication";
--- a/services/fxaccounts/FxAccountsPush.js
+++ b/services/fxaccounts/FxAccountsPush.js
@@ -147,16 +147,19 @@ FxAccountsPushService.prototype = {
this.log.trace("FxAccountsPushService _onPushMessage");
if (!message.data) {
// Use the empty signal to check the verification state of the account right away
this.fxAccounts.checkVerificationStatus();
return;
}
let payload = message.data.json();
switch (payload.command) {
+ case ON_DEVICE_DISCONNECTED_NOTIFICATION:
+ this.fxAccounts.handleDeviceDisconnection(payload.data.id);
+ break;
default:
this.log.warn("FxA Push command unrecognized: " + payload.command);
}
},
/**
* Fired when the Push server drops a subscription, or the subscription identifier changes.
*
* https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#Receiving_Push_Messages
--- a/services/fxaccounts/tests/xpcshell/test_push_service.js
+++ b/services/fxaccounts/tests/xpcshell/test_push_service.js
@@ -138,16 +138,46 @@ add_test(function observePushTopicVerify
let pushService = new FxAccountsPushService({
pushService: mockPushService,
fxAccounts: customAccounts,
});
pushService.observe(emptyMsg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
});
+add_test(function observePushTopicDeviceDisconnected() {
+ const deviceId = "bogusid";
+ let msg = {
+ data: {
+ json: () => ({
+ command: ON_DEVICE_DISCONNECTED_NOTIFICATION,
+ data: {
+ id: deviceId
+ }
+ })
+ },
+ QueryInterface: function() {
+ return this;
+ }
+ };
+ let customAccounts = Object.assign(mockFxAccounts, {
+ handleDeviceDisconnection: function () {
+ // checking verification status on push messages without data
+ run_next_test();
+ }
+ });
+
+ let pushService = new FxAccountsPushService({
+ pushService: mockPushService,
+ fxAccounts: customAccounts,
+ });
+
+ pushService.observe(msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
+});
+
add_test(function observeSubscriptionChangeTopic() {
let customAccounts = Object.assign(mockFxAccounts, {
updateDeviceRegistration: function () {
// subscription change means updating the device registration
run_next_test();
}
});