diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index dbfc9473ce89..070c35c582f7 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1764,8 +1764,10 @@ pref("media.gmp-gmpopenh264.provider.enabled", true); pref("browser.apps.URL", "https://marketplace.firefox.com/discovery/"); +#ifdef NIGHTLY_BUILD pref("browser.polaris.enabled", false); pref("privacy.trackingprotection.ui.enabled", false); +#endif // Temporary pref to allow printing in e10s windows on some platforms. #ifdef UNIX_BUT_NOT_MAC diff --git a/browser/components/loop/LoopCalls.jsm b/browser/components/loop/LoopCalls.jsm index d83e7f61ae81..d95878a1b0a8 100644 --- a/browser/components/loop/LoopCalls.jsm +++ b/browser/components/loop/LoopCalls.jsm @@ -250,6 +250,8 @@ let LoopCallsInternal = { respData.calls.forEach((callData) => { if (!this.callsData.inUse) { callData.sessionType = sessionType; + // XXX Bug 1090209 will transiton into a better window id. + callData.windowId = callData.callId; this._startCall(callData, "incoming"); } else { this._returnBusy(callData); @@ -277,7 +279,7 @@ let LoopCallsInternal = { null, // No title, let the page set that, to avoid flickering. "", - "about:loopconversation#" + conversationType + "/" + callData.callId); + "about:loopconversation#" + conversationType + "/" + callData.windowId); }, /** @@ -295,7 +297,7 @@ let LoopCallsInternal = { contact: contact, callType: callType, // XXX Really we shouldn't be using random numbers, bug 1090209 will fix this. - callId: Math.floor((Math.random() * 100000000)) + windowId: Math.floor((Math.random() * 100000000)) }; this._startCall(callData, "outgoing"); @@ -339,17 +341,17 @@ this.LoopCalls = { }, /** - * Returns the callData for a specific loopCallId + * Returns the callData for a specific conversation window id. * * The data was retrieved from the LoopServer via a GET/calls/ request * triggered by an incoming message from the LoopPushServer. * - * @param {int} loopCallId + * @param {Number} conversationWindowId * @return {callData} The callData or undefined if error. */ - getCallData: function(loopCallId) { + getCallData: function(conversationWindowId) { if (LoopCallsInternal.callsData.data && - LoopCallsInternal.callsData.data.callId == loopCallId) { + LoopCallsInternal.callsData.data.windowId == conversationWindowId) { return LoopCallsInternal.callsData.data; } else { return undefined; @@ -357,15 +359,15 @@ this.LoopCalls = { }, /** - * Releases the callData for a specific loopCallId + * Releases the callData for a specific conversation window id. * * The result of this call will be a free call session slot. * - * @param {int} loopCallId + * @param {Number} conversationWindowId */ - releaseCallData: function(loopCallId) { + releaseCallData: function(conversationWindowId) { if (LoopCallsInternal.callsData.data && - LoopCallsInternal.callsData.data.callId == loopCallId) { + LoopCallsInternal.callsData.data.windowId == conversationWindowId) { LoopCallsInternal.callsData.data = undefined; LoopCallsInternal.callsData.inUse = false; } diff --git a/browser/components/loop/LoopRooms.jsm b/browser/components/loop/LoopRooms.jsm index 0b728800be17..8fdeb51750f8 100644 --- a/browser/components/loop/LoopRooms.jsm +++ b/browser/components/loop/LoopRooms.jsm @@ -5,339 +5,222 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; -Cu.import("resource://gre/modules/Task.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "MozLoopService", - "resource:///modules/loop/MozLoopService.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "LOOP_SESSION_TYPE", - "resource:///modules/loop/MozLoopService.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "MozLoopPushHandler", - "resource:///modules/loop/MozLoopPushHandler.jsm"); +const {MozLoopService, LOOP_SESSION_TYPE} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}); +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() { + const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {}); + return new EventEmitter(); +}); this.EXPORTED_SYMBOLS = ["LoopRooms", "roomsPushNotification"]; -let gRoomsListFetched = false; -let gRooms = new Map(); -let gCallbacks = new Map(); - - /** - * Callback used to indicate changes to rooms data on the LoopServer. - * - * @param {Object} version Version number assigned to this change set. - * @param {Object} channelID Notification channel identifier. - * - */ const roomsPushNotification = function(version, channelID) { - return LoopRoomsInternal.onNotification(version, channelID); - }; - -let LoopRoomsInternal = { - getAll: function(callback) { - Task.spawn(function*() { - yield MozLoopService.register(); - - if (gRoomsListFetched) { - callback(null, [...gRooms.values()]); - return; - } - // Fetch the rooms from the server. - let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA : - LOOP_SESSION_TYPE.GUEST; - let rooms = yield this.requestRoomList(sessionType); - // Add each room to our in-memory Map using a locally unique - // identifier. - for (let room of rooms) { - let id = MozLoopService.generateLocalID(); - room.localRoomId = id; - // Next, request the detailed information for each room. - // If the request fails the room data will not be added to the map. - try { - let details = yield this.requestRoomDetails(room.roomToken, sessionType); - for (let attr in details) { - room[attr] = details[attr] - } - delete room.currSize; //This attribute will be eliminated in the next revision. - gRooms.set(id, room); - } - catch (error) {MozLoopService.log.warn( - "failed GETing room details for roomToken = " + room.roomToken + ": ", error)} - } - callback(null, [...gRooms.values()]); - return; - }.bind(this)).catch((error) => {MozLoopService.log.error("getAll error:", error); - callback(error)}); - return; - }, - - getRoomData: function(localRoomId, callback) { - if (gRooms.has(localRoomId)) { - callback(null, gRooms.get(localRoomId)); - } else { - callback(new Error("Room data not found or not fetched yet for room with ID " + localRoomId)); - } - return; - }, - - /** - * Request list of all rooms associated with this account. - * - * @param {String} sessionType Indicates which hawkRequest endpoint to use. - * - * @returns {Promise} room list - */ - requestRoomList: function(sessionType) { - return MozLoopService.hawkRequest(sessionType, "/rooms", "GET") - .then(response => { - let roomsList = JSON.parse(response.body); - if (!Array.isArray(roomsList)) { - // Force a reject in the returned promise. - // To be caught by the caller using the returned Promise. - throw new Error("Missing array of rooms in response."); - } - return roomsList; - }); - }, - - /** - * Request information about a specific room from the server. - * - * @param {Object} token Room identifier returned from the LoopServer. - * @param {String} sessionType Indicates which hawkRequest endpoint to use. - * - * @returns {Promise} room details - */ - requestRoomDetails: function(token, sessionType) { - return MozLoopService.hawkRequest(sessionType, "/rooms/" + token, "GET") - .then(response => JSON.parse(response.body)); - }, - - /** - * Callback used to indicate changes to rooms data on the LoopServer. - * - * @param {Object} version Version number assigned to this change set. - * @param {Object} channelID Notification channel identifier. - * - */ - onNotification: function(version, channelID) { - return; - }, - - createRoom: function(props, callback) { - // Always create a basic room record and launch the window, attaching - // the localRoomId. Later errors will be returned via the registered callback. - let localRoomId = MozLoopService.generateLocalID((id) => {gRooms.has(id)}) - let room = {localRoomId : localRoomId}; - for (let prop in props) { - room[prop] = props[prop] - } - - gRooms.set(localRoomId, room); - this.addCallback(localRoomId, "RoomCreated", callback); - MozLoopService.openChatWindow(null, "", "about:loopconversation#room/" + localRoomId); - - if (!"roomName" in props || - !"expiresIn" in props || - !"roomOwner" in props || - !"maxSize" in props) { - this.postCallback(localRoomId, "RoomCreated", - new Error("missing required room create property")); - return localRoomId; - } - - let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA : - LOOP_SESSION_TYPE.GUEST; - - MozLoopService.hawkRequest(sessionType, "/rooms", "POST", props).then( - (response) => { - let data = JSON.parse(response.body); - for (let attr in data) { - room[attr] = data[attr] - } - delete room.expiresIn; //Do not keep this value - it is a request to the server - this.postCallback(localRoomId, "RoomCreated", null, room); - }, - (error) => { - this.postCallback(localRoomId, "RoomCreated", error); - }); - - return localRoomId; - }, - - /** - * Send an update to the callbacks registered for a specific localRoomId - * for a callback type. - * - * The result set is always saved. Then each - * callback function that has been registered when this function is - * called will be called with the result set. Any new callback that - * is regsitered via addCallback will receive a copy of the last - * saved result set when registered. This allows the posting operation - * to complete before the callback is registered in an asynchronous - * operation. - * - * Callbacsk must be of the form: - * function (error, success) {...} - * - * @param {String} localRoomId Local room identifier. - * @param {String} callbackName callback type - * @param {?Error} error result or null. - * @param {?Object} success result if error argument is null. - */ - postCallback: function(localRoomId, callbackName, error, success) { - let roomCallbacks = gCallbacks.get(localRoomId); - if (!roomCallbacks) { - // No callbacks have been registered or results posted for this room. - // Initialize a record for this room and callbackName, saving the - // result set. - gCallbacks.set(localRoomId, new Map([[ - callbackName, - { callbackList: [], result: { error: error, success: success } }]])); - return; - } - - let namedCallback = roomCallbacks.get(callbackName); - // A callback of this name has not been registered. - if (!namedCallback) { - roomCallbacks.set( - callbackName, - {callbackList: [], result: {error: error, success: success}}); - return; - } - - // Record the latest result set. - namedCallback.result = {error: error, success: success}; - - // Call each registerd callback passing the new result posted. - namedCallback.callbackList.forEach((callback) => { - callback(error, success); - }); - }, - - addCallback: function(localRoomId, callbackName, callback) { - let roomCallbacks = gCallbacks.get(localRoomId); - if (!roomCallbacks) { - // No callbacks have been registered or results posted for this room. - // Initialize a record for this room and callbackName. - gCallbacks.set(localRoomId, new Map([[ - callbackName, - {callbackList: [callback]}]])); - return; - } - - let namedCallback = roomCallbacks.get(callbackName); - // A callback of this name has not been registered. - if (!namedCallback) { - roomCallbacks.set( - callbackName, - {callbackList: [callback]}); - return; - } - - // Add this callback if not already in the array - if (namedCallback.callbackList.indexOf(callback) >= 0) { - return; - } - namedCallback.callbackList.push(callback); - - // If a result has been posted for this callback - // send it using this new callback function. - let result = namedCallback.result; - if (result) { - callback(result.error, result.success); - } - }, - - deleteCallback: function(localRoomId, callbackName, callback) { - let roomCallbacks = gCallbacks.get(localRoomId); - if (!roomCallbacks) { - return; - } - - let namedCallback = roomCallbacks.get(callbackName); - if (!namedCallback) { - return; - } - - let i = namedCallback.callbackList.indexOf(callback); - if (i >= 0) { - namedCallback.callbackList.splice(i, 1); - } - - return; - }, + return LoopRoomsInternal.onNotification(version, channelID); }; -Object.freeze(LoopRoomsInternal); + +// Since the LoopRoomsInternal.rooms map as defined below is a local cache of +// room objects that are retrieved from the server, this is list may become out +// of date. The Push server may notify us of this event, which will set the global +// 'dirty' flag to TRUE. +let gDirty = true; /** - * The LoopRooms class. + * Extend a `target` object with the properties defined in `source`. + * + * @param {Object} target The target object to receive properties defined in `source` + * @param {Object} source The source object to copy properties from + */ +const extend = function(target, source) { + for (let key of Object.getOwnPropertyNames(source)) { + target[key] = source[key]; + } + return target; +}; + +/** + * The Rooms class. * * Each method that is a member of this class requires the last argument to be a * callback Function. MozLoopAPI will cause things to break if this invariant is * violated. You'll notice this as well in the documentation for each method. */ -this.LoopRooms = { +let LoopRoomsInternal = { + rooms: new Map(), + /** * Fetch a list of rooms that the currently registered user is a member of. * + * @param {String} [version] If set, we will fetch a list of changed rooms since + * `version`. Optional. + * @param {Function} callback Function that will be invoked once the operation + * finished. The first argument passed will be an + * `Error` object or `null`. The second argument will + * be the list of rooms, if it was fetched successfully. + */ + getAll: function(version = null, callback) { + if (!callback) { + callback = version; + version = null; + } + + Task.spawn(function* () { + yield MozLoopService.register(); + + if (!gDirty) { + callback(null, [...this.rooms.values()]); + return; + } + + // Fetch the rooms from the server. + let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA : + LOOP_SESSION_TYPE.GUEST; + let url = "/rooms" + (version ? "?version=" + encodeURIComponent(version) : ""); + let response = yield MozLoopService.hawkRequest(sessionType, url, "GET"); + let roomsList = JSON.parse(response.body); + if (!Array.isArray(roomsList)) { + throw new Error("Missing array of rooms in response."); + } + + // Next, request the detailed information for each room. If the request + // fails the room data will not be added to the map. + for (let room of roomsList) { + this.rooms.set(room.roomToken, room); + yield LoopRooms.promise("get", room.roomToken); + } + + // Set the 'dirty' flag back to FALSE, since the list is as fresh as can be now. + gDirty = false; + callback(null, [...this.rooms.values()]); + }.bind(this)).catch(error => { + callback(error); + }); + }, + + /** + * Request information about a specific room from the server. It will be + * returned from the cache if it's already in it. + * + * @param {String} roomToken Room identifier + * @param {Function} callback Function that will be invoked once the operation + * finished. The first argument passed will be an + * `Error` object or `null`. The second argument will + * be the list of rooms, if it was fetched successfully. + */ + get: function(roomToken, callback) { + let room = this.rooms.has(roomToken) ? this.rooms.get(roomToken) : {}; + // Check if we need to make a request to the server to collect more room data. + if (!room || gDirty || !("participants" in room)) { + let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA : + LOOP_SESSION_TYPE.GUEST; + MozLoopService.hawkRequest(sessionType, "/rooms/" + encodeURIComponent(roomToken), "GET") + .then(response => { + let eventName = ("roomToken" in room) ? "add" : "update"; + extend(room, JSON.parse(response.body)); + // Remove the `currSize` for posterity. + if ("currSize" in room) { + delete room.currSize; + } + this.rooms.set(roomToken, room); + + eventEmitter.emit(eventName, room); + callback(null, room); + }, err => callback(err)).catch(err => callback(err)); + } else { + callback(null, room); + } + }, + + /** + * Create a room. + * + * @param {Object} room Properties to be sent to the LoopServer * @param {Function} callback Function that will be invoked once the operation * finished. The first argument passed will be an * `Error` object or `null`. The second argument will - * be the list of rooms, if it was fetched successfully. + * be the room, if it was created successfully. */ - getAll: function(callback) { - return LoopRoomsInternal.getAll(callback); + create: function(room, callback) { + if (!("roomName" in room) || !("expiresIn" in room) || + !("roomOwner" in room) || !("maxSize" in room)) { + callback(new Error("Missing required property to create a room")); + return; + } + + let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA : + LOOP_SESSION_TYPE.GUEST; + + MozLoopService.hawkRequest(sessionType, "/rooms", "POST", room) + .then(response => { + let data = JSON.parse(response.body); + extend(room, data); + // Do not keep this value - it is a request to the server. + delete room.expiresIn; + this.rooms.set(room.roomToken, room); + + eventEmitter.emit("add", room); + callback(null, room); + }, error => callback(error)).catch(error => callback(error)); }, /** - * Return the current stored version of the data for the indicated room. + * Callback used to indicate changes to rooms data on the LoopServer. * - * @param {String} localRoomId Local room identifier - * @param {Function} callback Function that will be invoked once the operation - * finished. The first argument passed will be an - * `Error` object or `null`. The second argument will - * be the list of rooms, if it was fetched successfully. + * @param {String} version Version number assigned to this change set. + * @param {String} channelID Notification channel identifier. */ - getRoomData: function(localRoomId, callback) { - return LoopRoomsInternal.getRoomData(localRoomId, callback); - }, - - /** - * Create a room. Will both open a chat window for the new room - * and perform an exchange with the LoopServer to create the room. - * for a callback type. Callback must be of the form: - * function (error, success) {...} - * - * @param {Object} room properties to be sent to the LoopServer - * @param {Function} callback Must be of the form: function (error, success) {...} - * - * @returns {String} localRoomId assigned to this new room. - */ - createRoom: function(roomProps, callback) { - return LoopRoomsInternal.createRoom(roomProps, callback); - }, - - /** - * Register a callback of a specified type with a localRoomId. - * - * @param {String} localRoomId Local room identifier. - * @param {String} callbackName callback type - * @param {Function} callback Must be of the form: function (error, success) {...} - */ - addCallback: function(localRoomId, callbackName, callback) { - return LoopRoomsInternal.addCallback(localRoomId, callbackName, callback); - }, - - /** - * Un-register and delete a callback of a specified type for a localRoomId. - * - * @param {String} localRoomId Local room identifier. - * @param {String} callbackName callback type - * @param {Function} callback Previously passed to addCallback(). - */ - deleteCallback: function(localRoomId, callbackName, callback) { - return LoopRoomsInternal.deleteCallback(localRoomId, callbackName, callback); + onNotification: function(version, channelID) { + gDirty = true; + this.getAll(version, () => {}); }, }; -Object.freeze(LoopRooms); +Object.freeze(LoopRoomsInternal); + +/** + * Public Loop Rooms API. + * + * LoopRooms implements the EventEmitter interface by exposing three methods - + * `on`, `once` and `off` - to subscribe to events. + * At this point the following events may be subscribed to: + * - 'add': A new room object was successfully added to the data store. + * - 'remove': A room was successfully removed from the data store. + * - 'update': A room object was successfully updated with changed + * properties in the data store. + * + * See the internal code for the API documentation. + */ +this.LoopRooms = { + getAll: function(version, callback) { + return LoopRoomsInternal.getAll(version, callback); + }, + + get: function(roomToken, callback) { + return LoopRoomsInternal.get(roomToken, callback); + }, + + create: function(options, callback) { + return LoopRoomsInternal.create(options, callback); + }, + + promise: function(method, ...params) { + return new Promise((resolve, reject) => { + this[method](...params, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + }, + + on: (...params) => eventEmitter.on(...params), + + once: (...params) => eventEmitter.once(...params), + + off: (...params) => eventEmitter.off(...params) +}; +Object.freeze(this.LoopRooms); diff --git a/browser/components/loop/MozLoopAPI.jsm b/browser/components/loop/MozLoopAPI.jsm index 71a799ba524c..fb386983eff4 100644 --- a/browser/components/loop/MozLoopAPI.jsm +++ b/browser/components/loop/MozLoopAPI.jsm @@ -75,6 +75,14 @@ const cloneValueInto = function(value, targetWindow) { return value; } + // Strip Function properties, since they can not be cloned across boundaries + // like this. + for (let prop of Object.getOwnPropertyNames(value)) { + if (typeof value[prop] == "function") { + delete value[prop]; + } + } + // Inspect for an error this way, because the Error object is special. if (value.constructor.name == "Error") { return cloneErrorObject(value, targetWindow); @@ -176,8 +184,10 @@ function injectLoopAPI(targetWindow) { } // We have to clone the error property since it may be an Error object. + if (error.hasOwnProperty("toString")) { + delete error.toString; + } errors[type] = Cu.cloneInto(error, targetWindow); - } return Cu.cloneInto(errors, targetWindow); }, @@ -196,34 +206,34 @@ function injectLoopAPI(targetWindow) { }, /** - * Returns the callData for a specific callDataId + * Returns the callData for a specific conversation window id. * * The data was retrieved from the LoopServer via a GET/calls/ request * triggered by an incoming message from the LoopPushServer. * - * @param {int} loopCallId + * @param {Number} conversationWindowId * @returns {callData} The callData or undefined if error. */ getCallData: { enumerable: true, writable: true, - value: function(loopCallId) { - return Cu.cloneInto(LoopCalls.getCallData(loopCallId), targetWindow); + value: function(conversationWindowId) { + return Cu.cloneInto(LoopCalls.getCallData(conversationWindowId), targetWindow); } }, /** - * Releases the callData for a specific loopCallId + * Releases the callData for a specific conversation window id. * * The result of this call will be a free call session slot. * - * @param {int} loopCallId + * @param {Number} conversationWindowId */ releaseCallData: { enumerable: true, writable: true, - value: function(loopCallId) { - LoopCalls.releaseCallData(loopCallId); + value: function(conversationWindowId) { + LoopCalls.releaseCallData(conversationWindowId); } }, diff --git a/browser/components/loop/content/js/conversation.js b/browser/components/loop/content/js/conversation.js index 51eb49ac665a..b49d08f377aa 100644 --- a/browser/components/loop/content/js/conversation.js +++ b/browser/components/loop/content/js/conversation.js @@ -352,7 +352,7 @@ loop.conversation = (function(mozL10n) { setupIncomingCall: function() { navigator.mozLoop.startAlerting(); - var callData = navigator.mozLoop.getCallData(this.props.conversation.get("callId")); + var callData = navigator.mozLoop.getCallData(this.props.conversation.get("windowId")); if (!callData) { // XXX Not the ideal response, but bug 1047410 will be replacing // this by better "call failed" UI. @@ -374,7 +374,7 @@ loop.conversation = (function(mozL10n) { * Moves the call to the end state */ endCall: function() { - navigator.mozLoop.releaseCallData(this.props.conversation.get("callId")); + navigator.mozLoop.releaseCallData(this.props.conversation.get("windowId")); this.setState({callStatus: "end"}); }, @@ -475,7 +475,7 @@ loop.conversation = (function(mozL10n) { */ _declineCall: function() { this._websocket.decline(); - navigator.mozLoop.releaseCallData(this.props.conversation.get("callId")); + navigator.mozLoop.releaseCallData(this.props.conversation.get("windowId")); this._websocket.close(); // Having a timeout here lets the logging for the websocket complete and be // displayed on the console if both are on. @@ -618,10 +618,10 @@ loop.conversation = (function(mozL10n) { {sdk: window.OT} // Model dependencies ); - // Obtain the callId and pass it through + // Obtain the windowId and pass it through var helper = new loop.shared.utils.Helper(); var locationHash = helper.locationData().hash; - var callId; + var windowId; var outgoing; var localRoomStore; @@ -633,7 +633,7 @@ loop.conversation = (function(mozL10n) { var hash = locationHash.match(/#incoming\/(.*)/); if (hash) { - callId = hash[1]; + windowId = hash[1]; outgoing = false; } else if (hash = locationHash.match(/#room\/(.*)/)) { localRoomStore = new loop.store.LocalRoomStore({ @@ -643,16 +643,16 @@ loop.conversation = (function(mozL10n) { } else { hash = locationHash.match(/#outgoing\/(.*)/); if (hash) { - callId = hash[1]; + windowId = hash[1]; outgoing = true; } } - conversation.set({callId: callId}); + conversation.set({windowId: windowId}); window.addEventListener("unload", function(event) { // Handle direct close of dialog box via [x] control. - navigator.mozLoop.releaseCallData(callId); + navigator.mozLoop.releaseCallData(windowId); }); React.renderComponent(AppControllerView({ @@ -671,7 +671,7 @@ loop.conversation = (function(mozL10n) { } dispatcher.dispatch(new loop.shared.actions.GatherCallData({ - callId: callId, + windowId: windowId, outgoing: outgoing })); } diff --git a/browser/components/loop/content/js/conversation.jsx b/browser/components/loop/content/js/conversation.jsx index bdcc14502c25..66e745c3844d 100644 --- a/browser/components/loop/content/js/conversation.jsx +++ b/browser/components/loop/content/js/conversation.jsx @@ -352,7 +352,7 @@ loop.conversation = (function(mozL10n) { setupIncomingCall: function() { navigator.mozLoop.startAlerting(); - var callData = navigator.mozLoop.getCallData(this.props.conversation.get("callId")); + var callData = navigator.mozLoop.getCallData(this.props.conversation.get("windowId")); if (!callData) { // XXX Not the ideal response, but bug 1047410 will be replacing // this by better "call failed" UI. @@ -374,7 +374,7 @@ loop.conversation = (function(mozL10n) { * Moves the call to the end state */ endCall: function() { - navigator.mozLoop.releaseCallData(this.props.conversation.get("callId")); + navigator.mozLoop.releaseCallData(this.props.conversation.get("windowId")); this.setState({callStatus: "end"}); }, @@ -475,7 +475,7 @@ loop.conversation = (function(mozL10n) { */ _declineCall: function() { this._websocket.decline(); - navigator.mozLoop.releaseCallData(this.props.conversation.get("callId")); + navigator.mozLoop.releaseCallData(this.props.conversation.get("windowId")); this._websocket.close(); // Having a timeout here lets the logging for the websocket complete and be // displayed on the console if both are on. @@ -618,10 +618,10 @@ loop.conversation = (function(mozL10n) { {sdk: window.OT} // Model dependencies ); - // Obtain the callId and pass it through + // Obtain the windowId and pass it through var helper = new loop.shared.utils.Helper(); var locationHash = helper.locationData().hash; - var callId; + var windowId; var outgoing; var localRoomStore; @@ -633,7 +633,7 @@ loop.conversation = (function(mozL10n) { var hash = locationHash.match(/#incoming\/(.*)/); if (hash) { - callId = hash[1]; + windowId = hash[1]; outgoing = false; } else if (hash = locationHash.match(/#room\/(.*)/)) { localRoomStore = new loop.store.LocalRoomStore({ @@ -643,16 +643,16 @@ loop.conversation = (function(mozL10n) { } else { hash = locationHash.match(/#outgoing\/(.*)/); if (hash) { - callId = hash[1]; + windowId = hash[1]; outgoing = true; } } - conversation.set({callId: callId}); + conversation.set({windowId: windowId}); window.addEventListener("unload", function(event) { // Handle direct close of dialog box via [x] control. - navigator.mozLoop.releaseCallData(callId); + navigator.mozLoop.releaseCallData(windowId); }); React.renderComponent( { + res.setStatusLine(null, 200, "OK"); + res.processAsync(); + res.finish(); + }); + + loopServer.registerPathHandler("/rooms", (req, res) => { + res.setStatusLine(null, 200, "OK"); + + if (req.method == "POST") { + Assert.ok(req.bodyInputStream, "POST request should have a payload"); + let body = CommonUtils.readBytesFromInputStream(req.bodyInputStream); + let data = JSON.parse(body); + Assert.deepEqual(data, kCreateRoomProps); + + res.write(JSON.stringify(kCreateRoomData)); + } else { + res.write(JSON.stringify([...kRooms.values()])); + } + + res.processAsync(); + res.finish(); + }); + + function returnRoomDetails(res, roomName) { + roomDetail.roomName = roomName; + res.setStatusLine(null, 200, "OK"); + res.write(JSON.stringify(roomDetail)); + res.processAsync(); + res.finish(); + } + + // Add a request handler for each room in the list. + [...kRooms.values()].forEach(function(room) { + loopServer.registerPathHandler("/rooms/" + encodeURIComponent(room.roomToken), (req, res) => { + returnRoomDetails(res, room.roomName); + }); + }); + + loopServer.registerPathHandler("/rooms/error401", (req, res) => { + res.setStatusLine(null, 401, "Not Found"); + res.processAsync(); + res.finish(); + }); + + loopServer.registerPathHandler("/rooms/errorMalformed", (req, res) => { + res.setStatusLine(null, 200, "OK"); + res.write("{\"some\": \"Syntax Error!\"}}}}}}"); + res.processAsync(); + res.finish(); + }); +}); + +const normalizeRoom = function(room) { + delete room.currSize; + if (!("participants" in room)) { + let name = room.roomName; + for (let key of Object.getOwnPropertyNames(roomDetail)) { + room[key] = roomDetail[key]; + } + room.roomName = name; + } + return room; +}; + +const compareRooms = function(room1, room2) { + Assert.deepEqual(normalizeRoom(room1), normalizeRoom(room2)); +}; + +add_task(function* test_getAllRooms() { + yield MozLoopService.register(mockPushHandler); + + let rooms = yield LoopRooms.promise("getAll"); + Assert.equal(rooms.length, 3); + for (let room of rooms) { + compareRooms(kRooms.get(room.roomToken), room); + } +}); + +add_task(function* test_getRoom() { + yield MozLoopService.register(mockPushHandler); + + let roomToken = "_nxD4V4FflQ"; + let room = yield LoopRooms.promise("get", roomToken); + Assert.deepEqual(room, kRooms.get(roomToken)); +}); + +add_task(function* test_errorStates() { + yield Assert.rejects(LoopRooms.promise("get", "error401"), /Not Found/, "Fetching a non-existent room should fail"); + yield Assert.rejects(LoopRooms.promise("get", "errorMalformed"), /SyntaxError/, "Wrong message format should reject"); +}); + +add_task(function* test_createRoom() { + let eventCalled = false; + LoopRooms.once("add", (e, room) => { + compareRooms(room, kCreateRoomProps); + eventCalled = true; + }); + let room = yield LoopRooms.promise("create", kCreateRoomProps); + compareRooms(room, kCreateRoomProps); + Assert.ok(eventCalled, "Event should have fired"); +}); + +function run_test() { + setupFakeLoopServer(); + + run_next_test(); +} diff --git a/browser/components/loop/test/xpcshell/test_rooms_create.js b/browser/components/loop/test/xpcshell/test_rooms_create.js deleted file mode 100644 index 101c43bc7e98..000000000000 --- a/browser/components/loop/test/xpcshell/test_rooms_create.js +++ /dev/null @@ -1,103 +0,0 @@ -/* 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/. */ - -Cu.import("resource://services-common/utils.js"); - -XPCOMUtils.defineLazyModuleGetter(this, "Chat", - "resource:///modules/Chat.jsm"); -let hasTheseProps = function(a, b) { - for (let prop in a) { - if (a[prop] != b[prop]) { - return false; - } - } - return true; -} - -let openChatOrig = Chat.open; - -add_test(function test_openRoomsWindow() { - let roomProps = {roomName: "UX Discussion", - expiresIn: 5, - roomOwner: "Alexis", - maxSize: 2} - - let roomData = {roomToken: "_nxD4V4FflQ", - roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ", - expiresAt: 1405534180} - - loopServer.registerPathHandler("/rooms", (request, response) => { - if (!request.bodyInputStream) { - do_throw("empty request body"); - } - let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); - let data = JSON.parse(body); - do_check_true(hasTheseProps(roomProps, data)); - - response.setStatusLine(null, 200, "OK"); - response.write(JSON.stringify(roomData)); - response.processAsync(); - response.finish(); - }); - - MozLoopService.register(mockPushHandler).then(() => { - let opened = false; - let created = false; - let urlPieces = []; - - Chat.open = function(contentWindow, origin, title, url) { - urlPieces = url.split('/'); - do_check_eq(urlPieces[0], "about:loopconversation#room"); - opened = true; - }; - - let returnedID = LoopRooms.createRoom(roomProps, (error, data) => { - do_check_false(error); - do_check_true(data); - do_check_true(hasTheseProps(roomData, data)); - do_check_eq(data.localRoomId, urlPieces[1]); - created = true; - }); - - waitForCondition(function() created && opened).then(() => { - do_check_true(opened, "should open a chat window"); - do_check_eq(returnedID, urlPieces[1]); - - // Verify that a delayed callback, when attached, - // received the same data. - LoopRooms.addCallback( - urlPieces[1], "RoomCreated", - (error, data) => { - do_check_false(error); - do_check_true(data); - do_check_true(hasTheseProps(roomData, data)); - do_check_eq(data.localRoomId, urlPieces[1]); - }); - - run_next_test(); - }, () => { - do_throw("should have opened a chat window"); - }); - - }); -}); - -function run_test() -{ - setupFakeLoopServer(); - mockPushHandler.registrationPushURL = kEndPointUrl; - - loopServer.registerPathHandler("/registration", (request, response) => { - response.setStatusLine(null, 200, "OK"); - response.processAsync(); - response.finish(); - }); - - do_register_cleanup(function() { - // Revert original Chat.open implementation - Chat.open = openChatOrig; - }); - - run_next_test(); -} diff --git a/browser/components/loop/test/xpcshell/test_rooms_getdata.js b/browser/components/loop/test/xpcshell/test_rooms_getdata.js deleted file mode 100644 index d418ab83e500..000000000000 --- a/browser/components/loop/test/xpcshell/test_rooms_getdata.js +++ /dev/null @@ -1,132 +0,0 @@ -/* 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/. */ - -Cu.import("resource://services-common/utils.js"); - -XPCOMUtils.defineLazyModuleGetter(this, "Chat", - "resource:///modules/Chat.jsm"); -let hasTheseProps = function(a, b) { - for (let prop in a) { - if (a[prop] != b[prop]) { - do_print("hasTheseProps fail: prop = " + prop); - return false; - } - } - return true; -} - -let openChatOrig = Chat.open; - -add_test(function test_getAllRooms() { - - let roomList = [ - { roomToken: "_nxD4V4FflQ", - roomName: "First Room Name", - roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ", - maxSize: 2, - currSize: 0, - ctime: 1405517546 }, - { roomToken: "QzBbvGmIZWU", - roomName: "Second Room Name", - roomUrl: "http://localhost:3000/rooms/QzBbvGmIZWU", - maxSize: 2, - currSize: 0, - ctime: 140551741 }, - { roomToken: "3jKS_Els9IU", - roomName: "Third Room Name", - roomUrl: "http://localhost:3000/rooms/3jKS_Els9IU", - maxSize: 3, - clientMaxSize: 2, - currSize: 1, - ctime: 1405518241 } - ] - - let roomDetail = { - roomName: "First Room Name", - roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ", - roomOwner: "Alexis", - maxSize: 2, - clientMaxSize: 2, - creationTime: 1405517546, - expiresAt: 1405534180, - participants: [ - { displayName: "Alexis", account: "alexis@example.com", roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb" }, - { displayName: "Adam", roomConnectionId: "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7" } - ] - } - - loopServer.registerPathHandler("/rooms", (request, response) => { - response.setStatusLine(null, 200, "OK"); - response.write(JSON.stringify(roomList)); - response.processAsync(); - response.finish(); - }); - - let returnRoomDetails = function(response, roomName) { - roomDetail.roomName = roomName; - response.setStatusLine(null, 200, "OK"); - response.write(JSON.stringify(roomDetail)); - response.processAsync(); - response.finish(); - } - - loopServer.registerPathHandler("/rooms/_nxD4V4FflQ", (request, response) => { - returnRoomDetails(response, "First Room Name"); - }); - - loopServer.registerPathHandler("/rooms/QzBbvGmIZWU", (request, response) => { - returnRoomDetails(response, "Second Room Name"); - }); - - loopServer.registerPathHandler("/rooms/3jKS_Els9IU", (request, response) => { - returnRoomDetails(response, "Third Room Name"); - }); - - MozLoopService.register().then(() => { - - LoopRooms.getAll((error, rooms) => { - do_check_false(error); - do_check_true(rooms); - do_check_eq(rooms.length, 3); - do_check_eq(rooms[0].roomName, "First Room Name"); - do_check_eq(rooms[1].roomName, "Second Room Name"); - do_check_eq(rooms[2].roomName, "Third Room Name"); - - let room = rooms[0]; - do_check_true(room.localRoomId); - do_check_false(room.currSize); - delete roomList[0].currSize; - do_check_true(hasTheseProps(roomList[0], room)); - delete roomDetail.roomName; - delete room.participants; - delete roomDetail.participants; - do_check_true(hasTheseProps(roomDetail, room)); - - LoopRooms.getRoomData(room.localRoomId, (error, roomData) => { - do_check_false(error); - do_check_true(hasTheseProps(room, roomData)); - - run_next_test(); - }); - }); - }); -}); - -function run_test() { - setupFakeLoopServer(); - mockPushHandler.registrationPushURL = kEndPointUrl; - - loopServer.registerPathHandler("/registration", (request, response) => { - response.setStatusLine(null, 200, "OK"); - response.processAsync(); - response.finish(); - }); - - do_register_cleanup(function() { - // Revert original Chat.open implementation - Chat.open = openChatOrig; - }); - - run_next_test(); -} diff --git a/browser/components/loop/test/xpcshell/xpcshell.ini b/browser/components/loop/test/xpcshell/xpcshell.ini index c7377269d9a5..e43f65b64137 100644 --- a/browser/components/loop/test/xpcshell/xpcshell.ini +++ b/browser/components/loop/test/xpcshell/xpcshell.ini @@ -6,6 +6,7 @@ skip-if = toolkit == 'gonk' [test_loopapi_hawk_request.js] [test_looppush_initialize.js] +[test_looprooms.js] [test_loopservice_directcall.js] [test_loopservice_dnd.js] [test_loopservice_expiry.js] @@ -21,5 +22,3 @@ skip-if = toolkit == 'gonk' [test_loopservice_token_send.js] [test_loopservice_token_validation.js] [test_loopservice_busy.js] -[test_rooms_getdata.js] -[test_rooms_create.js] diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index b8b6f6e198c2..57f657383a75 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -401,6 +401,7 @@ BrowserGlue.prototype = { Services.obs.removeObserver(this, "browser-search-service"); this._syncSearchEngines(); break; +#ifdef NIGHTLY_BUILD case "nsPref:changed": if (data == POLARIS_ENABLED) { let enabled = Services.prefs.getBoolPref(POLARIS_ENABLED); @@ -408,6 +409,7 @@ BrowserGlue.prototype = { Services.prefs.setBoolPref("privacy.trackingprotection.enabled", enabled); Services.prefs.setBoolPref("privacy.trackingprotection.ui.enabled", enabled); } +#endif } }, diff --git a/browser/components/preferences/in-content/privacy.js b/browser/components/preferences/in-content/privacy.js index 9048d9760e64..c44787c142d5 100644 --- a/browser/components/preferences/in-content/privacy.js +++ b/browser/components/preferences/in-content/privacy.js @@ -14,6 +14,24 @@ var gPrivacyPane = { */ _shouldPromptForRestart: true, +#ifdef NIGHTLY_BUILD + /** + * Show the Tracking Protection UI depending on the + * privacy.trackingprotection.ui.enabled pref, and linkify its Learn More link + */ + _initTrackingProtection: function () { + if (!Services.prefs.getBoolPref("privacy.trackingprotection.ui.enabled")) { + return; + } + + let link = document.getElementById("trackingProtectionLearnMore"); + let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "tracking-protection"; + link.setAttribute("href", url); + + document.getElementById("trackingprotectionbox").hidden = false; + }, +#endif + /** * Sets up the UI for the number of days of history to keep, and updates the * label of the "Clear Now..." button. @@ -31,6 +49,9 @@ var gPrivacyPane = { this.updateHistoryModePane(); this.updatePrivacyMicroControls(); this.initAutoStartPrivateBrowsingReverter(); +#ifdef NIGHTLY_BUILD + this._initTrackingProtection(); +#endif setEventListener("browser.urlbar.default.behavior", "change", document.getElementById('browser.urlbar.autocomplete.enabled') diff --git a/browser/components/preferences/in-content/privacy.xul b/browser/components/preferences/in-content/privacy.xul index 973aeacac75f..639f778db0e1 100644 --- a/browser/components/preferences/in-content/privacy.xul +++ b/browser/components/preferences/in-content/privacy.xul @@ -13,6 +13,9 @@ +