Bug 1138590 - Create a WebChannel for receiving FxA profile change notifications. r=markh

This commit is contained in:
Zachary Carter 2015-03-05 11:23:06 -08:00
parent e8ffe73b99
commit 69338cfeaf
8 changed files with 318 additions and 0 deletions

View File

@ -12,6 +12,7 @@ support-files =
browser_bug678392-2.html
browser_bug970746.xhtml
browser_fxa_oauth.html
browser_fxa_profile_channel.html
browser_registerProtocolHandler_notification.html
browser_ssl_error_reports_content.js
browser_star_hsts.sjs
@ -319,6 +320,7 @@ skip-if = e10s
skip-if = buildapp == 'mulet' || e10s || os == "linux" # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly. Linux: Intermittent failures - bug 941575.
[browser_fxa_migrate.js]
[browser_fxa_oauth.js]
[browser_fxa_profile_channel.js]
[browser_gestureSupport.js]
skip-if = e10s # Bug 863514 - no gesture support.
[browser_getshortcutoruri.js]

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>fxa_profile_channel_test</title>
</head>
<body>
<script>
window.onload = function(){
var event = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "account_updates",
message: {
command: "profile:image:change",
data: {
uid: "abc123",
},
},
},
});
window.dispatchEvent(event);
};
</script>
</body>
</html>

View File

@ -0,0 +1,90 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
});
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsProfileChannel",
"resource://gre/modules/FxAccountsProfileChannel.jsm");
const HTTP_PATH = "http://example.com";
let gTests = [
{
desc: "FxA Profile Channel - should receive message about account updates",
run: function* () {
return new Promise(function(resolve, reject) {
let tabOpened = false;
let properUrl = "http://example.com/browser/browser/base/content/test/general/browser_fxa_profile_channel.html";
waitForTab(function (tab) {
Assert.ok("Tab successfully opened");
let match = gBrowser.currentURI.spec == properUrl;
Assert.ok(match);
tabOpened = true;
});
let client = new FxAccountsProfileChannel({
content_uri: HTTP_PATH,
});
makeObserver(FxAccountsCommon.ONPROFILE_IMAGE_CHANGE_NOTIFICATION, function (subject, topic, data) {
Assert.ok(tabOpened);
Assert.equal(data, "abc123");
resolve();
gBrowser.removeCurrentTab();
});
gBrowser.selectedTab = gBrowser.addTab(properUrl);
});
}
}
]; // gTests
function makeObserver(aObserveTopic, aObserveFunc) {
let callback = function (aSubject, aTopic, aData) {
if (aTopic == aObserveTopic) {
removeMe();
aObserveFunc(aSubject, aTopic, aData);
}
};
function removeMe() {
Services.obs.removeObserver(callback, aObserveTopic);
}
Services.obs.addObserver(callback, aObserveTopic, false);
return removeMe;
}
function waitForTab(aCallback) {
let container = gBrowser.tabContainer;
container.addEventListener("TabOpen", function tabOpener(event) {
container.removeEventListener("TabOpen", tabOpener, false);
gBrowser.addEventListener("load", function listener() {
gBrowser.removeEventListener("load", listener, true);
let tab = event.target;
aCallback(tab);
}, true);
}, false);
}
function test() {
waitForExplicitFinish();
Task.spawn(function () {
for (let test of gTests) {
info("Running: " + test.desc);
yield test.run();
}
}).then(finish, ex => {
Assert.ok(false, "Unexpected Exception: " + ex);
finish();
});
}

View File

@ -90,6 +90,8 @@ exports.ONLOGOUT_NOTIFICATION = "fxaccounts:onlogout";
// Internal to services/fxaccounts only
exports.ON_FXA_UPDATE_NOTIFICATION = "fxaccounts:update";
exports.ONPROFILE_IMAGE_CHANGE_NOTIFICATION = "fxaccounts:profileimagechange";
// UI Requests.
exports.UI_REQUEST_SIGN_IN_FLOW = "signInFlow";
exports.UI_REQUEST_REFRESH_AUTH = "refreshAuthentication";
@ -97,6 +99,9 @@ exports.UI_REQUEST_REFRESH_AUTH = "refreshAuthentication";
// The OAuth client ID for Firefox Desktop
exports.FX_OAUTH_CLIENT_ID = "5882386c6d801776";
// Profile WebChannel ID
exports.PROFILE_WEBCHANNEL_ID = "account_updates";
// Server errno.
// From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format
exports.ERRNO_ACCOUNT_ALREADY_EXISTS = 101;

View File

@ -0,0 +1,118 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/**
* Firefox Accounts Profile update helper.
* Uses the WebChannel component to receive messages about account changes.
*/
this.EXPORTED_SYMBOLS = ["FxAccountsProfileChannel"];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = 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/FxAccountsCommon.js");
XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
"resource://gre/modules/WebChannel.jsm");
const PROFILE_IMAGE_CHANGE_COMMAND = "profile:image:change";
/**
* Create a new FxAccountsProfileChannel to listen to profile updates
*
* @param {Object} options Options
* @param {Object} options.parameters
* @param {String} options.parameters.content_uri
* The FxA Content server uri
* @constructor
*/
this.FxAccountsProfileChannel = function(options) {
if (!options) {
throw new Error("Missing configuration options");
}
if (!options["content_uri"]) {
throw new Error("Missing 'content_uri' option");
}
this.parameters = options;
this._setupChannel();
};
this.FxAccountsProfileChannel.prototype = {
/**
* Configuration object
*/
parameters: null,
/**
* WebChannel that is used to communicate with content page
*/
_channel: null,
/**
* WebChannel origin, used to validate origin of messages
*/
_webChannelOrigin: null,
/**
* Release all resources that are in use.
*/
tearDown: function() {
this._channel.stopListening();
this._channel = null;
this._channelCallback = null;
},
/**
* Configures and registers a new WebChannel
*
* @private
*/
_setupChannel: function() {
// if this.parameters.content_uri is present but not a valid URI, then this will throw an error.
try {
this._webChannelOrigin = Services.io.newURI(this.parameters.content_uri, null, null);
this._registerChannel();
} catch (e) {
log.error(e);
throw e;
}
},
/**
* Create a new channel with the WebChannelBroker, setup a callback listener
* @private
*/
_registerChannel: function() {
/**
* Processes messages that are called back from the FxAccountsChannel
*
* @param webChannelId {String}
* Command webChannelId
* @param message {Object}
* Command message
* @param target {EventTarget}
* Channel message event target
* @private
*/
let listener = (webChannelId, message, target) => {
if (message) {
let command = message.command;
let data = message.data;
switch (command) {
case PROFILE_IMAGE_CHANGE_COMMAND:
Services.obs.notifyObservers(null, ONPROFILE_IMAGE_CHANGE_NOTIFICATION, data.uid);
break;
}
}
};
this._channelCallback = listener;
this._channel = new WebChannel(PROFILE_WEBCHANNEL_ID, this._webChannelOrigin);
this._channel.listen(this._channelCallback);
log.debug("Channel registered: " + PROFILE_WEBCHANNEL_ID + " with origin " + this._webChannelOrigin.prePath);
}
};

View File

@ -16,6 +16,7 @@ EXTRA_JS_MODULES += [
'FxAccountsCommon.js',
'FxAccountsOAuthClient.jsm',
'FxAccountsOAuthGrantClient.jsm',
'FxAccountsProfileChannel.jsm',
'FxAccountsProfileClient.jsm',
]

View File

@ -0,0 +1,75 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/FxAccountsProfileChannel.jsm");
const URL_STRING = "https://example.com";
add_test(function () {
validationHelper(undefined,
"Error: Missing configuration options");
validationHelper({},
"Error: Missing 'content_uri' option");
validationHelper({ content_uri: 'bad uri' },
/NS_ERROR_MALFORMED_URI/);
run_next_test();
});
add_test(function () {
var mockMessage = {
command: "profile:image:change",
data: { uid: "foo" }
};
makeObserver(ONPROFILE_IMAGE_CHANGE_NOTIFICATION, function (subject, topic, data) {
do_check_eq(data, "foo");
run_next_test();
});
var channel = new FxAccountsProfileChannel({
content_uri: URL_STRING
});
channel._channelCallback(PROFILE_WEBCHANNEL_ID, mockMessage);
});
function run_test() {
run_next_test();
}
function makeObserver(aObserveTopic, aObserveFunc) {
let callback = function (aSubject, aTopic, aData) {
log.debug("observed " + aTopic + " " + aData);
if (aTopic == aObserveTopic) {
removeMe();
aObserveFunc(aSubject, aTopic, aData);
}
};
function removeMe() {
log.debug("removing observer for " + aObserveTopic);
Services.obs.removeObserver(callback, aObserveTopic);
}
Services.obs.addObserver(callback, aObserveTopic, false);
return removeMe;
}
function validationHelper(params, expected) {
try {
new FxAccountsProfileChannel(params);
} catch (e) {
if (typeof expected === 'string') {
return do_check_eq(e.toString(), expected);
} else {
return do_check_true(e.toString().match(expected));
}
}
throw new Error("Validation helper error");
}

View File

@ -15,3 +15,4 @@ reason = FxAccountsManager is only available for B2G for now
[test_oauth_client.js]
[test_oauth_grant_client.js]
[test_profile_client.js]
[test_profile_channel.js]