--- a/browser/extensions/loop/content/modules/LoopRooms.jsm
+++ b/browser/extensions/loop/content/modules/LoopRooms.jsm
@@ -713,17 +713,17 @@ var LoopRoomsInternal = {
open: function(roomToken) {
let windowData = {
roomToken: roomToken,
type: "room"
};
eventEmitter.emit("open", roomToken);
- MozLoopService.openChatWindow(windowData, () => {
+ return MozLoopService.openChatWindow(windowData, () => {
eventEmitter.emit("close");
});
},
/**
* Deletes a room.
*
* @param {String} roomToken The room token.
--- a/browser/extensions/loop/content/modules/MozLoopAPI.jsm
+++ b/browser/extensions/loop/content/modules/MozLoopAPI.jsm
@@ -118,17 +118,17 @@ const updateSocialProvidersCache = funct
// Dispatch an event to content to let stores freshen-up.
LoopAPIInternal.broadcastPushMessage("SocialProvidersChanged");
}
return gSocialProviders;
};
var gAppVersionInfo = null;
-var gBrowserSharingListenerCount = 0;
+var gBrowserSharingListeners = new Set();
var gBrowserSharingWindows = new Set();
var gPageListeners = null;
var gOriginalPageListeners = null;
var gSocialProviders = null;
var gStringBundle = null;
var gStubbedMessageHandlers = null;
var gOriginalPanelHeight = null;
const kBatchMessage = "Batch";
@@ -136,20 +136,22 @@ const kMaxLoopCount = 10;
const kMessageName = "Loop:Message";
const kPushMessageName = "Loop:Message:Push";
const kPushSubscription = "pushSubscription";
const kRoomsPushPrefix = "Rooms:";
const kMessageHandlers = {
/**
* Start browser sharing, which basically means to start listening for tab
* switches and passing the new window ID to the sender whenever that happens.
- *
+ *
* @param {Object} message Message meant for the handler function, containing
* the following parameters in its `data` property:
- * [ ]
+ * [
+ * {Number} windowId The window ID of the chat window
+ * ]
* @param {Function} reply Callback function, invoked with the result of this
* message handler. The result will be sent back to
* the senders' channel.
*/
AddBrowserSharingListener: function(message, reply) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
let browser = win && win.gBrowser.selectedBrowser;
if (!win || !browser) {
@@ -164,20 +166,23 @@ const kMessageHandlers = {
// Tab sharing is not supported yet for e10s-enabled browsers. This will
// be fixed in bug 1137634.
let err = new Error("Tab sharing is not supported for e10s-enabled browsers");
MozLoopService.log.error(err);
reply(cloneableError(err));
return;
}
+ let [windowId] = message.data;
+
win.LoopUI.startBrowserSharing();
gBrowserSharingWindows.add(Cu.getWeakReference(win));
- ++gBrowserSharingListenerCount;
+ gBrowserSharingListeners.add(windowId);
+ reply();
},
/**
* Associates a session-id and a call-id with a window for debugging.
*
* @param {Object} message Message meant for the handler function, containing
* the following parameters in its `data` property:
* [
@@ -228,23 +233,24 @@ const kMessageHandlers = {
* {String} subject Subject of the email to send
* {String} body Body message of the email to send
* {String} recipient Recipient email address (optional)
* ]
* @param {Function} reply Callback function, invoked with the result of this
* message handler. The result will be sent back to
* the senders' channel.
*/
- ComposeEmail: function(message) {
+ ComposeEmail: function(message, reply) {
let [subject, body, recipient] = message.data;
recipient = recipient || "";
let mailtoURL = "mailto:" + encodeURIComponent(recipient) +
"?subject=" + encodeURIComponent(subject) +
"&body=" + encodeURIComponent(body);
extProtocolSvc.loadURI(CommonUtils.makeURI(mailtoURL));
+ reply();
},
/**
* Show a confirmation dialog with the standard - localized - 'Yes'/ 'No'
* buttons or custom labels.
*
* @param {Object} message Message meant for the handler function, containing
* the following parameters in its `data` property:
@@ -358,16 +364,17 @@ const kMessageHandlers = {
ROOM_CONTEXT_ADD: ROOM_CONTEXT_ADD,
ROOM_CREATE: ROOM_CREATE,
ROOM_DELETE: ROOM_DELETE,
SHARING_ROOM_URL: SHARING_ROOM_URL,
SHARING_STATE_CHANGE: SHARING_STATE_CHANGE,
TWO_WAY_MEDIA_CONN_LENGTH: TWO_WAY_MEDIA_CONN_LENGTH
});
},
+
/**
* Returns the app version information for use during feedback.
*
* @param {Object} message Message meant for the handler function, containing
* the following parameters in its `data` property:
* [ ]
* @param {Function} reply Callback function, invoked with the result of this
* message handler. The result will be sent back to
@@ -681,19 +688,53 @@ const kMessageHandlers = {
reply({
email: MozLoopService.userProfile.email,
uid: MozLoopService.userProfile.uid
});
},
/**
* Hangup and close all chat windows that are open.
+ *
+ * @param {Object} message Message meant for the handler function, containing
+ * the following parameters in its `data` property:
+ * [ ]
+ * @param {Function} reply Callback function, invoked with the result of this
+ * message handler. The result will be sent back to
+ * the senders' channel.
*/
- HangupAllChatWindows: function() {
+ HangupAllChatWindows: function(message, reply) {
MozLoopService.hangupAllChatWindows();
+ reply();
+ },
+
+ /**
+ * Hangup a specific chay window or room, by leaving a room, resetting the
+ * screensharing state and removing any active browser switch listeners.
+ *
+ * @param {Object} message Message meant for the handler function, containing
+ * the following parameters in its `data` property:
+ * [
+ * {String} roomToken The token of the room to leave
+ * {Number} windowId The window ID of the chat window
+ * ]
+ * @param {Function} reply Callback function, invoked with the result of this
+ * message handler. The result will be sent back to
+ * the senders' channel.
+ */
+ HangupNow: function(message, reply) {
+ let [roomToken, windowId] = message.data;
+
+ LoopRooms.leave(roomToken);
+ MozLoopService.setScreenShareState(windowId, false);
+ LoopAPI.sendMessageToHandler({
+ name: "RemoveBrowserSharingListener",
+ data: [windowId]
+ });
+ reply();
},
/**
* Check if the current browser has e10s enabled or not
*
* @param {Object} message Message meant for the handler function, containing
* the following parameters in its `data` property:
* []
@@ -810,16 +851,17 @@ const kMessageHandlers = {
* @param {Function} reply Callback function, invoked with the result of this
* message handler. The result will be sent back to
* the senders' channel.
*/
OpenNonE10sWindow: function(message, reply) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
let url = message.data[0] ? message.data[0] : "about:home";
win.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no,non-remote", url);
+ reply();
},
/**
* Opens a URL in a new tab in the browser.
*
* @param {Object} message Message meant for the handler function, containing
* the following parameters in its `data` property:
* [
@@ -832,36 +874,50 @@ const kMessageHandlers = {
OpenURL: function(message, reply) {
let url = message.data[0];
MozLoopService.openURL(url);
reply();
},
/**
* Removes a listener that was previously added.
+ *
+ * @param {Object} message Message meant for the handler function, containing
+ * the following parameters in its `data` property:
+ * [
+ * {Number} windowId The window ID of the chat
+ * ]
+ * @param {Function} reply Callback function, invoked with the result of this
+ * message handler. The result will be sent back to
+ * the senders' channel.
*/
- RemoveBrowserSharingListener: function() {
- if (!gBrowserSharingListenerCount) {
+ RemoveBrowserSharingListener: function(message, reply) {
+ if (!gBrowserSharingListeners.size) {
+ reply();
return;
}
- if (--gBrowserSharingListenerCount > 0) {
+ let [windowId] = message.data;
+ gBrowserSharingListeners.delete(windowId);
+ if (gBrowserSharingListeners.size > 0) {
// There are still clients listening in, so keep on listening...
+ reply();
return;
}
for (let win of gBrowserSharingWindows) {
win = win.get();
if (!win) {
continue;
}
win.LoopUI.stopBrowserSharing();
}
gBrowserSharingWindows.clear();
+ reply();
},
"Rooms:*": function(action, message, reply) {
LoopAPIInternal.handleObjectAPIMessage(LoopRooms, kRoomsPushPrefix,
action, message, reply);
},
/**
@@ -937,19 +993,20 @@ const kMessageHandlers = {
* the state is being changed for.
* {Boolean} active Whether or not screen sharing
* is now active.
* ]
* @param {Function} reply Callback function, invoked with the result of this
* message handler. The result will be sent back to
* the senders' channel.
*/
- SetScreenShareState: function(message) {
+ SetScreenShareState: function(message, reply) {
let [windowId, active] = message.data;
MozLoopService.setScreenShareState(windowId, active);
+ reply();
},
/**
* Share a room URL with the Social API.
*
* @param {Object} message Message meant for the handler function, containing
* the following parameters in its `data` property:
* [
@@ -1309,16 +1366,56 @@ this.LoopAPI = Object.freeze({
/* @see LoopAPIInternal#broadcastPushMessage */
broadcastPushMessage: function(name, data) {
LoopAPIInternal.broadcastPushMessage(name, data);
},
/* @see LoopAPIInternal#destroy */
destroy: function() {
LoopAPIInternal.destroy();
},
+ /**
+ * Gateway for chrome scripts to send a message to a message handler, when
+ * using the RemotePageManager module is not an option.
+ *
+ * @param {Object} message Message meant for the handler function, containing
+ * the following properties:
+ * - {String} name Name of handler to send this
+ * message to. See `kMessageHandlers`
+ * for the available names.
+ * - {String} [action] Optional action name of the
+ * function to call on a sub-API.
+ * - {Array} data List of arguments that the
+ * handler can use.
+ * @param {Function} [reply] Callback function, invoked with the result of this
+ * message handler. Optional.
+ */
+ sendMessageToHandler: function(message, reply) {
+ reply = reply || function() {};
+ let handlerName = message.name;
+ let handler = kMessageHandlers[handlerName];
+ if (gStubbedMessageHandlers && gStubbedMessageHandlers[handlerName]) {
+ handler = gStubbedMessageHandlers[handlerName];
+ }
+ if (!handler) {
+ let msg = "Ouch, no message handler available for '" + handlerName + "'";
+ MozLoopService.log.error(msg);
+ reply(cloneableError(msg));
+ return;
+ }
+
+ if (!message.data) {
+ message.data = [];
+ }
+
+ if (handlerName.endsWith("*")) {
+ handler(message.action, message, reply);
+ } else {
+ handler(message, reply);
+ }
+ },
// The following functions are only used in unit tests.
inspect: function() {
return [Object.create(LoopAPIInternal), Object.create(kMessageHandlers),
gPageListeners ? [...gPageListeners] : null];
},
stub: function(pageListeners) {
if (!gOriginalPageListeners) {
gOriginalPageListeners = gPageListeners;
--- a/browser/extensions/loop/content/modules/MozLoopService.jsm
+++ b/browser/extensions/loop/content/modules/MozLoopService.jsm
@@ -948,17 +948,20 @@ var MozLoopServiceInternal = {
let ref = chatbar.chatboxForURL.get(chatbox.src);
chatbox = ref && ref.get() || chatbox;
} else if (eventName == "Loop:ChatWindowClosed") {
windowCloseCallback();
if (conversationWindowData.type == "room") {
// NOTE: if you add something here, please also consider if something
// needs to be done on the content side as well (e.g.
// activeRoomStore#windowUnload).
- LoopRooms.leave(conversationWindowData.roomToken);
+ LoopAPI.sendMessageToHandler({
+ name: "HangupNow",
+ data: [conversationWindowData.roomToken, windowId]
+ });
}
}
}
window.addEventListener("socialFrameHide", socialFrameChanged.bind(null, "Loop:ChatWindowHidden"));
window.addEventListener("socialFrameShow", socialFrameChanged.bind(null, "Loop:ChatWindowShown"));
window.addEventListener("socialFrameDetached", socialFrameChanged.bind(null, "Loop:ChatWindowDetached"));
window.addEventListener("socialFrameAttached", socialFrameChanged.bind(null, "Loop:ChatWindowAttached"));
@@ -1199,17 +1202,17 @@ var gInitializeTimerFunc = (deferredInit
var gServiceInitialized = false;
/**
* Public API
*/
this.MozLoopService = {
_DNSService: gDNSService,
- _activeScreenShares: [],
+ _activeScreenShares: new Set(),
get channelIDs() {
// Channel ids that will be registered with the PushServer for notifications
return {
roomsFxA: "6add272a-d316-477c-8335-f00f73dfde71",
roomsGuest: "19d3f799-a8f3-4328-9822-b7cd02765832"
};
},
@@ -1917,26 +1920,25 @@ this.MozLoopService = {
* be reflected on the toolbar button.
*
* @param {String} windowId The id of the conversation window the state
* is being changed for.
* @param {Boolean} active Whether or not screen sharing is now active.
*/
setScreenShareState: function(windowId, active) {
if (active) {
- this._activeScreenShares.push(windowId);
+ this._activeScreenShares.add(windowId);
} else {
- var index = this._activeScreenShares.indexOf(windowId);
- if (index != -1) {
- this._activeScreenShares.splice(index, 1);
+ if (this._activeScreenShares.has(windowId)) {
+ this._activeScreenShares.delete(windowId);
}
}
MozLoopServiceInternal.notifyStatusChanged();
},
/**
* Returns true if screen sharing is active in at least one window.
*/
get screenShareActive() {
- return this._activeScreenShares.length > 0;
+ return this._activeScreenShares.size > 0;
}
};
--- a/browser/extensions/loop/content/panels/js/panel.js
+++ b/browser/extensions/loop/content/panels/js/panel.js
@@ -814,17 +814,17 @@ loop.panel = (function(_, mozL10n) {
// We would use onDocumentHidden to null out the data ready for the next
// opening. However, this seems to cause an awkward glitch in the display
// when opening the panel, and it seems cleaner just to update the data
// even if there's a small delay.
loop.request("GetSelectedTabMetadata").then(function(metadata) {
// Bail out when the component is not mounted (anymore).
// This occurs during test runs. See bug 1174611 for more info.
- if (!this.isMounted()) {
+ if (!this.isMounted() || !metadata) {
return;
}
var previewImage = metadata.favicon || "";
var description = metadata.title || metadata.description;
var url = metadata.url;
this.setState({
previewImage: previewImage,
--- a/browser/extensions/loop/content/panels/js/panel.jsx
+++ b/browser/extensions/loop/content/panels/js/panel.jsx
@@ -814,17 +814,17 @@ loop.panel = (function(_, mozL10n) {
// We would use onDocumentHidden to null out the data ready for the next
// opening. However, this seems to cause an awkward glitch in the display
// when opening the panel, and it seems cleaner just to update the data
// even if there's a small delay.
loop.request("GetSelectedTabMetadata").then(function(metadata) {
// Bail out when the component is not mounted (anymore).
// This occurs during test runs. See bug 1174611 for more info.
- if (!this.isMounted()) {
+ if (!this.isMounted() || !metadata) {
return;
}
var previewImage = metadata.favicon || "";
var description = metadata.title || metadata.description;
var url = metadata.url;
this.setState({
previewImage: previewImage,
--- a/browser/extensions/loop/content/shared/js/activeRoomStore.js
+++ b/browser/extensions/loop/content/shared/js/activeRoomStore.js
@@ -974,17 +974,17 @@ loop.store.ActiveRoomStore = (function()
},
/**
* Ends an active screenshare session.
*/
endScreenShare: function() {
if (this._browserSharingListener) {
// Remove the browser sharing listener as we don't need it now.
- loop.request("RemoveBrowserSharingListener");
+ loop.request("RemoveBrowserSharingListener", this.getStoreState().windowId);
loop.unsubscribe("BrowserSwitch", this._browserSharingListener);
this._browserSharingListener = null;
}
if (this._sdkDriver.endScreenShare()) {
this.dispatchAction(new sharedActions.ScreenSharingState({
state: SCREEN_SHARE_STATES.INACTIVE
}));
@@ -1112,23 +1112,18 @@ loop.store.ActiveRoomStore = (function()
});
return;
}
if (loop.standaloneMedia) {
loop.standaloneMedia.multiplexGum.reset();
}
- var requests = [
- ["SetScreenShareState", this.getStoreState().windowId, false]
- ];
-
if (this._browserSharingListener) {
// Remove the browser sharing listener as we don't need it now.
- requests.push(["RemoveBrowserSharingListener"]);
loop.unsubscribe("BrowserSwitch", this._browserSharingListener);
this._browserSharingListener = null;
}
// We probably don't need to end screen share separately, but lets be safe.
this._sdkDriver.disconnectSession();
// Reset various states.
@@ -1140,27 +1135,25 @@ loop.store.ActiveRoomStore = (function()
});
this.setStoreState(newStoreState);
if (this._timeout) {
clearTimeout(this._timeout);
delete this._timeout;
}
- if (!failedJoinRequest &&
- (this._storeState.roomState === ROOM_STATES.JOINING ||
- this._storeState.roomState === ROOM_STATES.JOINED ||
- this._storeState.roomState === ROOM_STATES.SESSION_CONNECTED ||
- this._storeState.roomState === ROOM_STATES.HAS_PARTICIPANTS)) {
- requests.push(["Rooms:Leave", this._storeState.roomToken,
- this._storeState.sessionToken]);
+ // If we're not going to close the window, we can hangup the call ourselves.
+ // NOTE: when the window _is_ closed, hanging up the call is performed by
+ // MozLoopService, because we can't get a message across to LoopAPI
+ // in time whilst a window is closing.
+ if (nextState === ROOM_STATES.FAILED && !failedJoinRequest) {
+ loop.request("HangupNow", this._storeState.roomToken,
+ this._storeState.windowId);
}
- loop.requestMulti.apply(null, requests);
-
this.setStoreState({ roomState: nextState });
},
/**
* When feedback is complete, we go back to the ready state, rather than
* init or gather, as we don't need to get the data from the server again.
*/
feedbackComplete: function() {
--- a/browser/extensions/loop/content/shared/js/mixins.js
+++ b/browser/extensions/loop/content/shared/js/mixins.js
@@ -388,16 +388,19 @@ loop.shared.mixins = (function() {
options.loop = options.loop || false;
this._ensureAudioStopped();
this._getAudioBlob(name, function(error, blob) {
if (error) {
console.error(error);
return;
}
+ if (!blob) {
+ return;
+ }
var url = URL.createObjectURL(blob);
this.audio = new Audio(url);
this.audio.loop = options.loop;
this.audio.play();
}.bind(this));
}.bind(this));
},
@@ -406,17 +409,17 @@ loop.shared.mixins = (function() {
this._canPlay().then(function(canPlay) {
if (!canPlay) {
callback();
return;
}
if (this._isLoopDesktop()) {
loop.request("GetAudioBlob", name).then(function(result) {
- if (result.isError) {
+ if (result && result.isError) {
callback(result);
return;
}
callback(null, result);
});
return;
}
--- a/browser/extensions/loop/test/mochitest/browser_fxa_login.js
+++ b/browser/extensions/loop/test/mochitest/browser_fxa_login.js
@@ -3,17 +3,16 @@
/**
* Test FxA logins with Loop.
*/
"use strict";
const BASE_URL = Services.prefs.getCharPref("loop.server");
-const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
function* checkFxA401() {
let err = MozLoopService.errors.get("login");
is(err.code, 401, "Check error code");
is(err.friendlyMessage, getLoopString("could_not_authenticate"),
"Check friendlyMessage");
is(err.friendlyDetails, getLoopString("password_changed_question"),
"Check friendlyDetails");
--- a/browser/extensions/loop/test/mochitest/browser_mozLoop_appVersionInfo.js
+++ b/browser/extensions/loop/test/mochitest/browser_mozLoop_appVersionInfo.js
@@ -1,13 +1,12 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
-const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
var [, gHandlers] = LoopAPI.inspect();
add_task(function* test_mozLoop_appVersionInfo() {
let appVersionInfo;
gHandlers.GetAppVersionInfo({}, result => appVersionInfo = result);
Assert.ok(appVersionInfo, "should have appVersionInfo");
--- a/browser/extensions/loop/test/mochitest/browser_mozLoop_chat.js
+++ b/browser/extensions/loop/test/mochitest/browser_mozLoop_chat.js
@@ -13,32 +13,54 @@ function isAnyLoopChatOpen() {
add_task(MozLoopService.initialize.bind(MozLoopService));
add_task(function* test_mozLoop_nochat() {
Assert.ok(!isAnyLoopChatOpen(), "there should be no chat windows yet");
});
add_task(function* test_mozLoop_openchat() {
- let windowData = {
- roomToken: "fake1234",
- type: "room"
- };
+ let windowId = LoopRooms.open("fake1234");
+ Assert.ok(isAnyLoopChatOpen(), "chat window should have been opened");
- LoopRooms.open(windowData);
- Assert.ok(isAnyLoopChatOpen(), "chat window should have been opened");
+ let chatboxesForRoom = [...Chat.chatboxes].filter(chatbox => {
+ return chatbox.src == MozLoopServiceInternal.getChatURL(windowId);
+ });
+ Assert.strictEqual(chatboxesForRoom.length, 1, "Only one chatbox should be open");
});
add_task(function* test_mozLoop_hangupAllChatWindows() {
- let windowData = {
- roomToken: "fake2345",
- type: "room"
- };
-
- LoopRooms.open(windowData);
+ LoopRooms.open("fake2345");
yield promiseWaitForCondition(() => {
MozLoopService.hangupAllChatWindows();
return !isAnyLoopChatOpen();
});
Assert.ok(!isAnyLoopChatOpen(), "chat window should have been closed");
});
+
+add_task(function* test_mozLoop_hangupOnClose() {
+ let roomToken = "fake1234";
+
+ let hangupNowCalls = [];
+ LoopAPI.stubMessageHandlers({
+ HangupNow: function(message, reply) {
+ hangupNowCalls.push(message);
+ reply();
+ }
+ });
+
+ let windowId = LoopRooms.open(roomToken);
+
+ yield promiseWaitForCondition(() => {
+ MozLoopService.hangupAllChatWindows();
+ return !isAnyLoopChatOpen();
+ });
+
+ Assert.strictEqual(hangupNowCalls.length, 1, "HangupNow handler should be called once");
+ Assert.deepEqual(hangupNowCalls.pop(), {
+ name: "HangupNow",
+ data: [roomToken, windowId]
+ }, "Messages should be the same");
+
+ LoopAPI.restore();
+});
--- a/browser/extensions/loop/test/mochitest/browser_mozLoop_context.js
+++ b/browser/extensions/loop/test/mochitest/browser_mozLoop_context.js
@@ -2,17 +2,16 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* This file contains tests for various context-in-conversations helpers in
* LoopUI and Loop API.
*/
"use strict";
-const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
var [, gHandlers] = LoopAPI.inspect();
function promiseGetMetadata() {
return new Promise(resolve => gHandlers.GetSelectedTabMetadata({}, resolve));
}
add_task(function* test_mozLoop_getSelectedTabMetadata() {
let metadata = yield promiseGetMetadata();
--- a/browser/extensions/loop/test/mochitest/browser_mozLoop_sharingListeners.js
+++ b/browser/extensions/loop/test/mochitest/browser_mozLoop_sharingListeners.js
@@ -1,35 +1,38 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* This file contains tests for the window.LoopUI active tab trackers.
*/
"use strict";
-const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
var [, gHandlers] = LoopAPI.inspect();
var handlers = [
{ windowId: null }, { windowId: null }
];
+var listenerCount = 41;
+var listenerIds = [];
+
function promiseWindowId() {
return new Promise(resolve => {
LoopAPI.stub([{
sendAsyncMessage: function(messageName, data) {
let [name, windowId] = data;
if (name == "BrowserSwitch") {
LoopAPI.restore();
resolve(windowId);
}
}
}]);
- gHandlers.AddBrowserSharingListener({}, () => {});
+ listenerIds.push(++listenerCount);
+ gHandlers.AddBrowserSharingListener({ data: [listenerCount] }, () => {});
});
}
function* promiseWindowIdReceivedOnAdd(handler) {
handler.windowId = yield promiseWindowId();
}
var createdTabs = [];
@@ -73,17 +76,17 @@ add_task(function* test_singleListener()
// Check that a new tab updates the window id.
yield promiseWindowIdReceivedNewTab([handlers[0]]);
let newWindowId = handlers[0].windowId;
Assert.notEqual(initialWindowId, newWindowId, "Tab contentWindow IDs shouldn't be the same");
// Now remove the listener.
- gHandlers.RemoveBrowserSharingListener();
+ gHandlers.RemoveBrowserSharingListener({ data: [listenerIds.pop()] }, function() {});
yield removeTabs();
});
add_task(function* test_multipleListener() {
yield promiseWindowIdReceivedOnAdd(handlers[0]);
let initialWindowId0 = handlers[0].windowId;
@@ -103,30 +106,30 @@ add_task(function* test_multipleListener
let newWindowId0 = handlers[0].windowId;
let newWindowId1 = handlers[1].windowId;
Assert.ok(newWindowId0, "windowId should not be null anymore");
Assert.equal(newWindowId0, newWindowId1, "Listeners should have the same windowId");
Assert.notEqual(initialWindowId0, newWindowId0, "Tab contentWindow IDs shouldn't be the same");
// Now remove the first listener.
- gHandlers.RemoveBrowserSharingListener();
+ gHandlers.RemoveBrowserSharingListener({ data: [listenerIds.pop()] }, function() {});
// Check that a new tab updates the window id.
yield promiseWindowIdReceivedNewTab([handlers[1]]);
let nextWindowId0 = handlers[0].windowId;
let nextWindowId1 = handlers[1].windowId;
Assert.ok(nextWindowId0, "windowId should not be null anymore");
Assert.equal(newWindowId0, nextWindowId0, "First listener shouldn't have updated");
Assert.notEqual(newWindowId1, nextWindowId1, "Second listener should have updated");
// Cleanup.
- gHandlers.RemoveBrowserSharingListener();
+ gHandlers.RemoveBrowserSharingListener({ data: [listenerIds.pop()] }, function() {});
yield removeTabs();
});
add_task(function* test_infoBar() {
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const kBrowserSharingNotificationId = "loop-sharing-notification";
const kPrefBrowserSharingInfoBar = "loop.browserSharing.showInfoBar";
@@ -173,12 +176,14 @@ add_task(function* test_infoBar() {
getInfoBar().querySelector(".notification-button-default").click();
Assert.equal(getInfoBar(), null, "The notification should be hidden now");
gBrowser.selectedIndex = Array.indexOf(gBrowser.tabs, createdTabs[1]);
Assert.equal(getInfoBar(), null, "The notification should still be hidden");
// Cleanup.
- gHandlers.RemoveBrowserSharingListener();
+ for (let listenerId of listenerIds) {
+ gHandlers.RemoveBrowserSharingListener({ data: [listenerId] }, function() {});
+ }
yield removeTabs();
Services.prefs.clearUserPref(kPrefBrowserSharingInfoBar);
});
--- a/browser/extensions/loop/test/mochitest/browser_mozLoop_socialShare.js
+++ b/browser/extensions/loop/test/mochitest/browser_mozLoop_socialShare.js
@@ -1,15 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://gre/modules/Promise.jsm");
const { SocialService } = Cu.import("resource://gre/modules/SocialService.jsm", {});
-const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
var [, gHandlers] = LoopAPI.inspect();
const kShareProvider = {
name: "provider 1",
origin: "https://example.com",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png",
shareURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar_empty.html"
--- a/browser/extensions/loop/test/mochitest/browser_mozLoop_telemetry.js
+++ b/browser/extensions/loop/test/mochitest/browser_mozLoop_telemetry.js
@@ -2,17 +2,16 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* This file contains tests for the mozLoop telemetry API.
*/
"use strict";
-const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
var [, gHandlers] = LoopAPI.inspect();
var gConstants;
gHandlers.GetAllConstants({}, constants => gConstants = constants);
/**
* Enable local telemetry recording for the duration of the tests.
*/
add_task(function* test_initialize() {
--- a/browser/extensions/loop/test/mochitest/head.js
+++ b/browser/extensions/loop/test/mochitest/head.js
@@ -4,16 +4,17 @@
"use strict";
const HAWK_TOKEN_LENGTH = 64;
const {
LOOP_SESSION_TYPE,
MozLoopServiceInternal,
MozLoopService
} = Cu.import("chrome://loop/content/modules/MozLoopService.jsm", {});
+const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
const { LoopRooms } = Cu.import("chrome://loop/content/modules/LoopRooms.jsm", {});
// Cache this value only once, at the beginning of a
// test run, so that it doesn't pick up the offline=true
// if offline mode is requested multiple times in a test run.
const WAS_OFFLINE = Services.io.offline;
function promisePanelLoaded() {
--- a/browser/extensions/loop/test/shared/activeRoomStore_test.js
+++ b/browser/extensions/loop/test/shared/activeRoomStore_test.js
@@ -22,23 +22,23 @@ describe("loop.store.ActiveRoomStore", f
clock = sandbox.useFakeTimers();
LoopMochaUtils.stubLoopRequest(requestStubs = {
GetLoopPref: sinon.stub(),
GetSelectedTabMetadata: sinon.stub(),
SetLoopPref: sinon.stub(),
AddConversationContext: sinon.stub(),
AddBrowserSharingListener: sinon.stub().returns(42),
+ HangupNow: sinon.stub(),
RemoveBrowserSharingListener: sinon.stub(),
"Rooms:Get": sinon.stub().returns({
roomUrl: "http://invalid"
}),
"Rooms:Join": sinon.stub().returns({}),
"Rooms:RefreshMembership": sinon.stub().returns({ expires: 42 }),
- "Rooms:Leave": sinon.stub(),
"Rooms:SendConnectionStatus": sinon.stub(),
"Rooms:PushSubscription": sinon.stub(),
SetScreenShareState: sinon.stub(),
GetActiveTabWindowId: sandbox.stub().returns(42),
GetSocialShareProviders: sinon.stub().returns([]),
TelemetryAddValue: sinon.stub()
});
@@ -90,17 +90,18 @@ describe("loop.store.ActiveRoomStore", f
beforeEach(function() {
sandbox.stub(console, "error");
fakeError = new Error("fake");
store.setStoreState({
roomState: ROOM_STATES.JOINED,
roomToken: "fakeToken",
- sessionToken: "1627384950"
+ sessionToken: "1627384950",
+ windowId: "42"
});
});
it("should log the error", function() {
store.roomFailure(new sharedActions.RoomFailure({
error: fakeError,
failedJoinRequest: false
}));
@@ -173,28 +174,16 @@ describe("loop.store.ActiveRoomStore", f
store.roomFailure(new sharedActions.RoomFailure({
error: fakeError,
failedJoinRequest: false
}));
sinon.assert.calledOnce(fakeMultiplexGum.reset);
});
- it("should set screen sharing inactive", function() {
- store.setStoreState({ windowId: "1234" });
-
- store.roomFailure(new sharedActions.RoomFailure({
- error: fakeError,
- failedJoinRequest: false
- }));
-
- sinon.assert.calledOnce(requestStubs.SetScreenShareState);
- sinon.assert.calledWithExactly(requestStubs.SetScreenShareState, "1234", false);
- });
-
it("should disconnect from the servers via the sdk", function() {
store.roomFailure(new sharedActions.RoomFailure({
error: fakeError,
failedJoinRequest: false
}));
sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
});
@@ -207,46 +196,49 @@ describe("loop.store.ActiveRoomStore", f
error: fakeError,
failedJoinRequest: false
}));
sinon.assert.calledOnce(clearTimeout);
});
it("should remove the sharing listener", function() {
+ sandbox.stub(loop, "unsubscribe");
+
// Setup the listener.
store.startBrowserShare(new sharedActions.StartBrowserShare());
// Now simulate room failure.
store.roomFailure(new sharedActions.RoomFailure({
error: fakeError,
failedJoinRequest: false
}));
- sinon.assert.calledOnce(requestStubs.RemoveBrowserSharingListener);
+ sinon.assert.calledOnce(loop.unsubscribe);
+ sinon.assert.calledWith(loop.unsubscribe, "BrowserSwitch");
});
- it("should call mozLoop.rooms.leave", function() {
+ it("should call 'HangupNow' Loop API", function() {
store.roomFailure(new sharedActions.RoomFailure({
error: fakeError,
failedJoinRequest: false
}));
- sinon.assert.calledOnce(requestStubs["Rooms:Leave"]);
- sinon.assert.calledWithExactly(requestStubs["Rooms:Leave"],
- "fakeToken", "1627384950");
+ sinon.assert.calledOnce(requestStubs["HangupNow"]);
+ sinon.assert.calledWithExactly(requestStubs["HangupNow"],
+ "fakeToken", "42");
});
- it("should not call mozLoop.rooms.leave if failedJoinRequest is true", function() {
+ it("should not call 'HangupNow' Loop API if failedJoinRequest is true", function() {
store.roomFailure(new sharedActions.RoomFailure({
error: fakeError,
failedJoinRequest: true
}));
- sinon.assert.notCalled(requestStubs["Rooms:Leave"]);
+ sinon.assert.notCalled(requestStubs["HangupNow"]);
});
});
describe("#retryAfterRoomFailure", function() {
beforeEach(function() {
sandbox.stub(console, "error");
});
@@ -1210,17 +1202,18 @@ describe("loop.store.ActiveRoomStore", f
describe("#connectionFailure", function() {
var connectionFailureAction;
beforeEach(function() {
store.setStoreState({
roomState: ROOM_STATES.JOINED,
roomToken: "fakeToken",
- sessionToken: "1627384950"
+ sessionToken: "1627384950",
+ windowId: "42"
});
connectionFailureAction = new sharedActions.ConnectionFailure({
reason: "FAIL"
});
});
it("should store the failure reason", function() {
@@ -1230,56 +1223,50 @@ describe("loop.store.ActiveRoomStore", f
});
it("should reset the multiplexGum", function() {
store.connectionFailure(connectionFailureAction);
sinon.assert.calledOnce(fakeMultiplexGum.reset);
});
- it("should set screen sharing inactive", function() {
- store.setStoreState({ windowId: "1234" });
-
- store.connectionFailure(connectionFailureAction);
-
- sinon.assert.calledOnce(requestStubs.SetScreenShareState);
- sinon.assert.calledWithExactly(requestStubs.SetScreenShareState, "1234", false);
- });
-
it("should disconnect from the servers via the sdk", function() {
store.connectionFailure(connectionFailureAction);
sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
});
it("should clear any existing timeout", function() {
sandbox.stub(window, "clearTimeout");
store._timeout = {};
store.connectionFailure(connectionFailureAction);
sinon.assert.calledOnce(clearTimeout);
});
- it("should call mozLoop.rooms.leave", function() {
+ it("should call 'HangupNow' Loop API", function() {
store.connectionFailure(connectionFailureAction);
- sinon.assert.calledOnce(requestStubs["Rooms:Leave"]);
- sinon.assert.calledWithExactly(requestStubs["Rooms:Leave"],
- "fakeToken", "1627384950");
+ sinon.assert.calledOnce(requestStubs["HangupNow"]);
+ sinon.assert.calledWithExactly(requestStubs["HangupNow"],
+ "fakeToken", "42");
});
it("should remove the sharing listener", function() {
+ sandbox.stub(loop, "unsubscribe");
+
// Setup the listener.
store.startBrowserShare(new sharedActions.StartBrowserShare());
// Now simulate connection failure.
store.connectionFailure(connectionFailureAction);
- sinon.assert.calledOnce(requestStubs.RemoveBrowserSharingListener);
+ sinon.assert.calledOnce(loop.unsubscribe);
+ sinon.assert.calledWith(loop.unsubscribe, "BrowserSwitch");
});
it("should set the state to `FAILED`", function() {
store.connectionFailure(connectionFailureAction);
expect(store.getStoreState().roomState).eql(ROOM_STATES.FAILED);
});
@@ -1815,43 +1802,42 @@ describe("loop.store.ActiveRoomStore", f
sandbox.stub(window, "clearTimeout");
store._timeout = {};
store.windowUnload();
sinon.assert.calledOnce(clearTimeout);
});
- it("should call mozLoop.rooms.leave", function() {
+ it("should not call 'HangupNow' Loop API", function() {
store.windowUnload();
- sinon.assert.calledOnce(requestStubs["Rooms:Leave"]);
- sinon.assert.calledWithExactly(requestStubs["Rooms:Leave"],
- "fakeToken", "1627384950");
+ sinon.assert.notCalled(requestStubs["HangupNow"]);
});
- it("should call mozLoop.rooms.leave if the room state is JOINING",
+ it("should call not call 'HangupNow' Loop API if the room state is JOINING",
function() {
store.setStoreState({ roomState: ROOM_STATES.JOINING });
store.windowUnload();
- sinon.assert.calledOnce(requestStubs["Rooms:Leave"]);
- sinon.assert.calledWithExactly(requestStubs["Rooms:Leave"],
- "fakeToken", "1627384950");
+ sinon.assert.notCalled(requestStubs["HangupNow"]);
});
it("should remove the sharing listener", function() {
+ sandbox.stub(loop, "unsubscribe");
+
// Setup the listener.
store.startBrowserShare(new sharedActions.StartBrowserShare());
// Now unload the window.
store.windowUnload();
- sinon.assert.calledOnce(requestStubs.RemoveBrowserSharingListener);
+ sinon.assert.calledOnce(loop.unsubscribe);
+ sinon.assert.calledWith(loop.unsubscribe, "BrowserSwitch");
});
it("should set the state to CLOSING", function() {
store.windowUnload();
expect(store._storeState.roomState).eql(ROOM_STATES.CLOSING);
});
});
@@ -1881,32 +1867,33 @@ describe("loop.store.ActiveRoomStore", f
sandbox.stub(window, "clearTimeout");
store._timeout = {};
store.leaveRoom();
sinon.assert.calledOnce(clearTimeout);
});
- it("should call mozLoop.rooms.leave", function() {
+ it("should not call 'HangupNow' Loop API", function() {
store.leaveRoom();
- sinon.assert.calledOnce(requestStubs["Rooms:Leave"]);
- sinon.assert.calledWithExactly(requestStubs["Rooms:Leave"],
- "fakeToken", "1627384950");
+ sinon.assert.notCalled(requestStubs["HangupNow"]);
});
it("should remove the sharing listener", function() {
+ sandbox.stub(loop, "unsubscribe");
+
// Setup the listener.
store.startBrowserShare(new sharedActions.StartBrowserShare());
// Now leave the room.
store.leaveRoom();
- sinon.assert.calledOnce(requestStubs.RemoveBrowserSharingListener);
+ sinon.assert.calledOnce(loop.unsubscribe);
+ sinon.assert.calledWith(loop.unsubscribe, "BrowserSwitch");
});
it("should set the state to ENDED", function() {
store.leaveRoom();
expect(store._storeState.roomState).eql(ROOM_STATES.ENDED);
});
--- a/browser/extensions/loop/test/xpcshell/test_loopapi_internal.js
+++ b/browser/extensions/loop/test/xpcshell/test_loopapi_internal.js
@@ -55,14 +55,48 @@ add_test(function test_handleMessage() {
Assert.strictEqual(result, true);
callCount++;
});
Assert.strictEqual(callCount, 2, "The reply handler should've been called");
run_next_test();
});
+add_test(function test_sendMessageToHandler() {
+ // Testing error branches.
+ LoopAPI.sendMessageToHandler({
+ name: "WellThisDoesNotExist"
+ }, err => {
+ Assert.ok(err.isError, "An error should be returned");
+ Assert.strictEqual(err.message,
+ "Ouch, no message handler available for 'WellThisDoesNotExist'",
+ "Error messages should match");
+ });
+
+ // Testing correct flow branches.
+ let hangupNowCalls = [];
+ LoopAPI.stubMessageHandlers({
+ HangupNow: function(message, reply) {
+ hangupNowCalls.push(message);
+ reply();
+ }
+ });
+
+ let message = {
+ name: "HangupNow",
+ data: ["fakeToken", 42]
+ };
+ LoopAPI.sendMessageToHandler(message);
+
+ Assert.strictEqual(hangupNowCalls.length, 1, "HangupNow handler should be called once");
+ Assert.deepEqual(hangupNowCalls.pop(), message, "Messages should be the same");
+
+ LoopAPI.restore();
+
+ run_next_test();
+});
+
function run_test() {
do_register_cleanup(function() {
LoopAPI.destroy();
});
run_next_test();
}