mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 09:45:41 +00:00
Bug 1205591 - Add a basic service channel implementation for listening to Loop's link clicker and opening rooms when requested. r=mikedeboer
This commit is contained in:
parent
a9b130c0b7
commit
5fbb3506f5
@ -1693,6 +1693,7 @@ pref("image.mem.max_decoded_image_kb", 256000);
|
||||
pref("loop.enabled", true);
|
||||
pref("loop.textChat.enabled", true);
|
||||
pref("loop.server", "https://loop.services.mozilla.com/v0");
|
||||
pref("loop.linkClicker.url", "https://hello.firefox.com/");
|
||||
pref("loop.gettingStarted.seen", false);
|
||||
pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
|
||||
pref("loop.gettingStarted.resumeOnFirstJoin", false);
|
||||
|
@ -50,6 +50,7 @@
|
||||
"SocialShare": false,
|
||||
"Task": false,
|
||||
"UITour": false,
|
||||
"WebChannel": false,
|
||||
"XPCOMUtils": false,
|
||||
"uuidgen": true,
|
||||
// Test Related
|
||||
|
@ -15,6 +15,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
|
||||
"resource://services-common/utils.js");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
|
||||
"resource://gre/modules/WebChannel.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
|
||||
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
|
||||
return new EventEmitter();
|
||||
@ -45,6 +48,9 @@ const MAX_TIME_BEFORE_ENCRYPTION = 30 * 60 * 1000;
|
||||
// Wait time between individual re-encryption cycles (1 second).
|
||||
const TIME_BETWEEN_ENCRYPTIONS = 1000;
|
||||
|
||||
// This is the pref name for the url of the standalone pages.
|
||||
const LINKCLICKER_URL_PREFNAME = "loop.linkClicker.url";
|
||||
|
||||
const roomsPushNotification = function(version, channelID) {
|
||||
return LoopRoomsInternal.onNotification(version, channelID);
|
||||
};
|
||||
@ -58,6 +64,8 @@ var gDirty = true;
|
||||
var gCurrentUser = null;
|
||||
// Global variable that keeps track of the room cache.
|
||||
var gRoomsCache = null;
|
||||
// Global variable that keeps track of the link clicker channel.
|
||||
var gLinkClickerChannel = null;
|
||||
|
||||
/**
|
||||
* Extend a `target` object with the properties defined in `source`.
|
||||
@ -177,6 +185,40 @@ var LoopRoomsInternal = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialises the rooms, sets up the link clicker listener.
|
||||
*/
|
||||
init: function() {
|
||||
Services.prefs.addObserver(LINKCLICKER_URL_PREFNAME,
|
||||
this.setupLinkClickerListener.bind(this), false);
|
||||
|
||||
this.setupLinkClickerListener();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up a WebChannel listener for the link clicker so that we can open
|
||||
* rooms in the Firefox UI.
|
||||
*/
|
||||
setupLinkClickerListener: function() {
|
||||
// Ensure any existing channel is tidied up.
|
||||
if (gLinkClickerChannel) {
|
||||
gLinkClickerChannel.stopListening();
|
||||
gLinkClickerChannel = null;
|
||||
}
|
||||
|
||||
let linkClickerUrl = Services.prefs.getCharPref(LINKCLICKER_URL_PREFNAME);
|
||||
|
||||
// Don't do anything if there's no url.
|
||||
if (!linkClickerUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
let uri = Services.io.newURI(linkClickerUrl, null, null);
|
||||
|
||||
gLinkClickerChannel = new WebChannel("loop-link-clicker", uri);
|
||||
gLinkClickerChannel.listen(this._handleLinkClickerMessage.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* @var {String} sessionType The type of user session. May be 'FXA' or 'GUEST'.
|
||||
*/
|
||||
@ -905,6 +947,42 @@ var LoopRoomsInternal = {
|
||||
eventEmitter.emit("refresh");
|
||||
this.getAll(null, () => {});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles a message received from the content channel.
|
||||
*
|
||||
* @param {String} id The channel id.
|
||||
* @param {Object} message The message received.
|
||||
* @param {Object} sendingContext The context for the sending location.
|
||||
*/
|
||||
_handleLinkClickerMessage: function(id, message, sendingContext) {
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sendResponse = response => {
|
||||
gLinkClickerChannel.send({
|
||||
response: response
|
||||
}, sendingContext);
|
||||
};
|
||||
|
||||
let hasRoom = this.rooms.has(message.roomToken);
|
||||
|
||||
switch (message.command) {
|
||||
case "checkWillOpenRoom":
|
||||
sendResponse(hasRoom);
|
||||
break;
|
||||
case "openRoom":
|
||||
if (hasRoom) {
|
||||
this.open(message.roomToken);
|
||||
}
|
||||
sendResponse(hasRoom);
|
||||
break;
|
||||
default:
|
||||
sendResponse(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
Object.freeze(LoopRoomsInternal);
|
||||
@ -926,6 +1004,10 @@ Object.freeze(LoopRoomsInternal);
|
||||
* See the internal code for the API documentation.
|
||||
*/
|
||||
this.LoopRooms = {
|
||||
init: function() {
|
||||
LoopRoomsInternal.init();
|
||||
},
|
||||
|
||||
get participantsCount() {
|
||||
return LoopRoomsInternal.participantsCount;
|
||||
},
|
||||
@ -1016,6 +1098,24 @@ this.LoopRooms = {
|
||||
|
||||
once: (...params) => eventEmitter.once(...params),
|
||||
|
||||
off: (...params) => eventEmitter.off(...params)
|
||||
off: (...params) => eventEmitter.off(...params),
|
||||
|
||||
/**
|
||||
* Expose the internal rooms map for testing purposes only. This avoids
|
||||
* needing to mock the server interfaces.
|
||||
*
|
||||
* @param {Map} roomsCache The new cache data to set for testing purposes. If
|
||||
* not specified, it will reset the cache.
|
||||
*/
|
||||
_setRoomsCache: function(roomsCache) {
|
||||
LoopRoomsInternal.rooms.clear();
|
||||
|
||||
if (roomsCache) {
|
||||
// Need a clone as the internal map is read-only.
|
||||
for (let [key, value] of roomsCache) {
|
||||
LoopRoomsInternal.rooms.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Object.freeze(this.LoopRooms);
|
||||
|
@ -1211,6 +1211,9 @@ this.MozLoopService = {
|
||||
// stub out API functions for unit testing
|
||||
Object.freeze(this);
|
||||
|
||||
// Initialise anything that needs it in rooms.
|
||||
LoopRooms.init();
|
||||
|
||||
// Don't do anything if loop is not enabled.
|
||||
if (!Services.prefs.getBoolPref("loop.enabled")) {
|
||||
return Promise.reject(new Error("loop is not enabled"));
|
||||
|
@ -32,5 +32,8 @@
|
||||
"MozLoopServiceInternal": true,
|
||||
"LoopRoomsInternal": true,
|
||||
"LoopUI": false,
|
||||
// Other items
|
||||
"Chat": true,
|
||||
"WebChannel": true
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ support-files =
|
||||
google_service.sjs
|
||||
head.js
|
||||
loop_fxa.sjs
|
||||
test_loopLinkClicker_channel.html
|
||||
../../../../base/content/test/general/browser_fxa_oauth_with_keys.html
|
||||
|
||||
[browser_CardDavImporter.js]
|
||||
@ -15,6 +16,7 @@ support-files =
|
||||
skip-if = e10s
|
||||
[browser_loop_fxa_server.js]
|
||||
[browser_LoopContacts.js]
|
||||
[browser_LoopRooms_channel.js]
|
||||
[browser_mozLoop_appVersionInfo.js]
|
||||
[browser_mozLoop_context.js]
|
||||
[browser_mozLoop_prefs.js]
|
||||
|
@ -0,0 +1,108 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* This file contains tests for checking the channel from the standalone to
|
||||
* LoopRooms works for checking if rooms can be opened within the conversation
|
||||
* window.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
var {WebChannel} = Cu.import("resource://gre/modules/WebChannel.jsm", {});
|
||||
var {Chat} = Cu.import("resource:///modules/Chat.jsm", {});
|
||||
|
||||
const TEST_URI =
|
||||
"example.com/browser/browser/components/loop/test/mochitest/test_loopLinkClicker_channel.html";
|
||||
const TEST_URI_GOOD = Services.io.newURI("https://" + TEST_URI, null, null);
|
||||
const TEST_URI_BAD = Services.io.newURI("http://" + TEST_URI, null, null);
|
||||
|
||||
const ROOM_TOKEN = "fake1234";
|
||||
const LINKCLICKER_URL_PREFNAME = "loop.linkClicker.url";
|
||||
|
||||
var openChatOrig = Chat.open;
|
||||
|
||||
var fakeRoomList = new Map([[ ROOM_TOKEN, { roomToken: ROOM_TOKEN } ]]);
|
||||
|
||||
// Loads the specified URI in a new tab and waits for it to send us data on our
|
||||
// test web-channel and resolves with that data.
|
||||
function promiseNewChannelResponse(uri, hash) {
|
||||
let waitForChannelPromise = new Promise((resolve, reject) => {
|
||||
let channel = new WebChannel("test-loop-link-clicker-backchannel", uri);
|
||||
channel.listen((id, data, target) => {
|
||||
channel.stopListening();
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
return BrowserTestUtils.withNewTab({
|
||||
gBrowser: gBrowser,
|
||||
url: uri.spec + "#" + hash
|
||||
}, () => waitForChannelPromise);
|
||||
}
|
||||
|
||||
add_task(function* test_loopRooms_webChannel_permissions() {
|
||||
// We haven't set the allowed web page yet - so even the "good" URI should fail.
|
||||
let got = yield promiseNewChannelResponse(TEST_URI_GOOD, "checkWillOpenRoom");
|
||||
// Should have no data.
|
||||
Assert.ok(got.message === undefined, "should have failed to get any data");
|
||||
|
||||
// Add a permission manager entry for our URI.
|
||||
Services.prefs.setCharPref(LINKCLICKER_URL_PREFNAME, TEST_URI_GOOD.spec);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(LINKCLICKER_URL_PREFNAME);
|
||||
});
|
||||
|
||||
// Try again - now we are expecting a response with actual data.
|
||||
got = yield promiseNewChannelResponse(TEST_URI_GOOD, "checkWillOpenRoom");
|
||||
|
||||
// The room doesn't exist, so we should get a negative response.
|
||||
Assert.equal(got.message.response, false, "should have got a response of false");
|
||||
|
||||
// Now a http:// URI - should get nothing even with the permission setup.
|
||||
got = yield promiseNewChannelResponse(TEST_URI_BAD, "checkWillOpenRoom");
|
||||
Assert.ok(got.message === undefined, "should have failed to get any data");
|
||||
});
|
||||
|
||||
add_task(function* test_loopRooms_webchannel_checkWillOpenRoom() {
|
||||
// We've already tested if the room doesn't exist above, so here we add the
|
||||
// room and check the result.
|
||||
LoopRooms._setRoomsCache(fakeRoomList);
|
||||
|
||||
let got = yield promiseNewChannelResponse(TEST_URI_GOOD, "checkWillOpenRoom");
|
||||
|
||||
Assert.equal(got.message.response, true, "should have got a response of true");
|
||||
});
|
||||
|
||||
add_task(function* test_loopRooms_webchannel_openRoom() {
|
||||
let openedUrl;
|
||||
Chat.open = function(contentWindow, origin, title, url) {
|
||||
openedUrl = url;
|
||||
};
|
||||
registerCleanupFunction(() => {
|
||||
Chat.open = openChatOrig;
|
||||
});
|
||||
|
||||
// Test when the room doesn't exist
|
||||
LoopRooms._setRoomsCache();
|
||||
|
||||
let got = yield promiseNewChannelResponse(TEST_URI_GOOD, "openRoom");
|
||||
|
||||
Assert.ok(!openedUrl, "should not open a chat window");
|
||||
Assert.equal(got.message.response, false, "should have got a response of false");
|
||||
|
||||
// Now add a room & check it.
|
||||
LoopRooms._setRoomsCache(fakeRoomList);
|
||||
|
||||
got = yield promiseNewChannelResponse(TEST_URI_GOOD, "openRoom");
|
||||
|
||||
// Check the room was opened.
|
||||
Assert.ok(openedUrl, "should open a chat window");
|
||||
|
||||
let windowId = openedUrl.match(/about:loopconversation\#(\w+)$/)[1];
|
||||
let windowData = MozLoopService.getConversationWindowData(windowId);
|
||||
|
||||
Assert.equal(windowData.type, "room", "window data should contain room as the type");
|
||||
Assert.equal(windowData.roomToken, ROOM_TOKEN, "window data should have the roomToken");
|
||||
|
||||
Assert.equal(got.message.response, true, "should have got a response of true");
|
||||
});
|
@ -0,0 +1,46 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
// Add a listener for responses to our remote requests.
|
||||
window.addEventListener("WebChannelMessageToContent", function (event) {
|
||||
if (event.detail.id == "loop-link-clicker") {
|
||||
// Send what we got back to the test.
|
||||
var backEvent = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "test-loop-link-clicker-backchannel",
|
||||
message: {
|
||||
message: event.detail.message
|
||||
}
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(backEvent);
|
||||
// and stick it in our DOM just for good measure/diagnostics.
|
||||
document.getElementById("troubleshooting").textContent =
|
||||
JSON.stringify(event.detail.message, null, 2);
|
||||
}
|
||||
});
|
||||
|
||||
// Send a message on load requesting that the room is opened.
|
||||
window.onload = function() {
|
||||
var hash = window.location.hash;
|
||||
|
||||
var event = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "loop-link-clicker",
|
||||
message: {
|
||||
command: hash.substring(1, hash.length),
|
||||
roomToken: "fake1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
};
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<pre id="troubleshooting"/>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -49,14 +49,15 @@ this.BrowserTestUtils = {
|
||||
* the tab is loaded. The first argument passed to the function is a
|
||||
* reference to the browser object for the new tab.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @return {} Returns the value that is returned from taskFn.
|
||||
* @resolves When the tab has been closed.
|
||||
* @rejects Any exception from taskFn is propagated.
|
||||
*/
|
||||
withNewTab: Task.async(function* (options, taskFn) {
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(options.gBrowser, options.url);
|
||||
yield taskFn(tab.linkedBrowser);
|
||||
let result = yield taskFn(tab.linkedBrowser);
|
||||
options.gBrowser.removeTab(tab);
|
||||
return Promise.resolve(result);
|
||||
}),
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user