Bug 1232707 - Import the latest version of the Loop system add-on. r=me for import of already reviewed code.
This commit is contained in:
57
browser/extensions/loop/bootstrap.js
vendored
57
browser/extensions/loop/bootstrap.js
vendored
@@ -3,6 +3,8 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* exported startup, shutdown, install, uninstall */
|
||||
|
||||
const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
|
||||
|
||||
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
@@ -426,7 +428,7 @@ var WindowListener = {
|
||||
}
|
||||
|
||||
let notification = new window.Notification(options.title, notificationOptions);
|
||||
notification.addEventListener("click", e => {
|
||||
notification.addEventListener("click", () => {
|
||||
if (window.closed) {
|
||||
return;
|
||||
}
|
||||
@@ -483,6 +485,7 @@ var WindowListener = {
|
||||
// Watch for title changes as opposed to location changes as more
|
||||
// metadata about the page is available when this event fires.
|
||||
gBrowser.addEventListener("DOMTitleChanged", this);
|
||||
this._browserSharePaused = false;
|
||||
}
|
||||
|
||||
this._maybeShowBrowserSharingInfoBar();
|
||||
@@ -504,6 +507,7 @@ var WindowListener = {
|
||||
gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
||||
gBrowser.removeEventListener("DOMTitleChanged", this);
|
||||
this._listeningToTabSelect = false;
|
||||
this._browserSharePaused = false;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -535,26 +539,37 @@ var WindowListener = {
|
||||
}
|
||||
|
||||
let box = gBrowser.getNotificationBox();
|
||||
let paused = false;
|
||||
let pauseButtonLabel = this._getString(this._browserSharePaused ?
|
||||
"infobar_button_resume_label" :
|
||||
"infobar_button_pause_label");
|
||||
let pauseButtonAccessKey = this._getString(this._browserSharePaused ?
|
||||
"infobar_button_resume_accesskey" :
|
||||
"infobar_button_pause_accesskey");
|
||||
let barLabel = this._getString(this._browserSharePaused ?
|
||||
"infobar_screenshare_paused_browser_message" :
|
||||
"infobar_screenshare_browser_message2");
|
||||
let bar = box.appendNotification(
|
||||
this._getString("infobar_screenshare_browser_message2"),
|
||||
barLabel,
|
||||
kBrowserSharingNotificationId,
|
||||
// Icon is defined in browser theme CSS.
|
||||
null,
|
||||
box.PRIORITY_WARNING_LOW,
|
||||
[{
|
||||
label: this._getString("infobar_button_pause_label"),
|
||||
accessKey: this._getString("infobar_button_pause_accesskey"),
|
||||
label: pauseButtonLabel,
|
||||
accessKey: pauseButtonAccessKey,
|
||||
isDefault: false,
|
||||
callback: (event, buttonInfo, buttonNode) => {
|
||||
paused = !paused;
|
||||
bar.label = paused ? this._getString("infobar_screenshare_paused_browser_message") :
|
||||
this._getString("infobar_screenshare_browser_message2");
|
||||
bar.classList.toggle("paused", paused);
|
||||
buttonNode.label = paused ? this._getString("infobar_button_resume_label") :
|
||||
this._getString("infobar_button_pause_label");
|
||||
buttonNode.accessKey = paused ? this._getString("infobar_button_resume_accesskey") :
|
||||
this._getString("infobar_button_pause_accesskey");
|
||||
this._browserSharePaused = !this._browserSharePaused;
|
||||
bar.label = this._getString(this._browserSharePaused ?
|
||||
"infobar_screenshare_paused_browser_message" :
|
||||
"infobar_screenshare_browser_message2");
|
||||
bar.classList.toggle("paused", this._browserSharePaused);
|
||||
buttonNode.label = this._getString(this._browserSharePaused ?
|
||||
"infobar_button_resume_label" :
|
||||
"infobar_button_pause_label");
|
||||
buttonNode.accessKey = this._getString(this._browserSharePaused ?
|
||||
"infobar_button_resume_accesskey" :
|
||||
"infobar_button_pause_accesskey");
|
||||
return true;
|
||||
},
|
||||
type: "pause"
|
||||
@@ -571,6 +586,9 @@ var WindowListener = {
|
||||
}]
|
||||
);
|
||||
|
||||
// Sets 'paused' class if needed.
|
||||
bar.classList.toggle("paused", !!this._browserSharePaused);
|
||||
|
||||
// Keep showing the notification bar until the user explicitly closes it.
|
||||
bar.persistence = -1;
|
||||
},
|
||||
@@ -612,7 +630,7 @@ var WindowListener = {
|
||||
* Handles events from gBrowser.
|
||||
*/
|
||||
handleEvent: function(event) {
|
||||
switch(event.type) {
|
||||
switch (event.type) {
|
||||
case "DOMTitleChanged":
|
||||
// Get the new title of the shared tab
|
||||
this._notifyBrowserSwitch();
|
||||
@@ -689,15 +707,10 @@ var WindowListener = {
|
||||
window.LoopUI = LoopUI;
|
||||
},
|
||||
|
||||
tearDownBrowserUI: function(window) {
|
||||
let document = window.document;
|
||||
|
||||
tearDownBrowserUI: function() {
|
||||
// Take any steps to remove UI or anything from the browser window
|
||||
// document.getElementById() etc. will work here
|
||||
if (window.LoopUI) {
|
||||
window.LoopUI.removeMenuItem();
|
||||
// XXX Add in tear-down of the panel.
|
||||
}
|
||||
},
|
||||
|
||||
// nsIWindowMediatorListener functions.
|
||||
@@ -717,10 +730,10 @@ var WindowListener = {
|
||||
}, false);
|
||||
},
|
||||
|
||||
onCloseWindow: function(xulWindow) {
|
||||
onCloseWindow: function() {
|
||||
},
|
||||
|
||||
onWindowTitleChange: function(xulWindow, newTitle) {
|
||||
onWindowTitleChange: function() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
const { utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
@@ -35,6 +35,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "loopCrypto",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ObjectUtils",
|
||||
"resource://gre/modules/ObjectUtils.jsm");
|
||||
|
||||
/* exported LoopRooms, roomsPushNotification */
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["LoopRooms", "roomsPushNotification"];
|
||||
|
||||
@@ -737,7 +738,7 @@ var LoopRoomsInternal = {
|
||||
let room = this.rooms.get(roomToken);
|
||||
let url = "/rooms/" + encodeURIComponent(roomToken);
|
||||
MozLoopService.hawkRequest(this.sessionType, url, "DELETE")
|
||||
.then(response => {
|
||||
.then(() => {
|
||||
this.rooms.delete(roomToken);
|
||||
eventEmitter.emit("delete", room);
|
||||
eventEmitter.emit("delete:" + room.roomToken, room);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
const { utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
@@ -394,7 +394,9 @@ const kMessageHandlers = {
|
||||
version: appInfo.version,
|
||||
OS: appInfo.OS
|
||||
};
|
||||
} catch (ex) {}
|
||||
} catch (ex) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
reply(gAppVersionInfo);
|
||||
},
|
||||
@@ -415,7 +417,7 @@ const kMessageHandlers = {
|
||||
let name = message.data[0];
|
||||
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
let url = `chrome://browser/content/loop/shared/sounds/${name}.ogg`;
|
||||
let url = `chrome://loop/content/shared/sounds/${name}.ogg`;
|
||||
|
||||
request.open("GET", url, true);
|
||||
request.responseType = "arraybuffer";
|
||||
@@ -425,7 +427,7 @@ const kMessageHandlers = {
|
||||
return;
|
||||
}
|
||||
|
||||
let blob = new Blob([request.response], {type: "audio/ogg"});
|
||||
let blob = new Blob([request.response], { type: "audio/ogg" });
|
||||
reply(blob);
|
||||
};
|
||||
|
||||
@@ -592,7 +594,7 @@ const kMessageHandlers = {
|
||||
win.messageManager.removeMessageListener("PageMetadata:PageDataResult", onPageDataResult);
|
||||
let pageData = msg.json;
|
||||
win.LoopUI.getFavicon(function(err, favicon) {
|
||||
if (err) {
|
||||
if (err && err !== "favicon not found for uri") {
|
||||
MozLoopService.log.error("Error occurred whilst fetching favicon", err);
|
||||
// We don't return here intentionally to make sure the callback is
|
||||
// invoked at all times. We just report the error here.
|
||||
@@ -624,49 +626,6 @@ const kMessageHandlers = {
|
||||
reply(gSocialProviders);
|
||||
},
|
||||
|
||||
/**
|
||||
* Compose a URL pointing to the location of an avatar by email address.
|
||||
* At the moment we use the Gravatar service to match email addresses with
|
||||
* avatars. If no email address is found we return null.
|
||||
*
|
||||
* @param {Object} message Message meant for the handler function, containing
|
||||
* the following parameters in its `data` property:
|
||||
* [
|
||||
* {String} emailAddress Users' email address
|
||||
* {Number} size Size of the avatar image
|
||||
* to return in pixels. Optional.
|
||||
* Default value: 40.
|
||||
* ]
|
||||
* @param {Function} reply Callback function, invoked with the result of this
|
||||
* message handler. The result will be sent back to
|
||||
* the senders' channel.
|
||||
* @return the URL pointing to an avatar matching the provided email address
|
||||
* or null if this is not available.
|
||||
*/
|
||||
GetUserAvatar: function(message, reply) {
|
||||
let [emailAddress, size] = message.data;
|
||||
if (!emailAddress || !MozLoopService.getLoopPref("contacts.gravatars.show")) {
|
||||
reply(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Do the MD5 dance.
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(Ci.nsICryptoHash.MD5);
|
||||
let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||
.createInstance(Ci.nsIStringInputStream);
|
||||
stringStream.data = emailAddress.trim().toLowerCase();
|
||||
hasher.updateFromStream(stringStream, -1);
|
||||
let hash = hasher.finish(false);
|
||||
// Convert the binary hash data to a hex string.
|
||||
let md5Email = Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
|
||||
|
||||
// Compose the Gravatar URL.
|
||||
reply("https://www.gravatar.com/avatar/" + md5Email +
|
||||
".jpg?default=blank&s=" + (size || 40));
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an object with data that represents the currently
|
||||
* authenticated user's identity.
|
||||
@@ -1043,58 +1002,6 @@ const kMessageHandlers = {
|
||||
reply();
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts alerting the user about an incoming call
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
StartAlerting: function(message, reply) {
|
||||
let chromeWindow = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
chromeWindow.getAttention();
|
||||
ringer = new chromeWindow.Audio();
|
||||
ringer.src = Services.prefs.getCharPref("loop.ringtone");
|
||||
ringer.loop = true;
|
||||
ringer.load();
|
||||
ringer.play();
|
||||
targetWindow.document.addEventListener("visibilitychange",
|
||||
ringerStopper = function(event) {
|
||||
if (event.currentTarget.hidden) {
|
||||
kMessageHandlers.StopAlerting();
|
||||
}
|
||||
});
|
||||
reply();
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops alerting the user about an incoming call
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
StopAlerting: function(message, reply) {
|
||||
if (!ringer) {
|
||||
reply();
|
||||
return;
|
||||
}
|
||||
if (ringerStopper) {
|
||||
ringer.ownerDocument.removeEventListener("visibilitychange",
|
||||
ringerStopper);
|
||||
ringerStopper = null;
|
||||
}
|
||||
ringer.pause();
|
||||
ringer = null;
|
||||
reply();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a value to a telemetry histogram.
|
||||
*
|
||||
@@ -1167,7 +1074,7 @@ const LoopAPIInternal = {
|
||||
} catch (ex) {
|
||||
MozLoopService.log.error("Failed to send reply back to content:", ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// First, check if this is a batch call.
|
||||
@@ -1289,7 +1196,7 @@ const LoopAPIInternal = {
|
||||
try {
|
||||
message.target.sendAsyncMessage(kPushMessageName, [pushMessagePrefix +
|
||||
prettyEventName, data]);
|
||||
} catch(ex) {
|
||||
} catch (ex) {
|
||||
MozLoopService.log.debug("Unable to send event through to target: " +
|
||||
ex.message);
|
||||
// Unregister event handlers when the message port is unreachable.
|
||||
@@ -1335,9 +1242,12 @@ const LoopAPIInternal = {
|
||||
for (let page of gPageListeners) {
|
||||
try {
|
||||
page.sendAsyncMessage(kPushMessageName, [name, data]);
|
||||
} catch (ex if ex.result == Components.results.NS_ERROR_NOT_INITIALIZED) {
|
||||
// Don't make noise when the Remote Page Manager needs more time to
|
||||
} catch (ex) {
|
||||
// Only make noise when the Remote Page Manager needs more time to
|
||||
// initialize.
|
||||
if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1349,7 +1259,9 @@ const LoopAPIInternal = {
|
||||
if (!gPageListeners) {
|
||||
return;
|
||||
}
|
||||
[for (listener of gPageListeners) listener.destroy()];
|
||||
for (let listener of gPageListeners) {
|
||||
listener.destroy();
|
||||
}
|
||||
gPageListeners = null;
|
||||
|
||||
// Unsubscribe from global events.
|
||||
|
||||
@@ -13,6 +13,8 @@ Cu.import("resource://gre/modules/Timer.jsm");
|
||||
const { MozLoopService } = Cu.import("chrome://loop/content/modules/MozLoopService.jsm", {});
|
||||
const consoleLog = MozLoopService.log;
|
||||
|
||||
/* exported MozLoopPushHandler */
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["MozLoopPushHandler"];
|
||||
|
||||
const CONNECTION_STATE_CLOSED = 0;
|
||||
@@ -556,7 +558,7 @@ var MozLoopPushHandler = {
|
||||
* This method will continually try to re-establish a connection
|
||||
* to the PushServer unless shutdown has been called.
|
||||
*/
|
||||
_onClose: function(aCode, aReason) {
|
||||
_onClose: function(aCode) {
|
||||
this._pingMonitor.stop();
|
||||
|
||||
switch (this.connectionState) {
|
||||
|
||||
@@ -4,11 +4,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
// Invalid auth token as per
|
||||
// https://github.com/mozilla-services/loop-server/blob/45787d34108e2f0d87d74d4ddf4ff0dbab23501c/loop/errno.json#L6
|
||||
const INVALID_AUTH_TOKEN = 110;
|
||||
const { interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
const LOOP_SESSION_TYPE = {
|
||||
GUEST: 1,
|
||||
@@ -201,7 +197,6 @@ var gFxAEnabled = true;
|
||||
var gFxAOAuthClientPromise = null;
|
||||
var gFxAOAuthClient = null;
|
||||
var gErrors = new Map();
|
||||
var gLastWindowId = 0;
|
||||
var gConversationWindowData = new Map();
|
||||
|
||||
/**
|
||||
@@ -1676,8 +1671,6 @@ this.MozLoopService = {
|
||||
return MozLoopServiceInternal.promiseFxAOAuthToken(response.code, response.state);
|
||||
}).then(tokenData => {
|
||||
MozLoopServiceInternal.fxAOAuthTokenData = tokenData;
|
||||
return tokenData;
|
||||
}).then(tokenData => {
|
||||
return MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
|
||||
MozLoopServiceInternal.clearError("login");
|
||||
MozLoopServiceInternal.clearError("profile");
|
||||
@@ -1768,6 +1761,13 @@ this.MozLoopService = {
|
||||
return;
|
||||
}
|
||||
let url = new URL("/settings", fxAOAuthClient.parameters.content_uri);
|
||||
|
||||
if (this.userProfile) {
|
||||
// fxA User profile is present, open settings for the correct profile. Bug: 1070208
|
||||
let fxAProfileUid = MozLoopService.userProfile.uid;
|
||||
url = new URL("/settings?uid=" + fxAProfileUid, fxAOAuthClient.parameters.content_uri);
|
||||
}
|
||||
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
win.switchToTabHavingURI(url.toString(), true);
|
||||
} catch (ex) {
|
||||
@@ -1865,7 +1865,7 @@ this.MozLoopService = {
|
||||
*/
|
||||
openURL: function(url) {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
win.openUILinkIn(url, "tab");
|
||||
win.openUILinkIn(Services.urlFormatter.formatURL(url), "tab");
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
importScripts("resource://gre/modules/osfile.jsm");
|
||||
|
||||
var File = OS.File;
|
||||
var Encoder = new TextEncoder();
|
||||
var Counter = 0;
|
||||
|
||||
@@ -49,13 +48,20 @@ onmessage = function(e) {
|
||||
// Save to disk
|
||||
let array = Encoder.encode(pingStr);
|
||||
try {
|
||||
File.makeDir(directory,
|
||||
{ unixMode: OS.Constants.S_IRWXU, ignoreExisting: true });
|
||||
File.writeAtomic(OS.Path.join(directory, filename), array);
|
||||
OS.File.makeDir(directory, {
|
||||
unixMode: OS.Constants.S_IRWXU,
|
||||
ignoreExisting: true
|
||||
});
|
||||
OS.File.writeAtomic(OS.Path.join(directory, filename), array);
|
||||
postMessage({ ok: true });
|
||||
} catch (ex if ex instanceof File.Error) {
|
||||
} catch (ex) {
|
||||
// Instances of OS.File.Error know how to serialize themselves
|
||||
postMessage({fail: File.Error.toMsg(ex)});
|
||||
if (ex instanceof OS.File.Error) {
|
||||
postMessage({ fail: OS.File.Error.toMsg(ex) });
|
||||
}
|
||||
else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
|
||||
<script type="text/javascript" src="panels/js/otconfig.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/sdk.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/react-0.13.3.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/lodash-3.9.3.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/backbone-1.2.1.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/classnames-2.2.0.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/react.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/lodash.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/backbone.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/classnames.js"></script>
|
||||
|
||||
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
|
||||
<script type="text/javascript" src="shared/js/utils.js"></script>
|
||||
|
||||
@@ -147,35 +147,6 @@ body {
|
||||
|
||||
/* Rooms CSS */
|
||||
|
||||
.no-conversations-message {
|
||||
/* example of vertical aligning a container in an element see:
|
||||
http://zerosixthree.se/vertical-align-anything-with-just-3-lines-of-css/ */
|
||||
text-align: center;
|
||||
color: #4a4a4a;
|
||||
font-weight: lighter;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding-top: 11rem;
|
||||
padding-bottom: 1rem;
|
||||
background-image: url("../../shared/img/empty_conversations.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: top center;
|
||||
}
|
||||
|
||||
.panel-text-medium,
|
||||
.panel-text-large {
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.panel-text-medium {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.panel-text-large {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.room-list-loading {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
@@ -203,18 +174,20 @@ body {
|
||||
.rooms > h1 {
|
||||
color: #666;
|
||||
font-size: 1rem;
|
||||
padding: .5rem 0;
|
||||
padding: .5rem 15px;
|
||||
height: 3rem;
|
||||
line-height: 3rem;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.new-room-view {
|
||||
border-bottom: 1px solid #d8d8d8;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.new-room-view + h1 {
|
||||
border-top: 1px solid #d8d8d8;
|
||||
}
|
||||
|
||||
.new-room-view > .btn {
|
||||
border-radius: 5px;
|
||||
font-size: 1.2rem;
|
||||
@@ -245,15 +218,6 @@ body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.room-list-empty {
|
||||
border-bottom-width: 0;
|
||||
flex: 1;
|
||||
/* the child no-conversations-message is vertical aligned inside this container
|
||||
see: http://zerosixthree.se/vertical-align-anything-with-just-3-lines-of-css/
|
||||
stops blurring from decimal pixels being rendered - pixel rounding */
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.room-list > .room-entry {
|
||||
padding: .2rem 15px;
|
||||
/* Always show the default pointer, even over the text part of the entry. */
|
||||
@@ -569,9 +533,8 @@ html[dir="rtl"] .generate-url-spinner {
|
||||
/* Sign in/up link */
|
||||
|
||||
.signin-link {
|
||||
flex: 2 1 auto;
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.signin-link > a {
|
||||
@@ -597,15 +560,6 @@ html[dir="rtl"] .generate-url-spinner {
|
||||
-moz-margin-start: .5em;
|
||||
}
|
||||
|
||||
.user-details .dropdown-menu {
|
||||
bottom: 1.3rem; /* Just above the text. */
|
||||
left: -5px; /* Compensate for button padding. */
|
||||
}
|
||||
|
||||
html[dir="rtl"] .user-details .dropdown-menu {
|
||||
right: -5px;
|
||||
}
|
||||
|
||||
.settings-menu .dropdown-menu {
|
||||
/* The panel can't have dropdown menu overflowing its iframe boudaries;
|
||||
let's anchor it from the bottom-right, while resetting the top & left values
|
||||
@@ -646,15 +600,18 @@ html[dir="rtl"] .settings-menu .dropdown-menu {
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
.footer .signin-details {
|
||||
.footer > .signin-details {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.footer .user-identity {
|
||||
.footer > .user-identity {
|
||||
flex: 1;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
margin-inline-end: 1rem;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* First time use */
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var loop = loop || {};
|
||||
loop.conversation = (function(mozL10n) {
|
||||
loop.conversation = function (mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
@@ -18,41 +18,37 @@ loop.conversation = (function(mozL10n) {
|
||||
* Master controller view for handling if incoming or outgoing calls are
|
||||
* in progress, and hence, which view to display.
|
||||
*/
|
||||
var AppControllerView = React.createClass({displayName: "AppControllerView",
|
||||
mixins: [
|
||||
Backbone.Events,
|
||||
loop.store.StoreMixin("conversationAppStore"),
|
||||
sharedMixins.DocumentTitleMixin,
|
||||
sharedMixins.WindowCloseMixin
|
||||
],
|
||||
var AppControllerView = React.createClass({
|
||||
displayName: "AppControllerView",
|
||||
|
||||
mixins: [Backbone.Events, loop.store.StoreMixin("conversationAppStore"), sharedMixins.DocumentTitleMixin, sharedMixins.WindowCloseMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore)
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
getInitialState: function () {
|
||||
return this.getStoreState();
|
||||
},
|
||||
|
||||
_renderFeedbackForm: function() {
|
||||
_renderFeedbackForm: function () {
|
||||
this.setTitle(mozL10n.get("conversation_has_ended"));
|
||||
|
||||
return (React.createElement(FeedbackView, {
|
||||
onAfterFeedbackReceived: this.closeWindow}));
|
||||
return React.createElement(FeedbackView, {
|
||||
onAfterFeedbackReceived: this.closeWindow });
|
||||
},
|
||||
|
||||
/**
|
||||
* We only show the feedback for once every 6 months, otherwise close
|
||||
* the window.
|
||||
*/
|
||||
handleCallTerminated: function() {
|
||||
handleCallTerminated: function () {
|
||||
var delta = new Date() - new Date(this.state.feedbackTimestamp);
|
||||
|
||||
// Show timestamp if feedback period (6 months) passed.
|
||||
// 0 is default value for pref. Always show feedback form on first use.
|
||||
if (this.state.feedbackTimestamp === 0 ||
|
||||
delta >= this.state.feedbackPeriod) {
|
||||
if (this.state.feedbackTimestamp === 0 || delta >= this.state.feedbackPeriod) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.ShowFeedbackForm());
|
||||
return;
|
||||
}
|
||||
@@ -60,25 +56,28 @@ loop.conversation = (function(mozL10n) {
|
||||
this.closeWindow();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render: function () {
|
||||
if (this.state.showFeedbackForm) {
|
||||
return this._renderFeedbackForm();
|
||||
}
|
||||
|
||||
switch (this.state.windowType) {
|
||||
case "room": {
|
||||
return (React.createElement(DesktopRoomConversationView, {
|
||||
case "room":
|
||||
{
|
||||
return React.createElement(DesktopRoomConversationView, {
|
||||
chatWindowDetached: this.state.chatWindowDetached,
|
||||
dispatcher: this.props.dispatcher,
|
||||
onCallTerminated: this.handleCallTerminated,
|
||||
roomStore: this.props.roomStore}));
|
||||
roomStore: this.props.roomStore });
|
||||
}
|
||||
case "failed": {
|
||||
return (React.createElement(RoomFailureView, {
|
||||
case "failed":
|
||||
{
|
||||
return React.createElement(RoomFailureView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
failureReason: FAILURE_DETAILS.UNKNOWN}));
|
||||
failureReason: FAILURE_DETAILS.UNKNOWN });
|
||||
}
|
||||
default: {
|
||||
default:
|
||||
{
|
||||
// If we don't have a windowType, we don't know what we are yet,
|
||||
// so don't display anything.
|
||||
return null;
|
||||
@@ -100,20 +99,10 @@ loop.conversation = (function(mozL10n) {
|
||||
windowId = hash[1];
|
||||
}
|
||||
|
||||
var requests = [
|
||||
["GetAllConstants"],
|
||||
["GetAllStrings"],
|
||||
["GetLocale"],
|
||||
["GetLoopPref", "ot.guid"],
|
||||
["GetLoopPref", "textChat.enabled"],
|
||||
["GetLoopPref", "feedback.periodSec"],
|
||||
["GetLoopPref", "feedback.dateLastSeenSec"]
|
||||
];
|
||||
var prefetch = [
|
||||
["GetConversationWindowData", windowId]
|
||||
];
|
||||
var requests = [["GetAllConstants"], ["GetAllStrings"], ["GetLocale"], ["GetLoopPref", "ot.guid"], ["GetLoopPref", "textChat.enabled"], ["GetLoopPref", "feedback.periodSec"], ["GetLoopPref", "feedback.dateLastSeenSec"]];
|
||||
var prefetch = [["GetConversationWindowData", windowId]];
|
||||
|
||||
return loop.requestMulti.apply(null, requests.concat(prefetch)).then(function(results) {
|
||||
return loop.requestMulti.apply(null, requests.concat(prefetch)).then(function (results) {
|
||||
// `requestIdx` is keyed off the order of the `requests` and `prefetch`
|
||||
// arrays. Be careful to update both when making changes.
|
||||
var requestIdx = 0;
|
||||
@@ -124,7 +113,7 @@ loop.conversation = (function(mozL10n) {
|
||||
var locale = results[++requestIdx];
|
||||
mozL10n.initialize({
|
||||
locale: locale,
|
||||
getStrings: function(key) {
|
||||
getStrings: function (key) {
|
||||
if (!(key in stringBundle)) {
|
||||
console.error("No string found for key: ", key);
|
||||
return "{ textContent: '' }";
|
||||
@@ -138,10 +127,10 @@ loop.conversation = (function(mozL10n) {
|
||||
// don't work in the conversation window
|
||||
var currGuid = results[++requestIdx];
|
||||
window.OT.overrideGuidStorage({
|
||||
get: function(callback) {
|
||||
get: function (callback) {
|
||||
callback(null, currGuid);
|
||||
},
|
||||
set: function(guid, callback) {
|
||||
set: function (guid, callback) {
|
||||
// See nsIPrefBranch
|
||||
var PREF_STRING = 32;
|
||||
currGuid = guid;
|
||||
@@ -177,7 +166,7 @@ loop.conversation = (function(mozL10n) {
|
||||
feedbackTimestamp: results[++requestIdx]
|
||||
});
|
||||
|
||||
prefetch.forEach(function(req) {
|
||||
prefetch.forEach(function (req) {
|
||||
req.shift();
|
||||
loop.storeRequest(req, results[++requestIdx]);
|
||||
});
|
||||
@@ -195,13 +184,12 @@ loop.conversation = (function(mozL10n) {
|
||||
textChatStore: textChatStore
|
||||
});
|
||||
|
||||
React.render(
|
||||
React.createElement(AppControllerView, {
|
||||
React.render(React.createElement(AppControllerView, {
|
||||
dispatcher: dispatcher,
|
||||
roomStore: roomStore}), document.querySelector("#main"));
|
||||
roomStore: roomStore }), document.querySelector("#main"));
|
||||
|
||||
document.documentElement.setAttribute("lang", mozL10n.getLanguage());
|
||||
document.documentElement.setAttribute("dir", mozL10n.getDirection());
|
||||
document.documentElement.setAttribute("lang", mozL10n.language.code);
|
||||
document.documentElement.setAttribute("dir", mozL10n.language.direction);
|
||||
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
|
||||
|
||||
dispatcher.dispatch(new sharedActions.GetWindowData({
|
||||
@@ -222,6 +210,6 @@ loop.conversation = (function(mozL10n) {
|
||||
*/
|
||||
_sdkDriver: null
|
||||
};
|
||||
})(document.mozL10n);
|
||||
}(document.mozL10n);
|
||||
|
||||
document.addEventListener("DOMContentLoaded", loop.conversation.init);
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var loop = loop || {};
|
||||
loop.feedbackViews = (function(_, mozL10n) {
|
||||
loop.feedbackViews = function (_, mozL10n) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Feedback view is displayed once every 6 months (loop.feedback.periodSec)
|
||||
* after a conversation has ended.
|
||||
*/
|
||||
var FeedbackView = React.createClass({displayName: "FeedbackView",
|
||||
|
||||
var FeedbackView = React.createClass({
|
||||
displayName: "FeedbackView",
|
||||
|
||||
propTypes: {
|
||||
onAfterFeedbackReceived: React.PropTypes.func.isRequired
|
||||
},
|
||||
@@ -19,26 +22,32 @@ loop.feedbackViews = (function(_, mozL10n) {
|
||||
* Pressing the button to leave feedback will open the form in a new page
|
||||
* and close the conversation window.
|
||||
*/
|
||||
onFeedbackButtonClick: function() {
|
||||
loop.request("GetLoopPref", "feedback.formURL").then(function(url) {
|
||||
onFeedbackButtonClick: function () {
|
||||
loop.request("GetLoopPref", "feedback.formURL").then(function (url) {
|
||||
loop.request("OpenURL", url).then(this.props.onAfterFeedbackReceived);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.createElement("div", {className: "feedback-view-container"},
|
||||
React.createElement("h2", {className: "feedback-heading"},
|
||||
render: function () {
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ className: "feedback-view-container" },
|
||||
React.createElement(
|
||||
"h2",
|
||||
{ className: "feedback-heading" },
|
||||
mozL10n.get("feedback_window_heading")
|
||||
),
|
||||
React.createElement("div", {className: "feedback-hello-logo"}),
|
||||
React.createElement("div", {className: "feedback-button-container"},
|
||||
React.createElement("button", {onClick: this.onFeedbackButtonClick,
|
||||
ref: "feedbackFormBtn"},
|
||||
React.createElement("div", { className: "feedback-hello-logo" }),
|
||||
React.createElement(
|
||||
"div",
|
||||
{ className: "feedback-button-container" },
|
||||
React.createElement(
|
||||
"button",
|
||||
{ onClick: this.onFeedbackButtonClick,
|
||||
ref: "feedbackFormBtn" },
|
||||
mozL10n.get("feedback_request_button")
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -46,4 +55,4 @@ loop.feedbackViews = (function(_, mozL10n) {
|
||||
return {
|
||||
FeedbackView: FeedbackView
|
||||
};
|
||||
})(_, navigator.mozL10n || document.mozL10n);
|
||||
}(_, navigator.mozL10n || document.mozL10n);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -260,9 +260,11 @@ loop.store = loop.store || {};
|
||||
this._notifications.remove("create-room-error");
|
||||
loop.request("Rooms:Create", roomCreationData).then(function(result) {
|
||||
var buckets = this._constants.ROOM_CREATE;
|
||||
if (result && result.isError) {
|
||||
if (!result || result.isError) {
|
||||
loop.request("TelemetryAddValue", "LOOP_ROOM_CREATE", buckets.CREATE_FAIL);
|
||||
this.dispatchAction(new sharedActions.CreateRoomError({ error: result }));
|
||||
this.dispatchAction(new sharedActions.CreateRoomError({
|
||||
error: result ? result : new Error("no result")
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var loop = loop || {};
|
||||
loop.roomViews = (function(mozL10n) {
|
||||
loop.roomViews = function (mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
@@ -24,20 +24,17 @@ loop.roomViews = (function(mozL10n) {
|
||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.listenTo(this.props.roomStore, "change:activeRoom",
|
||||
this._onActiveRoomStateChanged);
|
||||
this.listenTo(this.props.roomStore, "change:error",
|
||||
this._onRoomError);
|
||||
this.listenTo(this.props.roomStore, "change:savingContext",
|
||||
this._onRoomSavingContext);
|
||||
componentWillMount: function () {
|
||||
this.listenTo(this.props.roomStore, "change:activeRoom", this._onActiveRoomStateChanged);
|
||||
this.listenTo(this.props.roomStore, "change:error", this._onRoomError);
|
||||
this.listenTo(this.props.roomStore, "change:savingContext", this._onRoomSavingContext);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
componentWillUnmount: function () {
|
||||
this.stopListening(this.props.roomStore);
|
||||
},
|
||||
|
||||
_onActiveRoomStateChanged: function() {
|
||||
_onActiveRoomStateChanged: function () {
|
||||
// Only update the state if we're mounted, to avoid the problem where
|
||||
// stopListening doesn't nuke the active listeners during a event
|
||||
// processing.
|
||||
@@ -46,7 +43,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
_onRoomError: function() {
|
||||
_onRoomError: function () {
|
||||
// Only update the state if we're mounted, to avoid the problem where
|
||||
// stopListening doesn't nuke the active listeners during a event
|
||||
// processing.
|
||||
@@ -55,7 +52,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
_onRoomSavingContext: function() {
|
||||
_onRoomSavingContext: function () {
|
||||
// Only update the state if we're mounted, to avoid the problem where
|
||||
// stopListening doesn't nuke the active listeners during a event
|
||||
// processing.
|
||||
@@ -64,7 +61,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
getInitialState: function () {
|
||||
var storeState = this.props.roomStore.getStoreState("activeRoom");
|
||||
return _.extend({
|
||||
// Used by the UI showcase.
|
||||
@@ -77,7 +74,9 @@ loop.roomViews = (function(mozL10n) {
|
||||
/**
|
||||
* Used to display errors in direct calls and rooms to the user.
|
||||
*/
|
||||
var FailureInfoView = React.createClass({displayName: "FailureInfoView",
|
||||
var FailureInfoView = React.createClass({
|
||||
displayName: "FailureInfoView",
|
||||
|
||||
propTypes: {
|
||||
failureReason: React.PropTypes.string.isRequired
|
||||
},
|
||||
@@ -87,14 +86,13 @@ loop.roomViews = (function(mozL10n) {
|
||||
*
|
||||
* @return {String} The translated message for the failure reason.
|
||||
*/
|
||||
_getMessage: function() {
|
||||
_getMessage: function () {
|
||||
switch (this.props.failureReason) {
|
||||
case FAILURE_DETAILS.NO_MEDIA:
|
||||
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
||||
return mozL10n.get("no_media_failure_message");
|
||||
case FAILURE_DETAILS.TOS_FAILURE:
|
||||
return mozL10n.get("tos_failure_message",
|
||||
{ clientShortname: mozL10n.get("clientShortname2") });
|
||||
return mozL10n.get("tos_failure_message", { clientShortname: mozL10n.get("clientShortname2") });
|
||||
case FAILURE_DETAILS.ICE_FAILED:
|
||||
return mozL10n.get("ice_failure_message");
|
||||
default:
|
||||
@@ -102,11 +100,15 @@ loop.roomViews = (function(mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.createElement("div", {className: "failure-info"},
|
||||
React.createElement("div", {className: "failure-info-logo"}),
|
||||
React.createElement("h2", {className: "failure-info-message"}, this._getMessage())
|
||||
render: function () {
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ className: "failure-info" },
|
||||
React.createElement("div", { className: "failure-info-logo" }),
|
||||
React.createElement(
|
||||
"h2",
|
||||
{ className: "failure-info-message" },
|
||||
this._getMessage()
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -115,7 +117,9 @@ loop.roomViews = (function(mozL10n) {
|
||||
/**
|
||||
* Something went wrong view. Displayed when there's a big problem.
|
||||
*/
|
||||
var RoomFailureView = React.createClass({displayName: "RoomFailureView",
|
||||
var RoomFailureView = React.createClass({
|
||||
displayName: "RoomFailureView",
|
||||
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
@@ -123,19 +127,15 @@ loop.roomViews = (function(mozL10n) {
|
||||
failureReason: React.PropTypes.string
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
componentDidMount: function () {
|
||||
this.play("failure");
|
||||
},
|
||||
|
||||
handleRejoinCall: function() {
|
||||
handleRejoinCall: function () {
|
||||
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var settingsMenuItems = [
|
||||
{ id: "help" }
|
||||
];
|
||||
|
||||
render: function () {
|
||||
var btnTitle;
|
||||
if (this.props.failureReason === FAILURE_DETAILS.ICE_FAILED) {
|
||||
btnTitle = mozL10n.get("retry_call_button");
|
||||
@@ -143,24 +143,27 @@ loop.roomViews = (function(mozL10n) {
|
||||
btnTitle = mozL10n.get("rejoin_button");
|
||||
}
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: "room-failure"},
|
||||
React.createElement(FailureInfoView, {failureReason: this.props.failureReason}),
|
||||
React.createElement("div", {className: "btn-group call-action-group"},
|
||||
React.createElement("button", {className: "btn btn-info btn-rejoin",
|
||||
onClick: this.handleRejoinCall},
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ className: "room-failure" },
|
||||
React.createElement(FailureInfoView, { failureReason: this.props.failureReason }),
|
||||
React.createElement(
|
||||
"div",
|
||||
{ className: "btn-group call-action-group" },
|
||||
React.createElement(
|
||||
"button",
|
||||
{ className: "btn btn-info btn-rejoin",
|
||||
onClick: this.handleRejoinCall },
|
||||
btnTitle
|
||||
)
|
||||
),
|
||||
React.createElement(loop.shared.views.SettingsControlButton, {
|
||||
menuBelow: true,
|
||||
menuItems: settingsMenuItems})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var SocialShareDropdown = React.createClass({displayName: "SocialShareDropdown",
|
||||
var SocialShareDropdown = React.createClass({
|
||||
displayName: "SocialShareDropdown",
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
roomUrl: React.PropTypes.string,
|
||||
@@ -168,18 +171,17 @@ loop.roomViews = (function(mozL10n) {
|
||||
socialShareProviders: React.PropTypes.array
|
||||
},
|
||||
|
||||
handleAddServiceClick: function(event) {
|
||||
handleAddServiceClick: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.dispatcher.dispatch(new sharedActions.AddSocialShareProvider());
|
||||
},
|
||||
|
||||
handleProviderClick: function(event) {
|
||||
handleProviderClick: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
var origin = event.currentTarget.dataset.provider;
|
||||
var provider = this.props.socialShareProviders
|
||||
.filter(function(socialProvider) {
|
||||
var provider = this.props.socialShareProviders.filter(function (socialProvider) {
|
||||
return socialProvider.origin === origin;
|
||||
})[0];
|
||||
|
||||
@@ -190,7 +192,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render: function () {
|
||||
// Don't render a thing when no data has been fetched yet.
|
||||
if (!this.props.socialShareProviders) {
|
||||
return null;
|
||||
@@ -204,27 +206,35 @@ loop.roomViews = (function(mozL10n) {
|
||||
"hide": !this.props.show
|
||||
});
|
||||
|
||||
return (
|
||||
React.createElement("ul", {className: shareDropdown},
|
||||
React.createElement("li", {className: "dropdown-menu-item", onClick: this.handleAddServiceClick},
|
||||
React.createElement("i", {className: "icon icon-add-share-service"}),
|
||||
React.createElement("span", null, mozL10n.get("share_add_service_button"))
|
||||
return React.createElement(
|
||||
"ul",
|
||||
{ className: shareDropdown },
|
||||
React.createElement(
|
||||
"li",
|
||||
{ className: "dropdown-menu-item", onClick: this.handleAddServiceClick },
|
||||
React.createElement("i", { className: "icon icon-add-share-service" }),
|
||||
React.createElement(
|
||||
"span",
|
||||
null,
|
||||
mozL10n.get("share_add_service_button")
|
||||
)
|
||||
),
|
||||
this.props.socialShareProviders.length ? React.createElement("li", {className: "dropdown-menu-separator"}) : null,
|
||||
|
||||
this.props.socialShareProviders.map(function(provider, idx) {
|
||||
return (
|
||||
React.createElement("li", {className: "dropdown-menu-item",
|
||||
this.props.socialShareProviders.length ? React.createElement("li", { className: "dropdown-menu-separator" }) : null,
|
||||
this.props.socialShareProviders.map(function (provider, idx) {
|
||||
return React.createElement(
|
||||
"li",
|
||||
{ className: "dropdown-menu-item",
|
||||
"data-provider": provider.origin,
|
||||
key: "provider-" + idx,
|
||||
onClick: this.handleProviderClick},
|
||||
React.createElement("img", {className: "icon", src: provider.iconURL}),
|
||||
React.createElement("span", null, provider.name)
|
||||
onClick: this.handleProviderClick },
|
||||
React.createElement("img", { className: "icon", src: provider.iconURL }),
|
||||
React.createElement(
|
||||
"span",
|
||||
null,
|
||||
provider.name
|
||||
)
|
||||
);
|
||||
}.bind(this))
|
||||
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -232,7 +242,9 @@ loop.roomViews = (function(mozL10n) {
|
||||
/**
|
||||
* Desktop room invitation view (overlay).
|
||||
*/
|
||||
var DesktopRoomInvitationView = React.createClass({displayName: "DesktopRoomInvitationView",
|
||||
var DesktopRoomInvitationView = React.createClass({
|
||||
displayName: "DesktopRoomInvitationView",
|
||||
|
||||
statics: {
|
||||
TRIGGERED_RESET_DELAY: 2000
|
||||
},
|
||||
@@ -242,24 +254,20 @@ loop.roomViews = (function(mozL10n) {
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
error: React.PropTypes.object,
|
||||
onAddContextClick: React.PropTypes.func,
|
||||
onEditContextClose: React.PropTypes.func,
|
||||
// This data is supplied by the activeRoomStore.
|
||||
roomData: React.PropTypes.object.isRequired,
|
||||
savingContext: React.PropTypes.bool,
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
showEditContext: React.PropTypes.bool.isRequired,
|
||||
socialShareProviders: React.PropTypes.array
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
copiedUrl: false,
|
||||
newRoomName: ""
|
||||
};
|
||||
},
|
||||
|
||||
handleEmailButtonClick: function(event) {
|
||||
handleEmailButtonClick: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
var roomData = this.props.roomData;
|
||||
@@ -272,15 +280,14 @@ loop.roomViews = (function(mozL10n) {
|
||||
}
|
||||
}
|
||||
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.EmailRoomUrl({
|
||||
this.props.dispatcher.dispatch(new sharedActions.EmailRoomUrl({
|
||||
roomUrl: roomData.roomUrl,
|
||||
roomDescription: contextURL,
|
||||
from: "conversation"
|
||||
}));
|
||||
},
|
||||
|
||||
handleFacebookButtonClick: function(event) {
|
||||
handleFacebookButtonClick: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.dispatcher.dispatch(new sharedActions.FacebookShareRoomUrl({
|
||||
@@ -289,7 +296,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
}));
|
||||
},
|
||||
|
||||
handleCopyButtonClick: function(event) {
|
||||
handleCopyButtonClick: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.dispatcher.dispatch(new sharedActions.CopyRoomUrl({
|
||||
@@ -304,57 +311,84 @@ loop.roomViews = (function(mozL10n) {
|
||||
/**
|
||||
* Reset state of triggered buttons if necessary
|
||||
*/
|
||||
resetTriggeredButtons: function() {
|
||||
resetTriggeredButtons: function () {
|
||||
if (this.state.copiedUrl) {
|
||||
this.setState({ copiedUrl: false });
|
||||
}
|
||||
},
|
||||
|
||||
handleEditContextClose: function() {
|
||||
if (this.props.onEditContextClose) {
|
||||
this.props.onEditContextClose();
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render: function () {
|
||||
if (!this.props.show || !this.props.roomData.roomUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var cx = classNames;
|
||||
return (
|
||||
React.createElement("div", {className: "room-invitation-overlay"},
|
||||
React.createElement("div", {className: "room-invitation-content"},
|
||||
React.createElement("p", {className: cx({ hide: this.props.showEditContext })},
|
||||
mozL10n.get("invite_header_text2")
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ className: "room-invitation-overlay" },
|
||||
React.createElement(
|
||||
"div",
|
||||
{ className: "room-invitation-content" },
|
||||
React.createElement(
|
||||
"p",
|
||||
null,
|
||||
React.createElement(
|
||||
"span",
|
||||
{ className: "room-context-header" },
|
||||
mozL10n.get("invite_header_text_bold")
|
||||
),
|
||||
" ",
|
||||
React.createElement(
|
||||
"span",
|
||||
null,
|
||||
mozL10n.get("invite_header_text3")
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement("div", {className: cx({
|
||||
React.createElement(
|
||||
"div",
|
||||
{ className: cx({
|
||||
"btn-group": true,
|
||||
"call-action-group": true,
|
||||
hide: this.props.showEditContext
|
||||
})},
|
||||
React.createElement("div", {className: cx({
|
||||
"call-action-group": true
|
||||
}) },
|
||||
React.createElement(
|
||||
"div",
|
||||
{ className: cx({
|
||||
"btn-copy": true,
|
||||
"invite-button": true,
|
||||
"triggered": this.state.copiedUrl
|
||||
}),
|
||||
onClick: this.handleCopyButtonClick},
|
||||
React.createElement("img", {src: "shared/img/glyph-link-16x16.svg"}),
|
||||
React.createElement("p", null, mozL10n.get(this.state.copiedUrl ?
|
||||
"invite_copied_link_button" : "invite_copy_link_button"))
|
||||
onClick: this.handleCopyButtonClick },
|
||||
React.createElement("img", { src: "shared/img/glyph-link-16x16.svg" }),
|
||||
React.createElement(
|
||||
"p",
|
||||
null,
|
||||
mozL10n.get(this.state.copiedUrl ? "invite_copied_link_button" : "invite_copy_link_button")
|
||||
)
|
||||
),
|
||||
React.createElement("div", {className: "btn-email invite-button",
|
||||
React.createElement(
|
||||
"div",
|
||||
{ className: "btn-email invite-button",
|
||||
onClick: this.handleEmailButtonClick,
|
||||
onMouseOver: this.resetTriggeredButtons},
|
||||
React.createElement("img", {src: "shared/img/glyph-email-16x16.svg"}),
|
||||
React.createElement("p", null, mozL10n.get("invite_email_link_button"))
|
||||
onMouseOver: this.resetTriggeredButtons },
|
||||
React.createElement("img", { src: "shared/img/glyph-email-16x16.svg" }),
|
||||
React.createElement(
|
||||
"p",
|
||||
null,
|
||||
mozL10n.get("invite_email_link_button")
|
||||
)
|
||||
),
|
||||
React.createElement("div", {className: "btn-facebook invite-button",
|
||||
React.createElement(
|
||||
"div",
|
||||
{ className: "btn-facebook invite-button",
|
||||
onClick: this.handleFacebookButtonClick,
|
||||
onMouseOver: this.resetTriggeredButtons},
|
||||
React.createElement("img", {src: "shared/img/glyph-facebook-16x16.svg"}),
|
||||
React.createElement("p", null, mozL10n.get("invite_facebook_button3"))
|
||||
onMouseOver: this.resetTriggeredButtons },
|
||||
React.createElement("img", { src: "shared/img/glyph-facebook-16x16.svg" }),
|
||||
React.createElement(
|
||||
"p",
|
||||
null,
|
||||
mozL10n.get("invite_facebook_button3")
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement(SocialShareDropdown, {
|
||||
@@ -362,222 +396,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
ref: "menu",
|
||||
roomUrl: this.props.roomData.roomUrl,
|
||||
show: this.state.showMenu,
|
||||
socialShareProviders: this.props.socialShareProviders}),
|
||||
React.createElement(DesktopRoomEditContextView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
error: this.props.error,
|
||||
onClose: this.handleEditContextClose,
|
||||
roomData: this.props.roomData,
|
||||
savingContext: this.props.savingContext,
|
||||
show: this.props.showEditContext})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var DesktopRoomEditContextView = React.createClass({displayName: "DesktopRoomEditContextView",
|
||||
mixins: [React.addons.LinkedStateMixin],
|
||||
maxRoomNameLength: 124,
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
error: React.PropTypes.object,
|
||||
onClose: React.PropTypes.func,
|
||||
// This data is supplied by the activeRoomStore.
|
||||
roomData: React.PropTypes.object.isRequired,
|
||||
savingContext: React.PropTypes.bool.isRequired,
|
||||
show: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._fetchMetadata();
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
var newState = {};
|
||||
// When the 'show' prop is changed from outside this component, we do need
|
||||
// to update the state.
|
||||
if (("show" in nextProps) && nextProps.show !== this.props.show) {
|
||||
newState.show = nextProps.show;
|
||||
if (nextProps.show) {
|
||||
this._fetchMetadata();
|
||||
}
|
||||
}
|
||||
// When we receive an update for the `roomData` property, make sure that
|
||||
// the current form fields reflect reality. This is necessary, because the
|
||||
// form state is maintained in the components' state.
|
||||
if (nextProps.roomData) {
|
||||
// Right now it's only necessary to update the form input states when
|
||||
// they contain no text yet.
|
||||
if (!this.state.newRoomName && nextProps.roomData.roomName) {
|
||||
newState.newRoomName = nextProps.roomData.roomName;
|
||||
}
|
||||
var url = this._getURL(nextProps.roomData);
|
||||
if (url) {
|
||||
if (!this.state.newRoomURL && url.location) {
|
||||
newState.newRoomURL = url.location;
|
||||
}
|
||||
if (!this.state.newRoomDescription && url.description) {
|
||||
newState.newRoomDescription = url.description;
|
||||
}
|
||||
if (!this.state.newRoomThumbnail && url.thumbnail) {
|
||||
newState.newRoomThumbnail = url.thumbnail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Feature support: when a context save completed without error, we can
|
||||
// close the context edit form.
|
||||
if (("savingContext" in nextProps) && this.props.savingContext &&
|
||||
this.props.savingContext !== nextProps.savingContext && this.state.show
|
||||
&& !this.props.error && !nextProps.error) {
|
||||
newState.show = false;
|
||||
if (this.props.onClose) {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.getOwnPropertyNames(newState).length) {
|
||||
this.setState(newState);
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var url = this._getURL();
|
||||
return {
|
||||
// `availableContext` prop only used in tests.
|
||||
availableContext: null,
|
||||
show: this.props.show,
|
||||
newRoomName: this.props.roomData.roomName || "",
|
||||
newRoomURL: url && url.location || "",
|
||||
newRoomDescription: url && url.description || "",
|
||||
newRoomThumbnail: url && url.thumbnail || ""
|
||||
};
|
||||
},
|
||||
|
||||
_fetchMetadata: function() {
|
||||
loop.request("GetSelectedTabMetadata").then(function(metadata) {
|
||||
var previewImage = metadata.favicon || "";
|
||||
var description = metadata.title || metadata.description;
|
||||
var metaUrl = metadata.url;
|
||||
this.setState({
|
||||
availableContext: {
|
||||
previewImage: previewImage,
|
||||
description: description,
|
||||
url: metaUrl
|
||||
}
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
handleCloseClick: function(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({ show: false });
|
||||
if (this.props.onClose) {
|
||||
this.props.onClose();
|
||||
}
|
||||
},
|
||||
|
||||
handleContextClick: function(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
var url = this._getURL();
|
||||
if (!url || !url.location) {
|
||||
return;
|
||||
}
|
||||
|
||||
loop.requestMulti(
|
||||
["OpenURL", url.location],
|
||||
["TelemetryAddValue", "LOOP_ROOM_CONTEXT_CLICK", 1]);
|
||||
},
|
||||
|
||||
handleFormSubmit: function(event) {
|
||||
event && event.preventDefault();
|
||||
|
||||
this.props.dispatcher.dispatch(new sharedActions.UpdateRoomContext({
|
||||
roomToken: this.props.roomData.roomToken,
|
||||
newRoomName: this.state.newRoomName,
|
||||
newRoomURL: this.state.newRoomURL,
|
||||
newRoomDescription: this.state.newRoomDescription,
|
||||
newRoomThumbnail: this.state.newRoomThumbnail
|
||||
}));
|
||||
},
|
||||
|
||||
handleTextareaKeyDown: function(event) {
|
||||
// Submit the form as soon as the user press Enter in that field
|
||||
// Note: We're using a textarea instead of a simple text input to display
|
||||
// placeholder and entered text on two lines, to circumvent l10n
|
||||
// rendering/UX issues for some locales.
|
||||
if (event.which === 13) {
|
||||
this.handleFormSubmit(event);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility function to extract URL context data from the `roomData` property
|
||||
* that can also be supplied as an argument.
|
||||
*
|
||||
* @param {Object} roomData Optional room data object to use, equivalent to
|
||||
* the activeRoomStore state.
|
||||
* @return {Object} The first context URL found on the `roomData` object.
|
||||
*/
|
||||
_getURL: function(roomData) {
|
||||
roomData = roomData || this.props.roomData;
|
||||
return this.props.roomData.roomContextUrls &&
|
||||
this.props.roomData.roomContextUrls[0];
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.state.show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var url = this._getURL();
|
||||
var thumbnail = url && url.thumbnail || "shared/img/icons-16x16.svg#globe";
|
||||
var urlDescription = url && url.description || "";
|
||||
var location = url && url.location || "";
|
||||
|
||||
var cx = classNames;
|
||||
var availableContext = this.state.availableContext;
|
||||
return (
|
||||
React.createElement("div", {className: "room-context"},
|
||||
React.createElement("p", {className: cx({ "error": !!this.props.error,
|
||||
"error-display-area": true })},
|
||||
mozL10n.get("rooms_change_failed_label")
|
||||
),
|
||||
React.createElement("h2", {className: "room-context-header"}, mozL10n.get("context_inroom_header")),
|
||||
React.createElement("form", {onSubmit: this.handleFormSubmit},
|
||||
React.createElement("input", {className: "room-context-name",
|
||||
maxLength: this.maxRoomNameLength,
|
||||
onKeyDown: this.handleTextareaKeyDown,
|
||||
placeholder: mozL10n.get("context_edit_name_placeholder"),
|
||||
type: "text",
|
||||
valueLink: this.linkState("newRoomName")}),
|
||||
React.createElement("input", {className: "room-context-url",
|
||||
disabled: availableContext && availableContext.url === this.state.newRoomURL,
|
||||
onKeyDown: this.handleTextareaKeyDown,
|
||||
placeholder: "https://",
|
||||
type: "text",
|
||||
valueLink: this.linkState("newRoomURL")}),
|
||||
React.createElement("textarea", {className: "room-context-comments",
|
||||
onKeyDown: this.handleTextareaKeyDown,
|
||||
placeholder: mozL10n.get("context_edit_comments_placeholder"),
|
||||
rows: "2", type: "text",
|
||||
valueLink: this.linkState("newRoomDescription")}),
|
||||
React.createElement(sharedViews.ButtonGroup, null,
|
||||
React.createElement(sharedViews.Button, {additionalClass: "button-cancel",
|
||||
caption: mozL10n.get("context_cancel_label"),
|
||||
onClick: this.handleCloseClick}),
|
||||
React.createElement(sharedViews.Button, {additionalClass: "button-accept",
|
||||
caption: mozL10n.get("context_done_label"),
|
||||
disabled: this.props.savingContext,
|
||||
onClick: this.handleFormSubmit})
|
||||
)
|
||||
)
|
||||
)
|
||||
socialShareProviders: this.props.socialShareProviders })
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -585,14 +404,10 @@ loop.roomViews = (function(mozL10n) {
|
||||
/**
|
||||
* Desktop room conversation view.
|
||||
*/
|
||||
var DesktopRoomConversationView = React.createClass({displayName: "DesktopRoomConversationView",
|
||||
mixins: [
|
||||
ActiveRoomStoreMixin,
|
||||
sharedMixins.DocumentTitleMixin,
|
||||
sharedMixins.MediaSetupMixin,
|
||||
sharedMixins.RoomsAudioMixin,
|
||||
sharedMixins.WindowCloseMixin
|
||||
],
|
||||
var DesktopRoomConversationView = React.createClass({
|
||||
displayName: "DesktopRoomConversationView",
|
||||
|
||||
mixins: [ActiveRoomStoreMixin, sharedMixins.DocumentTitleMixin, sharedMixins.MediaSetupMixin, sharedMixins.RoomsAudioMixin, sharedMixins.WindowCloseMixin],
|
||||
|
||||
propTypes: {
|
||||
chatWindowDetached: React.PropTypes.bool.isRequired,
|
||||
@@ -604,18 +419,11 @@ loop.roomViews = (function(mozL10n) {
|
||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showEditContext: false
|
||||
};
|
||||
},
|
||||
|
||||
componentWillUpdate: function(nextProps, nextState) {
|
||||
componentWillUpdate: function (nextProps, nextState) {
|
||||
// The SDK needs to know about the configuration and the elements to use
|
||||
// for display. So the best way seems to pass the information here - ideally
|
||||
// the sdk wouldn't need to know this, but we can't change that.
|
||||
if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
|
||||
nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
|
||||
if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT && nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||
publisherConfig: this.getDefaultPublisherConfig({
|
||||
publishVideo: !this.state.videoMuted
|
||||
@@ -624,8 +432,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
}
|
||||
|
||||
// Automatically start sharing a tab now we're ready to share.
|
||||
if (this.state.roomState !== ROOM_STATES.SESSION_CONNECTED &&
|
||||
nextState.roomState === ROOM_STATES.SESSION_CONNECTED) {
|
||||
if (this.state.roomState !== ROOM_STATES.SESSION_CONNECTED && nextState.roomState === ROOM_STATES.SESSION_CONNECTED) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.StartBrowserShare());
|
||||
}
|
||||
},
|
||||
@@ -633,7 +440,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
/**
|
||||
* User clicked on the "Leave" button.
|
||||
*/
|
||||
leaveRoom: function() {
|
||||
leaveRoom: function () {
|
||||
if (this.state.used) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
|
||||
} else {
|
||||
@@ -647,9 +454,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
* @param {String} type The type of stream, e.g. "audio" or "video".
|
||||
* @param {Boolean} enabled True to enable the stream, false otherwise.
|
||||
*/
|
||||
publishStream: function(type, enabled) {
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.SetMute({
|
||||
publishStream: function (type, enabled) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.SetMute({
|
||||
type: type,
|
||||
enabled: enabled
|
||||
}));
|
||||
@@ -660,9 +466,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
*
|
||||
* @return {Boolean} True if there's no guests.
|
||||
*/
|
||||
_shouldRenderInvitationOverlay: function() {
|
||||
var hasGuests = typeof this.state.participants === "object" &&
|
||||
this.state.participants.filter(function(participant) {
|
||||
_shouldRenderInvitationOverlay: function () {
|
||||
var hasGuests = typeof this.state.participants === "object" && this.state.participants.filter(function (participant) {
|
||||
return !participant.owner;
|
||||
}).length > 0;
|
||||
|
||||
@@ -680,7 +485,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
* XXX Refactor shouldRenderRemoteVideo & shouldRenderLoading into one fn
|
||||
* that returns an enum
|
||||
*/
|
||||
shouldRenderRemoteVideo: function() {
|
||||
shouldRenderRemoteVideo: function () {
|
||||
switch (this.state.roomState) {
|
||||
case ROOM_STATES.HAS_PARTICIPANTS:
|
||||
if (this.state.remoteVideoEnabled) {
|
||||
@@ -710,8 +515,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
return true;
|
||||
|
||||
default:
|
||||
console.warn("DesktopRoomConversationView.shouldRenderRemoteVideo:" +
|
||||
" unexpected roomState: ", this.state.roomState);
|
||||
console.warn("DesktopRoomConversationView.shouldRenderRemoteVideo:" + " unexpected roomState: ", this.state.roomState);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
@@ -723,9 +527,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
_isLocalLoading: function() {
|
||||
return this.state.roomState === ROOM_STATES.MEDIA_WAIT &&
|
||||
!this.state.localSrcMediaElement;
|
||||
_isLocalLoading: function () {
|
||||
return this.state.roomState === ROOM_STATES.MEDIA_WAIT && !this.state.localSrcMediaElement;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -735,78 +538,55 @@ loop.roomViews = (function(mozL10n) {
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
_isRemoteLoading: function() {
|
||||
return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
|
||||
!this.state.remoteSrcMediaElement &&
|
||||
!this.state.mediaConnected);
|
||||
_isRemoteLoading: function () {
|
||||
return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS && !this.state.remoteSrcMediaElement && !this.state.mediaConnected);
|
||||
},
|
||||
|
||||
handleAddContextClick: function() {
|
||||
this.setState({ showEditContext: true });
|
||||
},
|
||||
|
||||
handleEditContextClick: function() {
|
||||
this.setState({ showEditContext: !this.state.showEditContext });
|
||||
},
|
||||
|
||||
handleEditContextClose: function() {
|
||||
this.setState({ showEditContext: false });
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState) {
|
||||
componentDidUpdate: function (prevProps, prevState) {
|
||||
// Handle timestamp and window closing only when the call has terminated.
|
||||
if (prevState.roomState === ROOM_STATES.ENDED &&
|
||||
this.state.roomState === ROOM_STATES.ENDED) {
|
||||
if (prevState.roomState === ROOM_STATES.ENDED && this.state.roomState === ROOM_STATES.ENDED) {
|
||||
this.props.onCallTerminated();
|
||||
}
|
||||
},
|
||||
|
||||
handleContextMenu: function(e) {
|
||||
handleContextMenu: function (e) {
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render: function () {
|
||||
if (this.state.roomName || this.state.roomContextUrls) {
|
||||
var roomTitle = this.state.roomName ||
|
||||
this.state.roomContextUrls[0].description ||
|
||||
this.state.roomContextUrls[0].location;
|
||||
var roomTitle = this.state.roomName || this.state.roomContextUrls[0].description || this.state.roomContextUrls[0].location;
|
||||
this.setTitle(roomTitle);
|
||||
}
|
||||
|
||||
var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
|
||||
var shouldRenderEditContextView = this.state.showEditContext;
|
||||
var roomData = this.props.roomStore.getStoreState("activeRoom");
|
||||
|
||||
switch (this.state.roomState) {
|
||||
case ROOM_STATES.FAILED:
|
||||
case ROOM_STATES.FULL: {
|
||||
case ROOM_STATES.FULL:
|
||||
{
|
||||
// Note: While rooms are set to hold a maximum of 2 participants, the
|
||||
// FULL case should never happen on desktop.
|
||||
return (
|
||||
React.createElement(RoomFailureView, {
|
||||
return React.createElement(RoomFailureView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
failureReason: this.state.failureReason})
|
||||
);
|
||||
failureReason: this.state.failureReason });
|
||||
}
|
||||
case ROOM_STATES.ENDED: {
|
||||
case ROOM_STATES.ENDED:
|
||||
{
|
||||
// When conversation ended we either display a feedback form or
|
||||
// close the window. This is decided in the AppControllerView.
|
||||
return null;
|
||||
}
|
||||
default: {
|
||||
var settingsMenuItems = [
|
||||
default:
|
||||
{
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ className: "room-conversation-wrapper desktop-room-wrapper",
|
||||
onContextMenu: this.handleContextMenu },
|
||||
React.createElement(
|
||||
sharedViews.MediaLayoutView,
|
||||
{
|
||||
id: "edit",
|
||||
enabled: !this.state.showEditContext,
|
||||
visible: true,
|
||||
onClick: this.handleEditContextClick
|
||||
},
|
||||
{ id: "help" }
|
||||
];
|
||||
return (
|
||||
React.createElement("div", {className: "room-conversation-wrapper desktop-room-wrapper",
|
||||
onContextMenu: this.handleContextMenu},
|
||||
React.createElement(sharedViews.MediaLayoutView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
displayScreenShare: false,
|
||||
isLocalLoading: this._isLocalLoading(),
|
||||
@@ -822,34 +602,20 @@ loop.roomViews = (function(mozL10n) {
|
||||
screenShareMediaElement: this.state.screenShareMediaElement,
|
||||
screenSharePosterUrl: null,
|
||||
showInitialContext: false,
|
||||
useDesktopPaths: true},
|
||||
useDesktopPaths: true },
|
||||
React.createElement(sharedViews.ConversationToolbar, {
|
||||
audio: { enabled: !this.state.audioMuted, visible: true},
|
||||
audio: { enabled: !this.state.audioMuted, visible: true },
|
||||
dispatcher: this.props.dispatcher,
|
||||
hangup: this.leaveRoom,
|
||||
publishStream: this.publishStream,
|
||||
settingsMenuItems: settingsMenuItems,
|
||||
show: !shouldRenderEditContextView,
|
||||
showHangup: this.props.chatWindowDetached,
|
||||
video: { enabled: !this.state.videoMuted, visible: true}}),
|
||||
video: { enabled: !this.state.videoMuted, visible: true } }),
|
||||
React.createElement(DesktopRoomInvitationView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
error: this.state.error,
|
||||
onAddContextClick: this.handleAddContextClick,
|
||||
onEditContextClose: this.handleEditContextClose,
|
||||
roomData: roomData,
|
||||
savingContext: this.state.savingContext,
|
||||
show: shouldRenderInvitationOverlay,
|
||||
showEditContext: shouldRenderInvitationOverlay && shouldRenderEditContextView,
|
||||
socialShareProviders: this.state.socialShareProviders}),
|
||||
React.createElement(DesktopRoomEditContextView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
error: this.state.error,
|
||||
onClose: this.handleEditContextClose,
|
||||
roomData: roomData,
|
||||
savingContext: this.state.savingContext,
|
||||
show: !shouldRenderInvitationOverlay && shouldRenderEditContextView})
|
||||
)
|
||||
socialShareProviders: this.state.socialShareProviders })
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -862,9 +628,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
FailureInfoView: FailureInfoView,
|
||||
RoomFailureView: RoomFailureView,
|
||||
SocialShareDropdown: SocialShareDropdown,
|
||||
DesktopRoomEditContextView: DesktopRoomEditContextView,
|
||||
DesktopRoomConversationView: DesktopRoomConversationView,
|
||||
DesktopRoomInvitationView: DesktopRoomInvitationView
|
||||
};
|
||||
|
||||
})(document.mozL10n || navigator.mozL10n);
|
||||
}(document.mozL10n || navigator.mozL10n);
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
<div id="main"></div>
|
||||
|
||||
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/react-0.13.3.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/lodash-3.9.3.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/backbone-1.2.1.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/classnames-2.2.0.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/react.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/lodash.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/backbone.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/classnames.js"></script>
|
||||
|
||||
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
|
||||
<script type="text/javascript" src="shared/js/utils.js"></script>
|
||||
|
||||
@@ -7,4 +7,9 @@
|
||||
"destructuring": true,
|
||||
"forOf": true
|
||||
},
|
||||
"rules": {
|
||||
// This is useful for some of the tests, e.g.
|
||||
// expect(new Foo()).to.Throw(/error/)
|
||||
"no-new": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,10 @@
|
||||
describe("loop.conversation", function() {
|
||||
"use strict";
|
||||
|
||||
var expect = chai.expect;
|
||||
var FeedbackView = loop.feedbackViews.FeedbackView;
|
||||
var TestUtils = React.addons.TestUtils;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedModels = loop.shared.models;
|
||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||
var fakeWindow, sandbox, getLoopPrefStub, setLoopPrefStub, mozL10nGet;
|
||||
var fakeWindow, sandbox, setLoopPrefStub, mozL10nGet;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = LoopMochaUtils.createSandbox();
|
||||
@@ -43,8 +40,6 @@ describe("loop.conversation", function() {
|
||||
}
|
||||
};
|
||||
},
|
||||
StartAlerting: sinon.stub(),
|
||||
StopAlerting: sinon.stub(),
|
||||
EnsureRegistered: sinon.stub(),
|
||||
GetAppVersionInfo: function() {
|
||||
return {
|
||||
@@ -53,7 +48,7 @@ describe("loop.conversation", function() {
|
||||
platform: "test"
|
||||
};
|
||||
},
|
||||
GetAudioBlob: sinon.spy(function(name) {
|
||||
GetAudioBlob: sinon.spy(function() {
|
||||
return new Blob([new ArrayBuffer(10)], { type: "audio/ogg" });
|
||||
}),
|
||||
GetSelectedTabMetadata: function() {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Loop desktop-local mocha tests</title>
|
||||
<link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.2.5.css">
|
||||
<link rel="stylesheet" media="all" href="/test/vendor/mocha.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha">
|
||||
@@ -14,22 +14,26 @@
|
||||
</div>
|
||||
<div id="messages"></div>
|
||||
<div id="fixtures"></div>
|
||||
<script src="../../content/shared/vendor/lodash-3.9.3.js"></script>
|
||||
<script src="../shared/loop_mocha_utils.js"></script>
|
||||
<script src="/shared/vendor/lodash.js"></script>
|
||||
<script src="/shared/test/loop_mocha_utils.js"></script>
|
||||
<script>
|
||||
LoopMochaUtils.trapErrors();
|
||||
</script>
|
||||
|
||||
<!-- libs -->
|
||||
<script src="../../content/panels/vendor/l10n.js"></script>
|
||||
<script src="../../content/shared/vendor/react-0.13.3.js"></script>
|
||||
<script src="../../content/shared/vendor/classnames-2.2.0.js"></script>
|
||||
<script src="../../content/shared/vendor/backbone-1.2.1.js"></script>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<script src="../shared/vendor/mocha-2.2.5.js"></script>
|
||||
<script src="../shared/vendor/chai-3.0.0.js"></script>
|
||||
<script src="../shared/vendor/sinon-1.16.1.js"></script>
|
||||
<script src="/add-on/panels/vendor/l10n.js"></script>
|
||||
<script src="/shared/vendor/react.js"></script>
|
||||
<script src="/shared/vendor/classnames.js"></script>
|
||||
<script src="/shared/vendor/backbone.js"></script>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<script src="/test/vendor/mocha.js"></script>
|
||||
<script src="/test/vendor/chai.js"></script>
|
||||
<script src="/test/vendor/chai-as-promised.js"></script>
|
||||
<script src="/test/vendor/sinon.js"></script>
|
||||
|
||||
<script>
|
||||
/*global chai,mocha */
|
||||
chai.config.includeStack = true;
|
||||
@@ -37,25 +41,25 @@
|
||||
</script>
|
||||
|
||||
<!-- App scripts -->
|
||||
<script src="../../content/shared/js/loopapi-client.js"></script>
|
||||
<script src="../../content/shared/js/utils.js"></script>
|
||||
<script src="../../content/shared/js/models.js"></script>
|
||||
<script src="../../content/shared/js/mixins.js"></script>
|
||||
<script src="../../content/shared/js/actions.js"></script>
|
||||
<script src="../../content/shared/js/validate.js"></script>
|
||||
<script src="../../content/shared/js/dispatcher.js"></script>
|
||||
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
||||
<script src="../../content/shared/js/store.js"></script>
|
||||
<script src="../../content/shared/js/activeRoomStore.js"></script>
|
||||
<script src="../../content/shared/js/views.js"></script>
|
||||
<script src="../../content/shared/js/textChatStore.js"></script>
|
||||
<script src="../../content/shared/js/textChatView.js"></script>
|
||||
<script src="../../content/panels/js/conversationAppStore.js"></script>
|
||||
<script src="../../content/panels/js/roomStore.js"></script>
|
||||
<script src="../../content/panels/js/roomViews.js"></script>
|
||||
<script src="../../content/panels/js/feedbackViews.js"></script>
|
||||
<script src="../../content/panels/js/conversation.js"></script>
|
||||
<script src="../../content/panels/js/panel.js"></script>
|
||||
<script src="/add-on/shared/js/loopapi-client.js"></script>
|
||||
<script src="/add-on/shared/js/utils.js"></script>
|
||||
<script src="/add-on/shared/js/models.js"></script>
|
||||
<script src="/add-on/shared/js/mixins.js"></script>
|
||||
<script src="/add-on/shared/js/actions.js"></script>
|
||||
<script src="/add-on/shared/js/validate.js"></script>
|
||||
<script src="/add-on/shared/js/dispatcher.js"></script>
|
||||
<script src="/add-on/shared/js/otSdkDriver.js"></script>
|
||||
<script src="/add-on/shared/js/store.js"></script>
|
||||
<script src="/add-on/shared/js/activeRoomStore.js"></script>
|
||||
<script src="/add-on/shared/js/views.js"></script>
|
||||
<script src="/add-on/shared/js/textChatStore.js"></script>
|
||||
<script src="/add-on/shared/js/textChatView.js"></script>
|
||||
<script src="/add-on/panels/js/conversationAppStore.js"></script>
|
||||
<script src="/add-on/panels/js/roomStore.js"></script>
|
||||
<script src="/add-on/panels/js/roomViews.js"></script>
|
||||
<script src="/add-on/panels/js/feedbackViews.js"></script>
|
||||
<script src="/add-on/panels/js/conversation.js"></script>
|
||||
<script src="/add-on/panels/js/panel.js"></script>
|
||||
|
||||
<!-- Test scripts -->
|
||||
<script src="conversationAppStore_test.js"></script>
|
||||
|
||||
@@ -8,13 +8,11 @@ describe("loop.panel", function() {
|
||||
var expect = chai.expect;
|
||||
var TestUtils = React.addons.TestUtils;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
|
||||
var sandbox, notifications, requestStubs;
|
||||
var fakeXHR, fakeWindow, fakeEvent;
|
||||
var requests = [];
|
||||
var roomData, roomData2, roomList, roomName;
|
||||
var mozL10nGetSpy;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = LoopMochaUtils.createSandbox();
|
||||
@@ -56,13 +54,16 @@ describe("loop.panel", function() {
|
||||
GetPluralRule: sinon.stub(),
|
||||
SetLoopPref: sinon.stub(),
|
||||
GetLoopPref: function(prefName) {
|
||||
if (prefName === "debug.dispatcher") {
|
||||
return false;
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
SetPanelHeight: function() { return null; },
|
||||
GetPluralForm: function() {
|
||||
return "fakeText";
|
||||
},
|
||||
"Rooms:GetAll": function(version) {
|
||||
"Rooms:GetAll": function() {
|
||||
return [];
|
||||
},
|
||||
"Rooms:PushSubscription": sinon.stub(),
|
||||
@@ -184,14 +185,9 @@ describe("loop.panel", function() {
|
||||
});
|
||||
|
||||
describe("loop.panel.PanelView", function() {
|
||||
var dispatcher, roomStore, callUrlData;
|
||||
var dispatcher, roomStore;
|
||||
|
||||
beforeEach(function() {
|
||||
callUrlData = {
|
||||
callUrl: "http://call.invalid/",
|
||||
expiresAt: 1000
|
||||
};
|
||||
|
||||
dispatcher = new loop.Dispatcher();
|
||||
roomStore = new loop.store.RoomStore(dispatcher, {
|
||||
constants: {}
|
||||
@@ -216,7 +212,7 @@ describe("loop.panel", function() {
|
||||
var view = TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.panel.SettingsDropdown));
|
||||
|
||||
expect(view.getDOMNode().querySelectorAll(".icon-account"))
|
||||
expect(view.getDOMNode().querySelectorAll(".entry-settings-account"))
|
||||
.to.have.length.of(0);
|
||||
});
|
||||
|
||||
@@ -266,19 +262,11 @@ describe("loop.panel", function() {
|
||||
expect(view.getDOMNode()).to.be.null;
|
||||
});
|
||||
|
||||
it("should add ellipsis to text over 24chars", function() {
|
||||
loop.storedRequests.GetUserProfile = { email: "reallyreallylongtext@example.com" };
|
||||
var view = createTestPanelView();
|
||||
var node = view.getDOMNode().querySelector(".user-identity");
|
||||
|
||||
expect(node.textContent).to.eql("reallyreallylongtext@exa…");
|
||||
});
|
||||
|
||||
it("should warn when user profile is different from {} or null",
|
||||
function() {
|
||||
var warnstub = sandbox.stub(console, "warn");
|
||||
|
||||
var view = TestUtils.renderIntoDocument(React.createElement(
|
||||
TestUtils.renderIntoDocument(React.createElement(
|
||||
loop.panel.AccountLink, {
|
||||
fxAEnabled: false,
|
||||
userProfile: []
|
||||
@@ -294,7 +282,7 @@ describe("loop.panel", function() {
|
||||
function() {
|
||||
var warnstub = sandbox.stub(console, "warn");
|
||||
|
||||
var view = TestUtils.renderIntoDocument(React.createElement(
|
||||
TestUtils.renderIntoDocument(React.createElement(
|
||||
loop.panel.AccountLink, {
|
||||
fxAEnabled: false,
|
||||
userProfile: {}
|
||||
@@ -311,7 +299,7 @@ describe("loop.panel", function() {
|
||||
React.createElement(loop.panel.SettingsDropdown));
|
||||
}
|
||||
|
||||
var loginToFxAStub, logoutFromFxAStub, openFxASettingsStub;
|
||||
var openFxASettingsStub;
|
||||
|
||||
beforeEach(function() {
|
||||
openFxASettingsStub = sandbox.stub();
|
||||
@@ -342,7 +330,7 @@ describe("loop.panel", function() {
|
||||
function() {
|
||||
var view = mountTestComponent();
|
||||
|
||||
expect(view.getDOMNode().querySelectorAll(".icon-account"))
|
||||
expect(view.getDOMNode().querySelectorAll(".entry-settings-account"))
|
||||
.to.have.length.of(0);
|
||||
});
|
||||
|
||||
@@ -354,36 +342,48 @@ describe("loop.panel", function() {
|
||||
|
||||
sinon.assert.calledOnce(requestStubs.LoginToFxA);
|
||||
});
|
||||
|
||||
it("should close the menu on clicking sign in", function() {
|
||||
var view = mountTestComponent();
|
||||
|
||||
TestUtils.Simulate.click(view.getDOMNode()
|
||||
.querySelector(".entry-settings-signin"));
|
||||
|
||||
expect(view.state.showMenu).eql(false);
|
||||
});
|
||||
|
||||
it("should close the panel on clicking sign in", function() {
|
||||
var view = mountTestComponent();
|
||||
|
||||
TestUtils.Simulate.click(view.getDOMNode()
|
||||
.querySelector(".entry-settings-signin"));
|
||||
|
||||
sinon.assert.calledOnce(fakeWindow.close);
|
||||
});
|
||||
});
|
||||
|
||||
describe("UserLoggedIn", function() {
|
||||
var view;
|
||||
|
||||
beforeEach(function() {
|
||||
loop.storedRequests.GetUserProfile = { email: "test@example.com" };
|
||||
view = mountTestComponent();
|
||||
});
|
||||
|
||||
it("should show a signout entry when user is authenticated", function() {
|
||||
loop.storedRequests.GetUserProfile = { email: "test@example.com" };
|
||||
|
||||
var view = mountTestComponent();
|
||||
|
||||
sinon.assert.calledWithExactly(document.mozL10n.get,
|
||||
"settings_menu_item_signout");
|
||||
sinon.assert.neverCalledWith(document.mozL10n.get,
|
||||
"settings_menu_item_signin");
|
||||
expect(view.getDOMNode().querySelectorAll(".entry-settings-signout"))
|
||||
.to.have.length.of(1);
|
||||
expect(view.getDOMNode().querySelectorAll(".entry-settings-signin"))
|
||||
.to.have.length.of(0);
|
||||
});
|
||||
|
||||
it("should show an account entry when user is authenticated", function() {
|
||||
LoopMochaUtils.stubLoopRequest({
|
||||
GetUserProfile: function() { return { email: "test@example.com" }; }
|
||||
});
|
||||
|
||||
var view = mountTestComponent();
|
||||
|
||||
sinon.assert.calledWithExactly(document.mozL10n.get,
|
||||
"settings_menu_item_settings");
|
||||
expect(view.getDOMNode().querySelectorAll(".entry-settings-account"))
|
||||
.to.have.length.of(1);
|
||||
});
|
||||
|
||||
it("should open the FxA settings when the account entry is clicked",
|
||||
function() {
|
||||
loop.storedRequests.GetUserProfile = { email: "test@example.com" };
|
||||
|
||||
var view = mountTestComponent();
|
||||
|
||||
TestUtils.Simulate.click(view.getDOMNode()
|
||||
.querySelector(".entry-settings-account"));
|
||||
|
||||
@@ -391,15 +391,33 @@ describe("loop.panel", function() {
|
||||
});
|
||||
|
||||
it("should sign out the user on click when authenticated", function() {
|
||||
loop.storedRequests.GetUserProfile = { email: "test@example.com" };
|
||||
var view = mountTestComponent();
|
||||
|
||||
TestUtils.Simulate.click(view.getDOMNode()
|
||||
.querySelector(".entry-settings-signout"));
|
||||
|
||||
sinon.assert.calledOnce(requestStubs.LogoutFromFxA);
|
||||
});
|
||||
|
||||
it("should close the dropdown menu on clicking sign out", function() {
|
||||
LoopMochaUtils.stubLoopRequest({
|
||||
GetUserProfile: function() { return { email: "test@example.com" }; }
|
||||
});
|
||||
|
||||
view.setState({ showMenu: true });
|
||||
|
||||
TestUtils.Simulate.click(view.getDOMNode()
|
||||
.querySelector(".entry-settings-signout"));
|
||||
|
||||
expect(view.state.showMenu).eql(false);
|
||||
});
|
||||
|
||||
it("should not close the panel on clicking sign out", function() {
|
||||
TestUtils.Simulate.click(view.getDOMNode()
|
||||
.querySelector(".entry-settings-signout"));
|
||||
|
||||
sinon.assert.notCalled(fakeWindow.close);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Toggle Notifications", function() {
|
||||
var view;
|
||||
|
||||
@@ -813,22 +831,6 @@ describe("loop.panel", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Room Entry click", function() {
|
||||
var roomEntry, roomEntryNode;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
|
||||
roomEntry = mountRoomEntry({
|
||||
dispatcher: dispatcher,
|
||||
isOpenedRoom: false,
|
||||
room: new loop.store.Room(roomData)
|
||||
});
|
||||
roomEntryNode = roomEntry.getDOMNode();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Room name updated", function() {
|
||||
it("should update room name", function() {
|
||||
var roomEntry = mountRoomEntry({
|
||||
@@ -953,7 +955,7 @@ describe("loop.panel", function() {
|
||||
});
|
||||
|
||||
it("should close the panel once a room is created and there is no error", function() {
|
||||
var view = createTestComponent();
|
||||
createTestComponent();
|
||||
|
||||
roomStore.setStoreState({ pendingCreation: true });
|
||||
|
||||
@@ -964,20 +966,10 @@ describe("loop.panel", function() {
|
||||
sinon.assert.calledOnce(fakeWindow.close);
|
||||
});
|
||||
|
||||
it("should render the no rooms view when no rooms available", function() {
|
||||
it("should not render the room list view when no rooms available", function() {
|
||||
var view = createTestComponent();
|
||||
var node = view.getDOMNode();
|
||||
|
||||
expect(node.querySelectorAll(".room-list-empty").length).to.eql(1);
|
||||
});
|
||||
|
||||
it("should call mozL10n.get for room empty strings", function() {
|
||||
var view = createTestComponent();
|
||||
|
||||
sinon.assert.calledWithExactly(document.mozL10n.get,
|
||||
"no_conversations_message_heading2");
|
||||
sinon.assert.calledWithExactly(document.mozL10n.get,
|
||||
"no_conversations_start_message2");
|
||||
expect(node.querySelectorAll(".room-list").length).to.eql(0);
|
||||
});
|
||||
|
||||
it("should display a loading animation when rooms are pending", function() {
|
||||
|
||||
@@ -134,7 +134,7 @@ describe("loop.store.RoomStore", function() {
|
||||
describe("MozLoop rooms event listeners", function() {
|
||||
beforeEach(function() {
|
||||
LoopMochaUtils.stubLoopRequest({
|
||||
"Rooms:GetAll": function(version) {
|
||||
"Rooms:GetAll": function() {
|
||||
return fakeRoomList;
|
||||
}
|
||||
});
|
||||
@@ -226,8 +226,6 @@ describe("loop.store.RoomStore", function() {
|
||||
});
|
||||
|
||||
describe("#createRoom", function() {
|
||||
var fakeLocalRoomId = "777";
|
||||
var fakeOwner = "fake@invalid";
|
||||
var fakeRoomCreationData;
|
||||
|
||||
beforeEach(function() {
|
||||
@@ -320,6 +318,19 @@ describe("loop.store.RoomStore", function() {
|
||||
}));
|
||||
});
|
||||
|
||||
it("should dispatch a CreateRoomError action if the operation fails with no result",
|
||||
function() {
|
||||
requestStubs["Rooms:Create"].returns();
|
||||
|
||||
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.CreateRoomError({
|
||||
error: new Error("no result")
|
||||
}));
|
||||
});
|
||||
|
||||
it("should log a telemetry event when the operation is successful", function() {
|
||||
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
|
||||
|
||||
@@ -339,6 +350,16 @@ describe("loop.store.RoomStore", function() {
|
||||
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
|
||||
"LOOP_ROOM_CREATE", 1);
|
||||
});
|
||||
|
||||
it("should log a telemetry event when the operation fails with no result", function() {
|
||||
requestStubs["Rooms:Create"].returns();
|
||||
|
||||
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
|
||||
|
||||
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
|
||||
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
|
||||
"LOOP_ROOM_CREATE", 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createdRoom", function() {
|
||||
@@ -725,14 +746,14 @@ describe("loop.store.RoomStore", function() {
|
||||
});
|
||||
|
||||
describe("#openRoom", function() {
|
||||
var store, fakeMozLoop;
|
||||
var store;
|
||||
|
||||
beforeEach(function() {
|
||||
store = new loop.store.RoomStore(dispatcher, { constants: {} });
|
||||
});
|
||||
|
||||
it("should open the room via mozLoop", function() {
|
||||
dispatcher.dispatch(new sharedActions.OpenRoom({ roomToken: "42abc" }));
|
||||
store.openRoom(new sharedActions.OpenRoom({ roomToken: "42abc" }));
|
||||
|
||||
sinon.assert.calledOnce(requestStubs["Rooms:Open"]);
|
||||
sinon.assert.calledWithExactly(requestStubs["Rooms:Open"], "42abc");
|
||||
@@ -781,7 +802,7 @@ describe("loop.store.RoomStore", function() {
|
||||
expect(store.getStoreState().savingContext).to.eql(false);
|
||||
|
||||
LoopMochaUtils.stubLoopRequest({
|
||||
"Rooms:Update": function(roomToken, roomData) {
|
||||
"Rooms:Update": function() {
|
||||
expect(store.getStoreState().savingContext).to.eql(true);
|
||||
}
|
||||
});
|
||||
@@ -799,7 +820,7 @@ describe("loop.store.RoomStore", function() {
|
||||
err.isError = true;
|
||||
|
||||
LoopMochaUtils.stubLoopRequest({
|
||||
"Rooms:Update": function(roomToken, roomData) {
|
||||
"Rooms:Update": function() {
|
||||
expect(store.getStoreState().savingContext).to.eql(true);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -7,21 +7,19 @@ describe("loop.roomViews", function() {
|
||||
var expect = chai.expect;
|
||||
var TestUtils = React.addons.TestUtils;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
var sharedViews = loop.shared.views;
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
|
||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||
|
||||
var sandbox, dispatcher, roomStore, activeRoomStore, view;
|
||||
var clock, fakeWindow, requestStubs, fakeContextURL;
|
||||
var clock, fakeWindow, requestStubs;
|
||||
var favicon = "data:image/x-icon;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = LoopMochaUtils.createSandbox();
|
||||
|
||||
LoopMochaUtils.stubLoopRequest(requestStubs = {
|
||||
GetAudioBlob: sinon.spy(function(name) {
|
||||
GetAudioBlob: sinon.spy(function() {
|
||||
return new Blob([new ArrayBuffer(10)], { type: "audio/ogg" });
|
||||
}),
|
||||
GetLoopPref: sinon.stub(),
|
||||
@@ -79,11 +77,6 @@ describe("loop.roomViews", function() {
|
||||
textChatStore: textChatStore
|
||||
});
|
||||
|
||||
fakeContextURL = {
|
||||
description: "An invalid page",
|
||||
location: "http://invalid.com",
|
||||
thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
|
||||
};
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
});
|
||||
|
||||
@@ -315,16 +308,6 @@ describe("loop.roomViews", function() {
|
||||
expect(copyBtn.textContent).eql("invite_copy_link_button");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edit Context", function() {
|
||||
it("should show the edit context view", function() {
|
||||
view = mountTestComponent({
|
||||
showEditContext: true
|
||||
});
|
||||
|
||||
expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("DesktopRoomConversationView", function() {
|
||||
@@ -433,7 +416,7 @@ describe("loop.roomViews", function() {
|
||||
});
|
||||
|
||||
describe("#componentWillUpdate", function() {
|
||||
function expectActionDispatched(component) {
|
||||
function expectActionDispatched() {
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
sinon.match.instanceOf(sharedActions.SetupStreamElements));
|
||||
@@ -441,29 +424,29 @@ describe("loop.roomViews", function() {
|
||||
|
||||
it("should dispatch a `SetupStreamElements` action when the MEDIA_WAIT state is entered", function() {
|
||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
|
||||
var component = mountTestComponent();
|
||||
mountTestComponent();
|
||||
|
||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.MEDIA_WAIT });
|
||||
|
||||
expectActionDispatched(component);
|
||||
expectActionDispatched();
|
||||
});
|
||||
|
||||
it("should dispatch a `SetupStreamElements` action on MEDIA_WAIT state is re-entered", function() {
|
||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.ENDED });
|
||||
var component = mountTestComponent();
|
||||
mountTestComponent();
|
||||
|
||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.MEDIA_WAIT });
|
||||
|
||||
expectActionDispatched(component);
|
||||
expectActionDispatched();
|
||||
});
|
||||
|
||||
it("should dispatch a `StartBrowserShare` action when the SESSION_CONNECTED state is entered", function() {
|
||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
|
||||
var component = mountTestComponent();
|
||||
mountTestComponent();
|
||||
|
||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.SESSION_CONNECTED });
|
||||
|
||||
expectActionDispatched("startBrowserShare");
|
||||
expectActionDispatched();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -680,7 +663,6 @@ describe("loop.roomViews", function() {
|
||||
});
|
||||
|
||||
describe("Room name priority", function() {
|
||||
var roomEntry;
|
||||
beforeEach(function() {
|
||||
activeRoomStore.setStoreState({
|
||||
participants: [{}],
|
||||
@@ -721,29 +703,6 @@ describe("loop.roomViews", function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edit Context", function() {
|
||||
it("should show the form when the edit button is clicked", function() {
|
||||
view = mountTestComponent();
|
||||
var node = view.getDOMNode();
|
||||
|
||||
expect(node.querySelector(".room-context")).to.eql(null);
|
||||
|
||||
var editButton = node.querySelector(".settings-menu > li.entry-settings-edit");
|
||||
React.addons.TestUtils.Simulate.click(editButton);
|
||||
|
||||
expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null);
|
||||
});
|
||||
|
||||
it("should not have a settings menu when the edit button is clicked", function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
var editButton = view.getDOMNode().querySelector(".settings-menu > li.entry-settings-edit");
|
||||
React.addons.TestUtils.Simulate.click(editButton);
|
||||
|
||||
expect(view.getDOMNode().querySelector(".settings-menu")).to.eql(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("SocialShareDropdown", function() {
|
||||
@@ -839,175 +798,4 @@ describe("loop.roomViews", function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("DesktopRoomEditContextView", function() {
|
||||
function mountTestComponent(props) {
|
||||
props = _.extend({
|
||||
dispatcher: dispatcher,
|
||||
savingContext: false,
|
||||
show: true,
|
||||
roomData: {
|
||||
roomToken: "fakeToken"
|
||||
}
|
||||
}, props);
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.roomViews.DesktopRoomEditContextView, props));
|
||||
}
|
||||
|
||||
describe("#render", function() {
|
||||
it("should not render the component when 'show' is false", function() {
|
||||
view = mountTestComponent({
|
||||
show: false
|
||||
});
|
||||
|
||||
expect(view.getDOMNode()).to.eql(null);
|
||||
});
|
||||
|
||||
it("should close the view when the cancel button is clicked", function() {
|
||||
view = mountTestComponent({
|
||||
roomData: { roomContextUrls: [fakeContextURL] }
|
||||
});
|
||||
|
||||
var closeBtn = view.getDOMNode().querySelector(".button-cancel");
|
||||
React.addons.TestUtils.Simulate.click(closeBtn);
|
||||
expect(view.getDOMNode()).to.eql(null);
|
||||
});
|
||||
|
||||
it("should render the view correctly", function() {
|
||||
var roomName = "Hello, is it me you're looking for?";
|
||||
view = mountTestComponent({
|
||||
roomData: {
|
||||
roomName: roomName,
|
||||
roomContextUrls: [fakeContextURL]
|
||||
}
|
||||
});
|
||||
|
||||
var node = view.getDOMNode();
|
||||
expect(node.querySelector("form")).to.not.eql(null);
|
||||
// Check the contents of the form fields.
|
||||
expect(node.querySelector(".room-context-name").value).to.eql(roomName);
|
||||
expect(node.querySelector(".room-context-url").value).to.eql(fakeContextURL.location);
|
||||
expect(node.querySelector(".room-context-comments").value).to.eql(fakeContextURL.description);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Update Room", function() {
|
||||
var roomNameBox;
|
||||
|
||||
beforeEach(function() {
|
||||
view = mountTestComponent({
|
||||
editMode: true,
|
||||
roomData: {
|
||||
roomToken: "fakeToken",
|
||||
roomName: "fakeName",
|
||||
roomContextUrls: [fakeContextURL]
|
||||
}
|
||||
});
|
||||
|
||||
roomNameBox = view.getDOMNode().querySelector(".room-context-name");
|
||||
});
|
||||
|
||||
it("should dispatch a UpdateRoomContext action when the save button is clicked",
|
||||
function() {
|
||||
React.addons.TestUtils.Simulate.change(roomNameBox, { target: {
|
||||
value: "reallyFake"
|
||||
} });
|
||||
|
||||
React.addons.TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-accept"));
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomContext({
|
||||
roomToken: "fakeToken",
|
||||
newRoomName: "reallyFake",
|
||||
newRoomDescription: fakeContextURL.description,
|
||||
newRoomURL: fakeContextURL.location,
|
||||
newRoomThumbnail: fakeContextURL.thumbnail
|
||||
}));
|
||||
});
|
||||
|
||||
it("should dispatch a UpdateRoomContext action when Enter key is pressed",
|
||||
function() {
|
||||
React.addons.TestUtils.Simulate.change(roomNameBox, { target: {
|
||||
value: "reallyFake"
|
||||
} });
|
||||
|
||||
TestUtils.Simulate.keyDown(roomNameBox, { key: "Enter", which: 13 });
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomContext({
|
||||
roomToken: "fakeToken",
|
||||
newRoomName: "reallyFake",
|
||||
newRoomDescription: fakeContextURL.description,
|
||||
newRoomURL: fakeContextURL.location,
|
||||
newRoomThumbnail: fakeContextURL.thumbnail
|
||||
}));
|
||||
});
|
||||
|
||||
it("should close the edit form when context was saved successfully", function(done) {
|
||||
view.setProps({ savingContext: true }, function() {
|
||||
var node = view.getDOMNode();
|
||||
// The button should show up as disabled.
|
||||
expect(node.querySelector(".button-accept").hasAttribute("disabled")).to.eql(true);
|
||||
|
||||
// Now simulate a successful save.
|
||||
view.setProps({ savingContext: false }, function() {
|
||||
// The 'show flag should be updated.
|
||||
expect(view.state.show).to.eql(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#handleContextClick", function() {
|
||||
var fakeEvent;
|
||||
|
||||
beforeEach(function() {
|
||||
fakeEvent = {
|
||||
preventDefault: sinon.stub(),
|
||||
stopPropagation: sinon.stub()
|
||||
};
|
||||
});
|
||||
|
||||
it("should not attempt to open a URL when none is attached", function() {
|
||||
view = mountTestComponent({
|
||||
roomData: {
|
||||
roomToken: "fakeToken",
|
||||
roomName: "fakeName"
|
||||
}
|
||||
});
|
||||
|
||||
view.handleContextClick(fakeEvent);
|
||||
|
||||
sinon.assert.calledOnce(fakeEvent.preventDefault);
|
||||
sinon.assert.calledOnce(fakeEvent.stopPropagation);
|
||||
|
||||
sinon.assert.notCalled(requestStubs.OpenURL);
|
||||
sinon.assert.notCalled(requestStubs.TelemetryAddValue);
|
||||
});
|
||||
|
||||
it("should open a URL", function() {
|
||||
view = mountTestComponent({
|
||||
roomData: {
|
||||
roomToken: "fakeToken",
|
||||
roomName: "fakeName",
|
||||
roomContextUrls: [fakeContextURL]
|
||||
}
|
||||
});
|
||||
|
||||
view.handleContextClick(fakeEvent);
|
||||
|
||||
sinon.assert.calledOnce(fakeEvent.preventDefault);
|
||||
sinon.assert.calledOnce(fakeEvent.stopPropagation);
|
||||
|
||||
sinon.assert.calledOnce(requestStubs.OpenURL);
|
||||
sinon.assert.calledWithExactly(requestStubs.OpenURL, fakeContextURL.location);
|
||||
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
|
||||
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
|
||||
"LOOP_ROOM_CONTEXT_CLICK", 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# need to get this dir in the path so that we make the import work
|
||||
import os
|
||||
import sys
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'shared'))
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'shared', 'test'))
|
||||
|
||||
from frontend_tester import BaseTestFrontendUnits
|
||||
|
||||
@@ -10,7 +10,7 @@ class TestDesktopUnits(BaseTestFrontendUnits):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDesktopUnits, self).setUp()
|
||||
self.set_server_prefix("../desktop-local/")
|
||||
self.set_server_prefix("../../../../")
|
||||
|
||||
def test_units(self):
|
||||
self.check_page("index.html")
|
||||
self.check_page("chrome/content/panels/test/index.html")
|
||||
|
||||
@@ -221,14 +221,19 @@
|
||||
get: translateString,
|
||||
|
||||
// get the document language
|
||||
getLanguage: function() { return gLanguage; },
|
||||
|
||||
// get the direction (ltr|rtl) of the current language
|
||||
getDirection: function() {
|
||||
language: {
|
||||
set code(lang) {
|
||||
throw new Error("unsupported");
|
||||
},
|
||||
get code() {
|
||||
return gLanguage;
|
||||
},
|
||||
get direction() {
|
||||
// http://www.w3.org/International/questions/qa-scripts
|
||||
// Arabic, Hebrew, Farsi, Pashto, Urdu
|
||||
var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];
|
||||
return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
// translate an element or document fragment
|
||||
|
||||
@@ -2,7 +2,7 @@ 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.latestFTUVersion", 0);
|
||||
pref("loop.gettingStarted.latestFTUVersion", 1);
|
||||
pref("loop.facebook.shareUrl", "https://www.facebook.com/sharer/sharer.php?u=%ROOM_URL%");
|
||||
pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
|
||||
pref("loop.gettingStarted.resumeOnFirstJoin", false);
|
||||
@@ -10,7 +10,6 @@ pref("loop.learnMoreUrl", "https://www.firefox.com/hello/");
|
||||
pref("loop.legal.ToS_url", "https://www.mozilla.org/about/legal/terms/firefox-hello/");
|
||||
pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/firefox-hello/");
|
||||
pref("loop.do_not_disturb", false);
|
||||
pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/ringtone.ogg");
|
||||
pref("loop.retry_delay.start", 60000);
|
||||
pref("loop.retry_delay.limit", 300000);
|
||||
pref("loop.ping.interval", 1800000);
|
||||
@@ -30,5 +29,5 @@ pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src * data:; font
|
||||
#endif
|
||||
pref("loop.fxa_oauth.tokendata", "");
|
||||
pref("loop.fxa_oauth.profile", "");
|
||||
pref("loop.support_url", "https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc");
|
||||
pref("loop.support_url", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/cobrowsing");
|
||||
pref("loop.browserSharing.showInfoBar", true);
|
||||
|
||||
@@ -587,10 +587,6 @@ html[dir="rtl"] .context-wrapper > .context-preview {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.clicks-allowed.context-wrapper:hover {
|
||||
border: 2px solid #5cccee;
|
||||
}
|
||||
|
||||
/* Only underline the url, not the associated text */
|
||||
.clicks-allowed.context-wrapper:hover > .context-info > .context-url {
|
||||
text-decoration: underline;
|
||||
|
||||
@@ -15,10 +15,10 @@ button::-moz-focus-inner {
|
||||
z-index: 1020; /* required to have it superimposed to the video element */
|
||||
border: 0;
|
||||
left: 1.2rem;
|
||||
right: 1.2rem;
|
||||
height: 2.4rem;
|
||||
position: absolute;
|
||||
bottom: 1.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .conversation-toolbar {
|
||||
@@ -95,14 +95,6 @@ html[dir="rtl"] .conversation-toolbar-media-btn-group-box > button:first-child:h
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.conversation-toolbar-btn-box.btn-edit-entry {
|
||||
float: right;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .conversation-toolbar-btn-box.btn-edit-entry {
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* conversationViews.jsx */
|
||||
|
||||
.conversation-toolbar .btn-hangup {
|
||||
@@ -168,8 +160,7 @@ html[dir="rtl"] .conversation-toolbar-btn-box.btn-edit-entry {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.call-action-group > .btn,
|
||||
.room-context > .btn {
|
||||
.call-action-group > .btn {
|
||||
min-height: 30px;
|
||||
border-radius: 4px;
|
||||
margin: 0 4px;
|
||||
@@ -509,92 +500,15 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
||||
margin: 0 1rem 1.5rem 1rem;
|
||||
}
|
||||
|
||||
.room-context {
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: .9em;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-content: flex-start;
|
||||
align-items: flex-start;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
/* Make the context view float atop the video elements. */
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.room-invitation-overlay .room-context {
|
||||
position: relative;
|
||||
left: auto;
|
||||
bottom: auto;
|
||||
flex: 0 1 auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.room-context > .error-display-area.error {
|
||||
display: block;
|
||||
background-color: rgba(215,67,69,.8);
|
||||
border-radius: 3px;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
.room-context > .error-display-area {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.room-context > .error-display-area.error {
|
||||
margin: 1em 0 .5em 0;
|
||||
text-align: center;
|
||||
text-shadow: 1px 1px 0 rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.room-invitation-content,
|
||||
.room-context-header {
|
||||
.room-invitation-content {
|
||||
color: #333;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
.room-context > form {
|
||||
margin-bottom: 1rem;
|
||||
padding: .5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.room-context > form > textarea,
|
||||
.room-context > form > input[type="text"] {
|
||||
border: 1px solid #c3c3c3;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
color: #4a4a4a;
|
||||
display: block;
|
||||
font-size: 1.1rem;
|
||||
height: 2.6rem;
|
||||
margin: 10px 0;
|
||||
outline: none;
|
||||
padding: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.room-context > form > textarea {
|
||||
font-family: inherit;
|
||||
height: 5.2rem;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.room-context > form > textarea::-moz-placeholder,
|
||||
.room-context > form > input::-moz-placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.room-context > form > textarea:focus,
|
||||
.room-context > form > input:focus {
|
||||
border: 0.1rem solid #5cccee;
|
||||
.room-invitation-content > p {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.media-layout {
|
||||
@@ -802,8 +716,16 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
||||
}
|
||||
}
|
||||
|
||||
/* e.g. very narrow widths similar to conversation window */
|
||||
@media screen and (max-width:350px) {
|
||||
/* e.g. very narrow widths similar to conversation window.
|
||||
Note: on some displays (e.g. windows / medium size) the width
|
||||
may be very slightly over the expected width, so we add on 2px
|
||||
just in case. */
|
||||
@media screen and (max-width:352px) {
|
||||
.conversation-toolbar {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.media-layout > .media-wrapper {
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
@@ -905,23 +827,23 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
||||
/* aligns paragraph to right side */
|
||||
justify-content: flex-end;
|
||||
margin-left: 0;
|
||||
margin-right: 4px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.text-chat-entry.received {
|
||||
margin-left: 2px;
|
||||
margin-left: 10px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .text-chat-entry.sent {
|
||||
margin-left: 5px;
|
||||
margin-left: 10px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
|
||||
html[dir="rtl"] .text-chat-entry.received {
|
||||
margin-left: 0;
|
||||
margin-right: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.text-chat-entry > p {
|
||||
@@ -940,6 +862,11 @@ html[dir="rtl"] .text-chat-entry.received {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.text-chat-entry > .context-wrapper {
|
||||
flex: 0 1 auto;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.text-chat-entry.sent > p {
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
@@ -1056,6 +983,21 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
/* Context updated */
|
||||
.text-chat-entry > .context-content > .context-wrapper {
|
||||
max-width: 300px;
|
||||
min-height: 50px;
|
||||
background: #fff;
|
||||
border: 1px solid rgba(0,0,0,.10);
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
.text-chat-entry.received > .context-content > .context-wrapper {
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.text-chat-header.special.room-name {
|
||||
color: #666;
|
||||
font-weight: bold;
|
||||
@@ -1120,8 +1062,11 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
|
||||
border-top: 1px solid #66c9f2;
|
||||
}
|
||||
|
||||
/* e.g. very narrow widths similar to conversation window */
|
||||
@media screen and (max-width:350px) {
|
||||
/* e.g. very narrow widths similar to conversation window.
|
||||
Note: on some displays (e.g. windows / medium size) the width
|
||||
may be very slightly over the expected width, so we add on 2px
|
||||
just in case. */
|
||||
@media screen and (max-width:352px) {
|
||||
.text-chat-view {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
@@ -326,6 +326,7 @@ loop.shared.actions = (function() {
|
||||
// newRoomDescription: String, Optional.
|
||||
// newRoomThumbnail: String, Optional.
|
||||
// newRoomURL: String Optional.
|
||||
// sentTimestamp: String, Optional.
|
||||
}),
|
||||
|
||||
/**
|
||||
|
||||
@@ -385,7 +385,7 @@ loop.store.ActiveRoomStore = (function() {
|
||||
* and decryption is complete.
|
||||
*/
|
||||
_getRoomDataForStandalone: function(roomCryptoKey) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new Promise(function(resolve) {
|
||||
loop.request("Rooms:Get", this._storeState.roomToken).then(function(result) {
|
||||
if (result.isError) {
|
||||
resolve(new sharedActions.RoomFailure({
|
||||
@@ -438,7 +438,7 @@ loop.store.ActiveRoomStore = (function() {
|
||||
roomInfoData.roomName = realResult.roomName;
|
||||
|
||||
resolve(roomInfoData);
|
||||
}, function(error) {
|
||||
}, function() {
|
||||
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.DECRYPT_FAILED;
|
||||
resolve(roomInfoData);
|
||||
});
|
||||
@@ -454,7 +454,7 @@ loop.store.ActiveRoomStore = (function() {
|
||||
* if Firefox can handle the room.
|
||||
*/
|
||||
_promiseDetectUserAgentHandles: function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new Promise(function(resolve) {
|
||||
function resolveWithNotHandlingResponse() {
|
||||
resolve(new sharedActions.UserAgentHandlesRoom({
|
||||
handlesRoom: false
|
||||
@@ -568,9 +568,8 @@ loop.store.ActiveRoomStore = (function() {
|
||||
/**
|
||||
* Handles the deletion of a room, notified by the Loop rooms API.
|
||||
*
|
||||
* @param {Object} roomData The roomData of the deleted room
|
||||
*/
|
||||
_handleRoomDelete: function(roomData) {
|
||||
_handleRoomDelete: function() {
|
||||
this._sdkDriver.forceDisconnectAll(function() {
|
||||
window.close();
|
||||
});
|
||||
@@ -926,7 +925,8 @@ loop.store.ActiveRoomStore = (function() {
|
||||
|
||||
// The browser being shared changed, so update to the new context
|
||||
loop.request("GetSelectedTabMetadata").then(function(meta) {
|
||||
if (!meta) {
|
||||
// Avoid sending the event if there is no data nor participants nor url
|
||||
if (!meta || !meta.url || !this._hasParticipants()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -951,7 +951,7 @@ loop.store.ActiveRoomStore = (function() {
|
||||
*
|
||||
* @param {sharedActions.StartBrowserShare} actionData
|
||||
*/
|
||||
startBrowserShare: function(actionData) {
|
||||
startBrowserShare: function() {
|
||||
// For the unit test we already set the state here, instead of indirectly
|
||||
// via an action, because actions are queued thus depending on the
|
||||
// asynchronous nature of `loop.request`.
|
||||
@@ -960,9 +960,6 @@ loop.store.ActiveRoomStore = (function() {
|
||||
state: SCREEN_SHARE_STATES.PENDING
|
||||
}));
|
||||
|
||||
var options = {
|
||||
videoSource: "browser"
|
||||
};
|
||||
this._browserSharingListener = this._handleSwitchBrowserShare.bind(this);
|
||||
|
||||
// Set up a listener for watching screen shares. This will get notified
|
||||
@@ -1224,6 +1221,24 @@ loop.store.ActiveRoomStore = (function() {
|
||||
*/
|
||||
sendTextChatMessage: function(actionData) {
|
||||
this._handleTextChatMessage(actionData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the room is empty or has participants.
|
||||
*
|
||||
*/
|
||||
_hasParticipants: function() {
|
||||
// Update the participants to just the owner.
|
||||
var participants = this.getStoreState("participants");
|
||||
if (participants) {
|
||||
participants = participants.filter(function(participant) {
|
||||
return !participant.owner;
|
||||
});
|
||||
|
||||
return participants.length > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
||||
|
||||
/* 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/. */
|
||||
@@ -5,7 +7,7 @@
|
||||
var loop = loop || {};
|
||||
loop.shared = loop.shared || {};
|
||||
loop.shared.views = loop.shared.views || {};
|
||||
loop.shared.views.LinkifiedTextView = (function() {
|
||||
loop.shared.views.LinkifiedTextView = function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
@@ -13,7 +15,10 @@ loop.shared.views.LinkifiedTextView = (function() {
|
||||
* links starting with http://, https://, or ftp:// as actual clickable
|
||||
* links inside a <p> container.
|
||||
*/
|
||||
var LinkifiedTextView = React.createClass({displayName: "LinkifiedTextView",
|
||||
|
||||
var LinkifiedTextView = React.createClass({
|
||||
displayName: "LinkifiedTextView",
|
||||
|
||||
propTypes: {
|
||||
// Call this instead of allowing the default <a> click semantics, if
|
||||
// given. Also causes sendReferrer and suppressTarget attributes to be
|
||||
@@ -28,18 +33,16 @@ loop.shared.views.LinkifiedTextView = (function() {
|
||||
suppressTarget: React.PropTypes.bool
|
||||
},
|
||||
|
||||
mixins: [
|
||||
React.addons.PureRenderMixin
|
||||
],
|
||||
mixins: [React.addons.PureRenderMixin],
|
||||
|
||||
_handleClickEvent: function(e) {
|
||||
_handleClickEvent: function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.props.linkClickHandler(e.currentTarget.href);
|
||||
},
|
||||
|
||||
_generateLinkAttributes: function(href) {
|
||||
_generateLinkAttributes: function (href) {
|
||||
var linkAttributes = {
|
||||
href: href
|
||||
};
|
||||
@@ -71,7 +74,7 @@ loop.shared.views.LinkifiedTextView = (function() {
|
||||
*
|
||||
* @returns {Array} of strings and React <a> elements in order.
|
||||
*/
|
||||
parseStringToElements: function(s) {
|
||||
parseStringToElements: function (s) {
|
||||
var elements = [];
|
||||
var result = loop.shared.urlRegExps.fullUrlMatch.exec(s);
|
||||
var reactElementsCounter = 0; // For giving keys to each ReactElement.
|
||||
@@ -85,12 +88,12 @@ loop.shared.views.LinkifiedTextView = (function() {
|
||||
}
|
||||
|
||||
// Push the first link itself, and advance the string pointer again.
|
||||
elements.push(
|
||||
React.createElement("a", React.__spread({}, this._generateLinkAttributes(result[0]) ,
|
||||
{key: reactElementsCounter++}),
|
||||
elements.push(React.createElement(
|
||||
"a",
|
||||
_extends({}, this._generateLinkAttributes(result[0]), {
|
||||
key: reactElementsCounter++ }),
|
||||
result[0]
|
||||
)
|
||||
);
|
||||
));
|
||||
s = s.substr(result[0].length);
|
||||
|
||||
// Check for another link, and perhaps continue...
|
||||
@@ -104,13 +107,14 @@ loop.shared.views.LinkifiedTextView = (function() {
|
||||
return elements;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.createElement("p", null, this.parseStringToElements(this.props.rawText))
|
||||
render: function () {
|
||||
return React.createElement(
|
||||
"p",
|
||||
null,
|
||||
this.parseStringToElements(this.props.rawText)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return LinkifiedTextView;
|
||||
|
||||
})();
|
||||
}();
|
||||
|
||||
@@ -133,11 +133,12 @@ loop.OTSdkDriver = (function() {
|
||||
// the initial connect of the session. This saves time when setting up
|
||||
// the media.
|
||||
this.publisher = this.sdk.initPublisher(this._mockPublisherEl,
|
||||
_.extend(this._getDataChannelSettings, this._getCopyPublisherConfig));
|
||||
_.extend(this._getDataChannelSettings, this._getCopyPublisherConfig),
|
||||
this._onPublishComplete.bind(this));
|
||||
|
||||
this.publisher.on("streamCreated", this._onLocalStreamCreated.bind(this));
|
||||
this.publisher.on("streamDestroyed", this._onLocalStreamDestroyed.bind(this));
|
||||
this.publisher.on("accessAllowed", this._onPublishComplete.bind(this));
|
||||
this.publisher.on("accessAllowed", this._onPublishAllowed.bind(this));
|
||||
this.publisher.on("accessDenied", this._onPublishDenied.bind(this));
|
||||
this.publisher.on("accessDialogOpened",
|
||||
this._onAccessDialogOpened.bind(this));
|
||||
@@ -185,9 +186,9 @@ loop.OTSdkDriver = (function() {
|
||||
this._mockScreenSharePreviewEl = document.createElement("div");
|
||||
|
||||
this.screenshare = this.sdk.initPublisher(this._mockScreenSharePreviewEl,
|
||||
config);
|
||||
config, this._onScreenSharePublishComplete.bind(this));
|
||||
this.screenshare.on("accessAllowed", this._onScreenShareGranted.bind(this));
|
||||
this.screenshare.on("accessDenied", this._onScreenShareDenied.bind(this));
|
||||
this.screenshare.on("accessDenied", this._onScreenSharePublishError.bind(this));
|
||||
this.screenshare.on("streamCreated", this._onScreenShareStreamCreated.bind(this));
|
||||
|
||||
this._noteSharingState(options.videoSource, true);
|
||||
@@ -902,7 +903,7 @@ loop.OTSdkDriver = (function() {
|
||||
*
|
||||
* @param {OT.Event} event
|
||||
*/
|
||||
_onPublishComplete: function(event) {
|
||||
_onPublishAllowed: function(event) {
|
||||
event.preventDefault();
|
||||
this._publisherReady = true;
|
||||
|
||||
@@ -911,6 +912,33 @@ loop.OTSdkDriver = (function() {
|
||||
this._maybePublishLocalStream();
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* Handles publisher Complete.
|
||||
*
|
||||
* @param {Error} error An OT error object, null if there was no error.
|
||||
*/
|
||||
_onPublishComplete: function(error) {
|
||||
if (!error) {
|
||||
// Nothing to do for the success case.
|
||||
return;
|
||||
}
|
||||
if (!(error.message && error.message === "DENIED")) {
|
||||
// We free up the publisher here in case the store wants to try
|
||||
// grabbing the media again.
|
||||
if (this.publisher) {
|
||||
this.publisher.off("accessAllowed accessDenied accessDialogOpened streamCreated");
|
||||
this.publisher.destroy();
|
||||
delete this.publisher;
|
||||
delete this._mockPublisherEl;
|
||||
}
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
||||
}));
|
||||
this._notifyMetricsEvent("sdk.exception." + error.code + "." + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles publishing of media being denied.
|
||||
*
|
||||
@@ -936,26 +964,6 @@ loop.OTSdkDriver = (function() {
|
||||
}));
|
||||
this._notifyMetricsEvent("sdk.exception." + event.code);
|
||||
break;
|
||||
case OT.ExceptionCodes.UNABLE_TO_PUBLISH:
|
||||
if (event.message === "GetUserMedia") {
|
||||
// We free up the publisher here in case the store wants to try
|
||||
// grabbing the media again.
|
||||
if (this.publisher) {
|
||||
this.publisher.off("accessAllowed accessDenied accessDialogOpened streamCreated");
|
||||
this.publisher.destroy();
|
||||
delete this.publisher;
|
||||
delete this._mockPublisherEl;
|
||||
}
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
||||
}));
|
||||
// No exception logging as this is a handled event.
|
||||
} else {
|
||||
// We need to log the message so that we can understand where the exception
|
||||
// is coming from. Potentially a temporary addition.
|
||||
this._notifyMetricsEvent("sdk.exception." + event.code + "." + event.message);
|
||||
}
|
||||
break;
|
||||
case OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE:
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||
reason: FAILURE_DETAILS.TOS_FAILURE
|
||||
@@ -1054,12 +1062,37 @@ loop.OTSdkDriver = (function() {
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a screenshare is denied. Notifies the other stores.
|
||||
* Called when a screenshare is complete.
|
||||
*
|
||||
* @param {Error} error An OT error object, null if there was no error.
|
||||
*/
|
||||
_onScreenShareDenied: function() {
|
||||
_onScreenSharePublishComplete: function(error) {
|
||||
if (!error) {
|
||||
// Nothing to do for the success case.
|
||||
return;
|
||||
}
|
||||
|
||||
// Free up publisher
|
||||
this.screenshare.off("accessAllowed accessDenied streamCreated");
|
||||
this.screenshare.destroy();
|
||||
delete this.screenshare;
|
||||
delete this._mockScreenSharePreviewEl;
|
||||
this.dispatcher.dispatch(new sharedActions.ScreenSharingState({
|
||||
state: SCREEN_SHARE_STATES.INACTIVE
|
||||
}));
|
||||
this._notifyMetricsEvent("sdk.exception.screen." + error.code + "." + error.message);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a screenshare is denied. Notifies the other stores.
|
||||
*/
|
||||
_onScreenSharePublishError: function() {
|
||||
this.dispatcher.dispatch(new sharedActions.ScreenSharingState({
|
||||
state: SCREEN_SHARE_STATES.INACTIVE
|
||||
}));
|
||||
this.screenshare.off("accessAllowed accessDenied streamCreated");
|
||||
this.screenshare.destroy();
|
||||
delete this.screenshare;
|
||||
delete this._mockScreenSharePreviewEl;
|
||||
},
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ loop.store.TextChatStore = (function() {
|
||||
"dataChannelsAvailable",
|
||||
"receivedTextChatMessage",
|
||||
"sendTextChatMessage",
|
||||
"updateRoomInfo"
|
||||
"updateRoomInfo",
|
||||
"updateRoomContext"
|
||||
],
|
||||
|
||||
/**
|
||||
@@ -134,7 +135,8 @@ loop.store.TextChatStore = (function() {
|
||||
receivedTextChatMessage: function(actionData) {
|
||||
// If we don't know how to deal with this content, then skip it
|
||||
// as this version doesn't support it.
|
||||
if (actionData.contentType !== CHAT_CONTENT_TYPES.TEXT) {
|
||||
if (actionData.contentType !== CHAT_CONTENT_TYPES.TEXT &&
|
||||
actionData.contentType !== CHAT_CONTENT_TYPES.CONTEXT_TILE) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -187,6 +189,57 @@ loop.store.TextChatStore = (function() {
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles receiving information about the room context due to a change of the tabs
|
||||
*
|
||||
* @param {sharedActions.updateRoomContext} actionData
|
||||
*/
|
||||
updateRoomContext: function(actionData) {
|
||||
// Firstly, check if there is a previous context tile, if not, create it
|
||||
var contextTile = null;
|
||||
|
||||
for (var i = this._storeState.messageList.length - 1; i >= 0; i--) {
|
||||
if (this._storeState.messageList[i].contentType === CHAT_CONTENT_TYPES.CONTEXT_TILE) {
|
||||
contextTile = this._storeState.messageList[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!contextTile) {
|
||||
this._appendContextTileMessage(actionData);
|
||||
return;
|
||||
}
|
||||
|
||||
var oldDomain = new URL(contextTile.extraData.newRoomURL).hostname;
|
||||
var currentDomain = new URL(actionData.newRoomURL).hostname;
|
||||
|
||||
if (oldDomain === currentDomain) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._appendContextTileMessage(actionData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Appends a context tile message to the UI and sends it.
|
||||
*
|
||||
* @param {sharedActions.updateRoomContext} data
|
||||
*/
|
||||
_appendContextTileMessage: function(data) {
|
||||
var msgData = {
|
||||
contentType: CHAT_CONTENT_TYPES.CONTEXT_TILE,
|
||||
message: data.newRoomDescription,
|
||||
extraData: {
|
||||
roomToken: data.roomToken,
|
||||
newRoomThumbnail: data.newRoomThumbnail,
|
||||
newRoomURL: data.newRoomURL
|
||||
},
|
||||
sentTimestamp: (new Date()).toISOString()
|
||||
};
|
||||
|
||||
this.sendTextChatMessage(msgData);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
||||
|
||||
/* 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/. */
|
||||
@@ -5,7 +7,7 @@
|
||||
var loop = loop || {};
|
||||
loop.shared = loop.shared || {};
|
||||
loop.shared.views = loop.shared.views || {};
|
||||
loop.shared.views.chat = (function(mozL10n) {
|
||||
loop.shared.views.chat = function (mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
@@ -17,15 +19,20 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
/**
|
||||
* Renders an individual entry for the text chat entries view.
|
||||
*/
|
||||
var TextChatEntry = React.createClass({displayName: "TextChatEntry",
|
||||
var TextChatEntry = React.createClass({
|
||||
displayName: "TextChatEntry",
|
||||
|
||||
mixins: [React.addons.PureRenderMixin],
|
||||
|
||||
propTypes: {
|
||||
contentType: React.PropTypes.string.isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
|
||||
extraData: React.PropTypes.object,
|
||||
message: React.PropTypes.string.isRequired,
|
||||
showTimestamp: React.PropTypes.bool.isRequired,
|
||||
timestamp: React.PropTypes.string.isRequired,
|
||||
type: React.PropTypes.string.isRequired
|
||||
type: React.PropTypes.string.isRequired,
|
||||
useDesktopPaths: React.PropTypes.bool
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -33,21 +40,18 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
* (or L10N equivalent).
|
||||
*
|
||||
*/
|
||||
_renderTimestamp: function() {
|
||||
_renderTimestamp: function () {
|
||||
var date = new Date(this.props.timestamp);
|
||||
var language = mozL10n.language ? mozL10n.language.code
|
||||
: mozL10n.getLanguage();
|
||||
|
||||
return (
|
||||
React.createElement("span", {className: "text-chat-entry-timestamp"},
|
||||
date.toLocaleTimeString(language,
|
||||
{ hour: "numeric", minute: "numeric",
|
||||
return React.createElement(
|
||||
"span",
|
||||
{ className: "text-chat-entry-timestamp" },
|
||||
date.toLocaleTimeString(mozL10n.language.code, { hour: "numeric", minute: "numeric",
|
||||
hour12: false })
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render: function () {
|
||||
var classes = classNames({
|
||||
"text-chat-entry": true,
|
||||
"received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED,
|
||||
@@ -58,33 +62,54 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
|
||||
var optionalProps = {};
|
||||
if (loop.shared.utils.isDesktop()) {
|
||||
optionalProps.linkClickHandler = function(url) {
|
||||
optionalProps.linkClickHandler = function (url) {
|
||||
loop.request("OpenURL", url);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: classes},
|
||||
React.createElement(sharedViews.LinkifiedTextView, React.__spread({}, optionalProps,
|
||||
{rawText: this.props.message})),
|
||||
React.createElement("span", {className: "text-chat-arrow"}),
|
||||
if (this.props.contentType === CHAT_CONTENT_TYPES.CONTEXT_TILE) {
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ className: classes },
|
||||
React.createElement(sharedViews.ContextUrlView, {
|
||||
allowClick: true,
|
||||
description: this.props.message,
|
||||
dispatcher: this.props.dispatcher,
|
||||
thumbnail: this.props.extraData.newRoomThumbnail,
|
||||
url: this.props.extraData.newRoomURL,
|
||||
useDesktopPaths: this.props.useDesktopPaths }),
|
||||
this.props.showTimestamp ? this._renderTimestamp() : null
|
||||
);
|
||||
}
|
||||
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ className: classes },
|
||||
React.createElement(sharedViews.LinkifiedTextView, _extends({}, optionalProps, {
|
||||
rawText: this.props.message })),
|
||||
React.createElement("span", { className: "text-chat-arrow" }),
|
||||
this.props.showTimestamp ? this._renderTimestamp() : null
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var TextChatRoomName = React.createClass({displayName: "TextChatRoomName",
|
||||
var TextChatRoomName = React.createClass({
|
||||
displayName: "TextChatRoomName",
|
||||
|
||||
mixins: [React.addons.PureRenderMixin],
|
||||
|
||||
propTypes: {
|
||||
message: React.PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.createElement("div", {className: "text-chat-header special room-name"},
|
||||
React.createElement("p", null, mozL10n.get("rooms_welcome_title", { conversationName: this.props.message }))
|
||||
render: function () {
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ className: "text-chat-header special room-name" },
|
||||
React.createElement(
|
||||
"p",
|
||||
null,
|
||||
mozL10n.get("rooms_welcome_title", { conversationName: this.props.message })
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -95,11 +120,10 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
* TextChatView so that scrolling can be managed more efficiently - this
|
||||
* component only updates when the message list is changed.
|
||||
*/
|
||||
var TextChatEntriesView = React.createClass({displayName: "TextChatEntriesView",
|
||||
mixins: [
|
||||
React.addons.PureRenderMixin,
|
||||
sharedMixins.AudioMixin
|
||||
],
|
||||
var TextChatEntriesView = React.createClass({
|
||||
displayName: "TextChatEntriesView",
|
||||
|
||||
mixins: [React.addons.PureRenderMixin, sharedMixins.AudioMixin],
|
||||
|
||||
statics: {
|
||||
ONE_MINUTE: 60
|
||||
@@ -112,31 +136,30 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
useDesktopPaths: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
receivedMessageCount: 0
|
||||
};
|
||||
},
|
||||
|
||||
_hasChatMessages: function() {
|
||||
return this.props.messageList.some(function(message) {
|
||||
_hasChatMessages: function () {
|
||||
return this.props.messageList.some(function (message) {
|
||||
return message.contentType === CHAT_CONTENT_TYPES.TEXT;
|
||||
});
|
||||
},
|
||||
|
||||
componentWillUpdate: function() {
|
||||
componentWillUpdate: function () {
|
||||
var node = this.getDOMNode();
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
// Scroll only if we're right at the bottom of the display, or if we've
|
||||
// not had any chat messages so far.
|
||||
this.shouldScroll = !this._hasChatMessages() ||
|
||||
node.scrollHeight === node.scrollTop + node.clientHeight;
|
||||
this.shouldScroll = !this._hasChatMessages() || node.scrollHeight === node.scrollTop + node.clientHeight;
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
var receivedMessageCount = nextProps.messageList.filter(function(message) {
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
var receivedMessageCount = nextProps.messageList.filter(function (message) {
|
||||
return message.type === CHAT_MESSAGE_TYPES.RECEIVED;
|
||||
}).length;
|
||||
|
||||
@@ -147,12 +170,12 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
componentDidUpdate: function () {
|
||||
// Don't scroll if we haven't got any chat messages yet - e.g. for context
|
||||
// display, we want to display starting at the top.
|
||||
if (this.shouldScroll && this._hasChatMessages()) {
|
||||
// This ensures the paint is complete.
|
||||
window.requestAnimationFrame(function() {
|
||||
window.requestAnimationFrame(function () {
|
||||
try {
|
||||
var node = this.getDOMNode();
|
||||
node.scrollTop = node.scrollHeight - node.clientHeight;
|
||||
@@ -163,7 +186,7 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render: function () {
|
||||
/* Keep track of the last printed timestamp. */
|
||||
var lastTimestamp = 0;
|
||||
|
||||
@@ -171,35 +194,36 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
"text-chat-entries": true
|
||||
});
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: entriesClasses},
|
||||
React.createElement("div", {className: "text-chat-scroller"},
|
||||
|
||||
this.props.messageList.map(function(entry, i) {
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ className: entriesClasses },
|
||||
React.createElement(
|
||||
"div",
|
||||
{ className: "text-chat-scroller" },
|
||||
this.props.messageList.map(function (entry, i) {
|
||||
if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
|
||||
if (!this.props.showInitialContext) { return null; }
|
||||
if (!this.props.showInitialContext) {
|
||||
return null;
|
||||
}
|
||||
switch (entry.contentType) {
|
||||
case CHAT_CONTENT_TYPES.ROOM_NAME:
|
||||
return (
|
||||
React.createElement(TextChatRoomName, {
|
||||
return React.createElement(TextChatRoomName, {
|
||||
key: i,
|
||||
message: entry.message})
|
||||
);
|
||||
message: entry.message });
|
||||
case CHAT_CONTENT_TYPES.CONTEXT:
|
||||
return (
|
||||
React.createElement("div", {className: "context-url-view-wrapper", key: i},
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ className: "context-url-view-wrapper", key: i },
|
||||
React.createElement(sharedViews.ContextUrlView, {
|
||||
allowClick: true,
|
||||
description: entry.message,
|
||||
dispatcher: this.props.dispatcher,
|
||||
thumbnail: entry.extraData.thumbnail,
|
||||
url: entry.extraData.location,
|
||||
useDesktopPaths: this.props.useDesktopPaths})
|
||||
)
|
||||
useDesktopPaths: this.props.useDesktopPaths })
|
||||
);
|
||||
default:
|
||||
console.error("Unsupported contentType",
|
||||
entry.contentType);
|
||||
console.error("Unsupported contentType", entry.contentType);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -208,24 +232,22 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
var timestamp = entry.receivedTimestamp || entry.sentTimestamp;
|
||||
|
||||
var timeDiff = this._isOneMinDelta(timestamp, lastTimestamp);
|
||||
var shouldShowTimestamp = this._shouldShowTimestamp(i,
|
||||
timeDiff);
|
||||
var shouldShowTimestamp = this._shouldShowTimestamp(i, timeDiff);
|
||||
|
||||
if (shouldShowTimestamp) {
|
||||
lastTimestamp = timestamp;
|
||||
}
|
||||
|
||||
return (
|
||||
React.createElement(TextChatEntry, {contentType: entry.contentType,
|
||||
return React.createElement(TextChatEntry, { contentType: entry.contentType,
|
||||
dispatcher: this.props.dispatcher,
|
||||
extraData: entry.extraData,
|
||||
key: i,
|
||||
message: entry.message,
|
||||
showTimestamp: shouldShowTimestamp,
|
||||
timestamp: timestamp,
|
||||
type: entry.type})
|
||||
);
|
||||
type: entry.type,
|
||||
useDesktopPaths: this.props.useDesktopPaths });
|
||||
}, this)
|
||||
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
@@ -239,14 +261,13 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
* @param {boolean} timeDiff If difference between consecutive messages is
|
||||
* bigger than one minute.
|
||||
*/
|
||||
_shouldShowTimestamp: function(idx, timeDiff) {
|
||||
_shouldShowTimestamp: function (idx, timeDiff) {
|
||||
if (!idx) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If consecutive messages are from different senders */
|
||||
if (this.props.messageList[idx].type !==
|
||||
this.props.messageList[idx - 1].type) {
|
||||
if (this.props.messageList[idx].type !== this.props.messageList[idx - 1].type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -262,7 +283,7 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
* @param {string} currTime Timestamp of message yet to be rendered.
|
||||
* @param {string} prevTime Last timestamp printed in the chat view.
|
||||
*/
|
||||
_isOneMinDelta: function(currTime, prevTime) {
|
||||
_isOneMinDelta: function (currTime, prevTime) {
|
||||
var date1 = new Date(currTime);
|
||||
var date2 = new Date(prevTime);
|
||||
var delta = date1 - date2;
|
||||
@@ -283,11 +304,10 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
* @property {Boolean} textChatEnabled Set to true to enable the box. If false, the
|
||||
* text chat box won't be displayed.
|
||||
*/
|
||||
var TextChatInputView = React.createClass({displayName: "TextChatInputView",
|
||||
mixins: [
|
||||
React.addons.LinkedStateMixin,
|
||||
React.addons.PureRenderMixin
|
||||
],
|
||||
var TextChatInputView = React.createClass({
|
||||
displayName: "TextChatInputView",
|
||||
|
||||
mixins: [React.addons.LinkedStateMixin, React.addons.PureRenderMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
@@ -295,7 +315,7 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
textChatEnabled: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
messageDetail: ""
|
||||
};
|
||||
@@ -307,7 +327,7 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
*
|
||||
* @param {Object} event The DOM event.
|
||||
*/
|
||||
handleKeyDown: function(event) {
|
||||
handleKeyDown: function (event) {
|
||||
if (event.which === 13) {
|
||||
this.handleFormSubmit(event);
|
||||
}
|
||||
@@ -318,7 +338,7 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
*
|
||||
* @param {Object} event The DOM event.
|
||||
*/
|
||||
handleFormSubmit: function(event) {
|
||||
handleFormSubmit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Don't send empty messages.
|
||||
@@ -329,27 +349,29 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.SendTextChatMessage({
|
||||
contentType: CHAT_CONTENT_TYPES.TEXT,
|
||||
message: this.state.messageDetail,
|
||||
sentTimestamp: (new Date()).toISOString()
|
||||
sentTimestamp: new Date().toISOString()
|
||||
}));
|
||||
|
||||
// Reset the form to empty, ready for the next message.
|
||||
this.setState({ messageDetail: "" });
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render: function () {
|
||||
if (!this.props.textChatEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: "text-chat-box"},
|
||||
React.createElement("form", {onSubmit: this.handleFormSubmit},
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ className: "text-chat-box" },
|
||||
React.createElement(
|
||||
"form",
|
||||
{ onSubmit: this.handleFormSubmit },
|
||||
React.createElement("input", {
|
||||
onKeyDown: this.handleKeyDown,
|
||||
placeholder: this.props.showPlaceholder ? mozL10n.get("chat_textbox_placeholder") : "",
|
||||
type: "text",
|
||||
valueLink: this.linkState("messageDetail")})
|
||||
)
|
||||
valueLink: this.linkState("messageDetail") })
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -363,11 +385,10 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
* @property {Boolean} showInitialContext Set to true to show the room name
|
||||
* and initial context tile for linker clicker's special list items
|
||||
*/
|
||||
var TextChatView = React.createClass({displayName: "TextChatView",
|
||||
mixins: [
|
||||
React.addons.LinkedStateMixin,
|
||||
loop.store.StoreMixin("textChatStore")
|
||||
],
|
||||
var TextChatView = React.createClass({
|
||||
displayName: "TextChatView",
|
||||
|
||||
mixins: [React.addons.LinkedStateMixin, loop.store.StoreMixin("textChatStore")],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
@@ -375,25 +396,23 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
useDesktopPaths: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
getInitialState: function () {
|
||||
return this.getStoreState();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render: function () {
|
||||
var messageList = this.state.messageList;
|
||||
|
||||
// Filter out items not displayed when showing initial context.
|
||||
// We do this here so that we can set the classes correctly on the view.
|
||||
if (!this.props.showInitialContext) {
|
||||
messageList = messageList.filter(function(item) {
|
||||
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
|
||||
(item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME &&
|
||||
item.contentType !== CHAT_CONTENT_TYPES.CONTEXT);
|
||||
messageList = messageList.filter(function (item) {
|
||||
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL || item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME && item.contentType !== CHAT_CONTENT_TYPES.CONTEXT;
|
||||
});
|
||||
}
|
||||
|
||||
// Only show the placeholder if we've sent messages.
|
||||
var hasSentMessages = messageList.some(function(item) {
|
||||
var hasSentMessages = messageList.some(function (item) {
|
||||
return item.type === CHAT_MESSAGE_TYPES.SENT;
|
||||
});
|
||||
|
||||
@@ -403,18 +422,18 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
"text-chat-disabled": !this.state.textChatEnabled
|
||||
});
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: textChatViewClasses},
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ className: textChatViewClasses },
|
||||
React.createElement(TextChatEntriesView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
messageList: messageList,
|
||||
showInitialContext: this.props.showInitialContext,
|
||||
useDesktopPaths: this.props.useDesktopPaths}),
|
||||
useDesktopPaths: this.props.useDesktopPaths }),
|
||||
React.createElement(TextChatInputView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
showPlaceholder: !hasSentMessages,
|
||||
textChatEnabled: this.state.textChatEnabled})
|
||||
)
|
||||
textChatEnabled: this.state.textChatEnabled })
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -424,4 +443,4 @@ loop.shared.views.chat = (function(mozL10n) {
|
||||
TextChatEntry: TextChatEntry,
|
||||
TextChatView: TextChatView
|
||||
};
|
||||
})(navigator.mozL10n || document.mozL10n);
|
||||
}(navigator.mozL10n || document.mozL10n);
|
||||
|
||||
@@ -105,7 +105,8 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
||||
var CHAT_CONTENT_TYPES = {
|
||||
CONTEXT: "chat-context",
|
||||
TEXT: "chat-text",
|
||||
ROOM_NAME: "room-name"
|
||||
ROOM_NAME: "room-name",
|
||||
CONTEXT_TILE: "context-tile"
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -720,34 +721,6 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate a string if it exceeds the length as defined in `maxLen`, which
|
||||
* is defined as '72' characters by default. If the string needs trimming,
|
||||
* it'll be suffixed with the unicode ellipsis char, \u2026.
|
||||
*
|
||||
* @param {String} str The string to truncate, if needed.
|
||||
* @param {Number} maxLen Maximum number of characters that the string is
|
||||
* allowed to contain. Optional, defaults to 72.
|
||||
* @return {String} Truncated version of `str`.
|
||||
*/
|
||||
function truncate(str, maxLen) {
|
||||
maxLen = maxLen || 72;
|
||||
|
||||
if (str.length > maxLen) {
|
||||
var substring = str.substr(0, maxLen);
|
||||
// XXX Due to the fact that we have two different l10n libraries.
|
||||
var direction = mozL10n.getDirection ? mozL10n.getDirection() :
|
||||
mozL10n.language.direction;
|
||||
if (direction === "rtl") {
|
||||
return "…" + substring;
|
||||
}
|
||||
|
||||
return substring + "…";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the DOM hierarchy for a node matching `selector`.
|
||||
* If it is not found return the parent node, this is a sane default so
|
||||
@@ -802,7 +775,6 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
||||
strToUint8Array: strToUint8Array,
|
||||
Uint8ArrayToStr: Uint8ArrayToStr,
|
||||
objectDiff: objectDiff,
|
||||
stripFalsyValues: stripFalsyValues,
|
||||
truncate: truncate
|
||||
stripFalsyValues: stripFalsyValues
|
||||
};
|
||||
}).call(inChrome ? this : loop.shared);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"rules": {
|
||||
// This is useful for some of the tests, e.g.
|
||||
// expect(new Foo()).to.Throw(/error/)
|
||||
"no-new": 0
|
||||
}
|
||||
}
|
||||
@@ -540,7 +540,7 @@ describe("loop.store.ActiveRoomStore", function() {
|
||||
// easier.
|
||||
sandbox.stub(loop.crypto, "decryptBytes", function() {
|
||||
return {
|
||||
then: function(resolve, reject) {
|
||||
then: function(resolve) {
|
||||
resolve(JSON.stringify(roomContext));
|
||||
}
|
||||
};
|
||||
@@ -1552,10 +1552,15 @@ describe("loop.store.ActiveRoomStore", function() {
|
||||
store.setStoreState({
|
||||
roomState: ROOM_STATES.JOINED,
|
||||
roomToken: "fakeToken",
|
||||
sessionToken: "1627384950"
|
||||
sessionToken: "1627384950",
|
||||
participants: [{
|
||||
displayName: "Owner",
|
||||
owner: true
|
||||
}, {
|
||||
displayName: "Guest",
|
||||
owner: false
|
||||
}]
|
||||
});
|
||||
|
||||
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@@ -1563,6 +1568,7 @@ describe("loop.store.ActiveRoomStore", function() {
|
||||
});
|
||||
|
||||
it("should set the state to 'pending'", function() {
|
||||
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWith(dispatcher.dispatch,
|
||||
new sharedActions.ScreenSharingState({
|
||||
@@ -1571,10 +1577,12 @@ describe("loop.store.ActiveRoomStore", function() {
|
||||
});
|
||||
|
||||
it("should add a browser sharing listener for tab sharing", function() {
|
||||
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||
sinon.assert.calledOnce(requestStubs.AddBrowserSharingListener);
|
||||
});
|
||||
|
||||
it("should invoke the SDK driver with the correct options for tab sharing", function() {
|
||||
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||
sinon.assert.calledOnce(fakeSdkDriver.startScreenShare);
|
||||
sinon.assert.calledWith(fakeSdkDriver.startScreenShare, {
|
||||
videoSource: "browser",
|
||||
@@ -1586,6 +1594,7 @@ describe("loop.store.ActiveRoomStore", function() {
|
||||
});
|
||||
|
||||
it("should request the new metadata when the browser being shared change", function() {
|
||||
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||
clock.tick(500);
|
||||
sinon.assert.calledOnce(getSelectedTabMetadataStub);
|
||||
sinon.assert.calledTwice(dispatcher.dispatch);
|
||||
@@ -1599,6 +1608,7 @@ describe("loop.store.ActiveRoomStore", function() {
|
||||
});
|
||||
|
||||
it("should process only one request", function() {
|
||||
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||
// Simulates multiple requests.
|
||||
LoopMochaUtils.publish("BrowserSwitch", 72);
|
||||
LoopMochaUtils.publish("BrowserSwitch", 72);
|
||||
@@ -1614,6 +1624,39 @@ describe("loop.store.ActiveRoomStore", function() {
|
||||
roomToken: store.getStoreState().roomToken
|
||||
}));
|
||||
});
|
||||
|
||||
it("should not process a request without url", function() {
|
||||
clock.tick(500);
|
||||
getSelectedTabMetadataStub.returns({
|
||||
title: "fakeTitle",
|
||||
favicon: "fakeFavicon"
|
||||
});
|
||||
|
||||
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||
sinon.assert.calledOnce(getSelectedTabMetadataStub);
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
});
|
||||
|
||||
it("should not process a request if no-one is in the room", function() {
|
||||
store.setStoreState({
|
||||
roomState: ROOM_STATES.JOINED,
|
||||
roomToken: "fakeToken",
|
||||
sessionToken: "1627384950",
|
||||
participants: [{
|
||||
displayName: "Owner",
|
||||
owner: true
|
||||
}]
|
||||
});
|
||||
clock.tick(500);
|
||||
getSelectedTabMetadataStub.returns({
|
||||
title: "fakeTitle",
|
||||
favicon: "fakeFavicon"
|
||||
});
|
||||
|
||||
store.startBrowserShare(new sharedActions.StartBrowserShare());
|
||||
sinon.assert.calledOnce(getSelectedTabMetadataStub);
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Screen share Events", function() {
|
||||
|
||||
@@ -4,26 +4,71 @@ import threading
|
||||
import SimpleHTTPServer
|
||||
import SocketServer
|
||||
import BaseHTTPServer
|
||||
import socket
|
||||
import urllib
|
||||
import urlparse
|
||||
import os
|
||||
|
||||
DEBUG = False
|
||||
|
||||
|
||||
# XXX Once we're on a branch with bug 993478 landed, we may want to get
|
||||
# rid of this HTTP server and just use the built-in one from Marionette,
|
||||
# since there will less code to maintain, and it will be faster. We'll
|
||||
# need to consider whether this code wants to be shared with WebDriver tests
|
||||
# for other browsers, though.
|
||||
#
|
||||
|
||||
gCommonDir = None
|
||||
|
||||
# These redirects map the paths expected by the index.html files to the paths
|
||||
# in mozilla-central. In the github repo, the unit tests are run entirely within
|
||||
# karma, where the files are loaded directly. For mozilla-central we must map
|
||||
# the files to the appropriate place.
|
||||
REDIRECTIONS = {
|
||||
"/test/vendor": "/chrome/content/shared/test/vendor",
|
||||
"/shared/js": "/chrome/content/shared/js",
|
||||
"/shared/test": "/chrome/content/shared/test",
|
||||
"/shared/vendor": "/chrome/content/shared/vendor",
|
||||
"/add-on/panels/vendor": "/chrome/content/panels/vendor",
|
||||
"/add-on/panels/js": "/chrome/content/panels/js",
|
||||
"/add-on/shared/js": "/chrome/content/shared/js",
|
||||
"/add-on/shared/vendor": "/chrome/content/shared/vendor",
|
||||
}
|
||||
|
||||
|
||||
class ThreadingSimpleServer(SocketServer.ThreadingMixIn,
|
||||
BaseHTTPServer.HTTPServer):
|
||||
pass
|
||||
|
||||
|
||||
class QuietHttpRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
class HttpRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
def do_HEAD(s):
|
||||
lastSlash = s.path.rfind("/")
|
||||
path = s.path[:lastSlash]
|
||||
|
||||
if (path in REDIRECTIONS):
|
||||
filename = s.path[lastSlash:]
|
||||
|
||||
s.send_response(301)
|
||||
# Prefix the redirections with the common directory segment.
|
||||
s.send_header("Location", "/" + gCommonDir + REDIRECTIONS.get(path, "/") + filename)
|
||||
s.end_headers()
|
||||
else:
|
||||
SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(s)
|
||||
|
||||
def do_GET(s):
|
||||
lastSlash = s.path.rfind("/")
|
||||
path = s.path[:lastSlash]
|
||||
|
||||
if (path in REDIRECTIONS):
|
||||
s.do_HEAD()
|
||||
else:
|
||||
SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(s)
|
||||
|
||||
def log_message(self, format, *args, **kwargs):
|
||||
if DEBUG:
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.log_message(self, format, *args, **kwargs)
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
@@ -33,13 +78,8 @@ class BaseTestFrontendUnits(MarionetteTestCase):
|
||||
def setUpClass(cls):
|
||||
super(BaseTestFrontendUnits, cls).setUpClass()
|
||||
|
||||
if DEBUG:
|
||||
handler = SimpleHTTPServer.SimpleHTTPRequestHandler
|
||||
else:
|
||||
handler = QuietHttpRequestHandler
|
||||
|
||||
# Port 0 means to select an arbitrary unused port
|
||||
cls.server = ThreadingSimpleServer(('', 0), handler)
|
||||
cls.server = ThreadingSimpleServer(('', 0), HttpRequestHandler)
|
||||
cls.ip, cls.port = cls.server.server_address
|
||||
|
||||
cls.server_thread = threading.Thread(target=cls.server.serve_forever)
|
||||
@@ -78,6 +118,8 @@ class BaseTestFrontendUnits(MarionetteTestCase):
|
||||
|
||||
# srcdir_path should be the directory relative to this file.
|
||||
def set_server_prefix(self, srcdir_path):
|
||||
global gCommonDir
|
||||
|
||||
# We may be run from a different path than topsrcdir, e.g. in the case
|
||||
# of packaged tests. If so, then we have to work out the right directory
|
||||
# for the local server.
|
||||
@@ -90,6 +132,13 @@ class BaseTestFrontendUnits(MarionetteTestCase):
|
||||
|
||||
self.relPath = urllib.pathname2url(os.path.join(self.relPath, srcdir_path))
|
||||
|
||||
# This is the common directory segment, what you need to get from the
|
||||
# common path to the relative path location. Used to get the redirects
|
||||
# correct both locally and on the build systems.
|
||||
# The .replace is to change windows path slashes to unix like ones for
|
||||
# the url.
|
||||
gCommonDir = os.path.normpath(self.relPath).replace("\\", "//")
|
||||
|
||||
# Finally join the relative path with the given src path
|
||||
self.server_prefix = urlparse.urljoin("http://localhost:" + str(self.port),
|
||||
self.relPath)
|
||||
@@ -122,8 +171,9 @@ class BaseTestFrontendUnits(MarionetteTestCase):
|
||||
# but not from marionette, uncomment the two lines below to break
|
||||
# on failing tests, so that the browsers won't be torn down, and you
|
||||
# can use the browser debugging facilities to see what's going on.
|
||||
#from ipdb import set_trace
|
||||
#set_trace()
|
||||
#
|
||||
# from ipdb import set_trace
|
||||
# set_trace()
|
||||
|
||||
raise AssertionError(self.get_failure_details(page))
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Loop shared mocha tests</title>
|
||||
<link rel="stylesheet" media="all" href="vendor/mocha-2.2.5.css">
|
||||
<link rel="stylesheet" media="all" href="/test/vendor/mocha.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha">
|
||||
@@ -14,23 +14,26 @@
|
||||
</div>
|
||||
<div id="messages"></div>
|
||||
<div id="fixtures"></div>
|
||||
<script src="../../content/shared/vendor/lodash-3.9.3.js"></script>
|
||||
<script src="../shared/loop_mocha_utils.js"></script>
|
||||
<script src="/shared/vendor/lodash.js"></script>
|
||||
<script src="loop_mocha_utils.js"></script>
|
||||
<script>
|
||||
LoopMochaUtils.trapErrors();
|
||||
</script>
|
||||
|
||||
<!-- libs -->
|
||||
<script src="../../content/shared/vendor/react-0.13.3.js"></script>
|
||||
<script src="../../content/shared/vendor/classnames-2.2.0.js"></script>
|
||||
<script src="../../content/shared/vendor/backbone-1.2.1.js"></script>
|
||||
<script src="../../standalone/content/vendor/l10n-gaia-02ca67948fe8.js"></script>
|
||||
<script src="/shared/vendor/react.js"></script>
|
||||
<script src="/shared/vendor/classnames.js"></script>
|
||||
<script src="/shared/vendor/backbone.js"></script>
|
||||
|
||||
<!-- Use desktop's l10n.js, since that's all we have when we export to
|
||||
mozilla-central. -->
|
||||
<script src="/add-on/panels/vendor/l10n.js"></script>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<script src="vendor/mocha-2.2.5.js"></script>
|
||||
<script src="vendor/chai-3.0.0.js"></script>
|
||||
<script src="vendor/chai-as-promised-5.1.0.js"></script>
|
||||
<script src="vendor/sinon-1.16.1.js"></script>
|
||||
<script src="/test/vendor/mocha.js"></script>
|
||||
<script src="/test/vendor/chai.js"></script>
|
||||
<script src="/test/vendor/chai-as-promised.js"></script>
|
||||
<script src="/test/vendor/sinon.js"></script>
|
||||
<script>
|
||||
/*global chai, mocha */
|
||||
chai.config.includeStack = true;
|
||||
@@ -38,22 +41,22 @@
|
||||
</script>
|
||||
|
||||
<!-- App scripts -->
|
||||
<script src="../../content/shared/js/loopapi-client.js"></script>
|
||||
<script src="../../content/shared/js/utils.js"></script>
|
||||
<script src="../../content/shared/js/models.js"></script>
|
||||
<script src="../../content/shared/js/mixins.js"></script>
|
||||
<script src="../../content/shared/js/crypto.js"></script>
|
||||
<script src="../../content/shared/js/validate.js"></script>
|
||||
<script src="../../content/shared/js/actions.js"></script>
|
||||
<script src="../../content/shared/js/dispatcher.js"></script>
|
||||
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
||||
<script src="../../content/shared/js/store.js"></script>
|
||||
<script src="../../content/shared/js/activeRoomStore.js"></script>
|
||||
<script src="../../content/shared/js/views.js"></script>
|
||||
<script src="../../content/shared/js/textChatStore.js"></script>
|
||||
<script src="../../content/shared/js/textChatView.js"></script>
|
||||
<script src="../../content/shared/js/urlRegExps.js"></script>
|
||||
<script src="../../content/shared/js/linkifiedTextView.js"></script>
|
||||
<script src="/shared/js/loopapi-client.js"></script>
|
||||
<script src="/shared/js/utils.js"></script>
|
||||
<script src="/shared/js/models.js"></script>
|
||||
<script src="/shared/js/mixins.js"></script>
|
||||
<script src="/shared/js/crypto.js"></script>
|
||||
<script src="/shared/js/validate.js"></script>
|
||||
<script src="/shared/js/actions.js"></script>
|
||||
<script src="/shared/js/dispatcher.js"></script>
|
||||
<script src="/shared/js/otSdkDriver.js"></script>
|
||||
<script src="/shared/js/store.js"></script>
|
||||
<script src="/shared/js/activeRoomStore.js"></script>
|
||||
<script src="/shared/js/views.js"></script>
|
||||
<script src="/shared/js/textChatStore.js"></script>
|
||||
<script src="/shared/js/textChatView.js"></script>
|
||||
<script src="/shared/js/urlRegExps.js"></script>
|
||||
<script src="/shared/js/linkifiedTextView.js"></script>
|
||||
|
||||
<!-- Test scripts -->
|
||||
<script src="models_test.js"></script>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* exported LoopMochaUtils */
|
||||
|
||||
var LoopMochaUtils = (function(global, _) {
|
||||
"use strict";
|
||||
|
||||
@@ -30,7 +32,7 @@ var LoopMochaUtils = (function(global, _) {
|
||||
* @param {Function} asyncFn Function to invoke directly that contains async
|
||||
* continuation(s).
|
||||
*/
|
||||
function syncThenable(asyncFn) {
|
||||
function SyncThenable(asyncFn) {
|
||||
var continuations = [];
|
||||
var resolved = false;
|
||||
var resolvedWith = null;
|
||||
@@ -45,6 +47,12 @@ var LoopMochaUtils = (function(global, _) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to resolve an object of type SyncThenable.
|
||||
*
|
||||
* @param {*} result The result to return.
|
||||
* @return {SyncThenable} A resolved SyncThenable object.
|
||||
*/
|
||||
this.resolve = function(result) {
|
||||
resolved = true;
|
||||
resolvedWith = result;
|
||||
@@ -69,8 +77,8 @@ var LoopMochaUtils = (function(global, _) {
|
||||
asyncFn(this.resolve.bind(this), this.reject.bind(this));
|
||||
}
|
||||
|
||||
syncThenable.all = function(promises) {
|
||||
return new syncThenable(function(resolve) {
|
||||
SyncThenable.all = function(promises) {
|
||||
return new SyncThenable(function(resolve) {
|
||||
var results = [];
|
||||
|
||||
promises.forEach(function(promise) {
|
||||
@@ -83,15 +91,28 @@ var LoopMochaUtils = (function(global, _) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This simulates the equivalent of Promise.resolve() - calling the
|
||||
* resolve function on the raw object.
|
||||
*
|
||||
* @param {*} result The result to return.
|
||||
* @return {SyncThenable} A resolved SyncThenable object.
|
||||
*/
|
||||
SyncThenable.resolve = function(result) {
|
||||
return new SyncThenable(function(resolve) {
|
||||
resolve(result);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple wrapper around `sinon.sandbox.create()` to also stub the native Promise
|
||||
* object out with `syncThenable`.
|
||||
* object out with `SyncThenable`.
|
||||
*
|
||||
* @return {Sandbox} A Sinon.JS sandbox object.
|
||||
*/
|
||||
function createSandbox() {
|
||||
var sandbox = sinon.sandbox.create();
|
||||
sandbox.stub(global, "Promise", syncThenable);
|
||||
sandbox.stub(global, "Promise", SyncThenable);
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
@@ -201,7 +222,8 @@ var LoopMochaUtils = (function(global, _) {
|
||||
* to the listeners.
|
||||
*/
|
||||
function publish() {
|
||||
var args = Array.slice(arguments);
|
||||
// Convert to a proper array.
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var name = args.shift();
|
||||
gPushListenerCallbacks.forEach(function(cb) {
|
||||
cb({ data: [name, args] });
|
||||
|
||||
@@ -6,8 +6,6 @@ describe("loopapi-client", function() {
|
||||
|
||||
var expect = chai.expect;
|
||||
var sandbox, clock, replyTimeoutMs;
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
var TestUtils = React.addons.TestUtils;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
@@ -6,7 +6,7 @@ describe("loop.shared.models", function() {
|
||||
"use strict";
|
||||
|
||||
var expect = chai.expect;
|
||||
var l10n = navigator.mozL10n;
|
||||
var l10n = navigator.mozL10n || document.mozL10n;
|
||||
var sharedModels = loop.shared.models;
|
||||
var sandbox;
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ describe("loop.OTSdkDriver", function() {
|
||||
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
|
||||
|
||||
var sandbox, constants;
|
||||
var dispatcher, driver, requestStubs, publisher, sdk, session, sessionData, subscriber;
|
||||
var publisherConfig, fakeEvent;
|
||||
var dispatcher, driver, requestStubs, publisher, screenshare, sdk, session;
|
||||
var sessionData, subscriber, publisherConfig, fakeEvent;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = LoopMochaUtils.createSandbox();
|
||||
@@ -59,6 +59,8 @@ describe("loop.OTSdkDriver", function() {
|
||||
}
|
||||
}, Backbone.Events);
|
||||
|
||||
screenshare = publisher;
|
||||
|
||||
subscriber = _.extend({
|
||||
_: {
|
||||
getDataChannel: sinon.stub()
|
||||
@@ -141,11 +143,14 @@ describe("loop.OTSdkDriver", function() {
|
||||
});
|
||||
|
||||
describe("#setupStreamElements", function() {
|
||||
it("should call initPublisher", function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(publisher, "off");
|
||||
driver.setupStreamElements(new sharedActions.SetupStreamElements({
|
||||
publisherConfig: publisherConfig
|
||||
}));
|
||||
});
|
||||
|
||||
it("should call initPublisher", function() {
|
||||
var expectedConfig = _.extend({
|
||||
channels: {
|
||||
text: {}
|
||||
@@ -157,6 +162,45 @@ describe("loop.OTSdkDriver", function() {
|
||||
sinon.match.instanceOf(HTMLDivElement),
|
||||
expectedConfig);
|
||||
});
|
||||
|
||||
it("should not do anything if publisher completed successfully", function() {
|
||||
sdk.initPublisher.callArg(2);
|
||||
|
||||
sinon.assert.notCalled(publisher.off);
|
||||
sinon.assert.notCalled(dispatcher.dispatch);
|
||||
});
|
||||
|
||||
it("should clean up publisher if an error occurred", function() {
|
||||
sdk.initPublisher.callArgWith(2, { message: "FAKE" });
|
||||
|
||||
sinon.assert.calledOnce(publisher.off);
|
||||
sinon.assert.calledOnce(publisher.destroy);
|
||||
expect(driver.publisher).to.equal(undefined);
|
||||
expect(driver._mockPublisherEl).to.equal(undefined);
|
||||
});
|
||||
|
||||
it("should dispatch ConnectionFailure if an error occurred", function() {
|
||||
sdk.initPublisher.callArgWith(2, { message: "FAKE" });
|
||||
|
||||
sinon.assert.calledTwice(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.ConnectionFailure({
|
||||
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
||||
}));
|
||||
});
|
||||
|
||||
it("should notify metrics if an error occurred", function() {
|
||||
sdk.initPublisher.callArgWith(2, { code: 123, message: "FAKE" });
|
||||
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.ConnectionStatus({
|
||||
event: "sdk.exception.123.FAKE",
|
||||
state: "starting",
|
||||
connections: 0,
|
||||
sendStreams: 0,
|
||||
recvStreams: 0
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setMute", function() {
|
||||
@@ -190,28 +234,76 @@ describe("loop.OTSdkDriver", function() {
|
||||
});
|
||||
|
||||
describe("#startScreenShare", function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(driver, "_noteSharingState");
|
||||
});
|
||||
var options = {};
|
||||
|
||||
it("should initialize a publisher", function() {
|
||||
// We're testing with `videoSource` set to 'browser', not 'window', as it
|
||||
// has multiple options.
|
||||
var options = {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(screenshare, "off");
|
||||
sandbox.stub(driver, "_noteSharingState");
|
||||
options = {
|
||||
videoSource: "browser",
|
||||
constraints: {
|
||||
browserWindow: 42,
|
||||
scrollWithPage: true
|
||||
}
|
||||
};
|
||||
driver.startScreenShare(options);
|
||||
|
||||
driver.startScreenShare(options);
|
||||
});
|
||||
|
||||
it("should initialize a publisher", function() {
|
||||
sinon.assert.calledOnce(sdk.initPublisher);
|
||||
sinon.assert.calledWithMatch(sdk.initPublisher,
|
||||
sinon.match.instanceOf(HTMLDivElement), options);
|
||||
});
|
||||
|
||||
it("should log a telemetry action", function() {
|
||||
sinon.assert.calledWithExactly(driver._noteSharingState, "browser", true);
|
||||
});
|
||||
|
||||
it("should not do anything if publisher completed successfully", function() {
|
||||
sdk.initPublisher.callArg(2);
|
||||
|
||||
sinon.assert.notCalled(screenshare.off);
|
||||
sinon.assert.notCalled(dispatcher.dispatch);
|
||||
});
|
||||
|
||||
it("should clean up publisher if an error occurred", function() {
|
||||
sdk.initPublisher.callArgWith(2, { code: 123, message: "FAKE" });
|
||||
|
||||
sinon.assert.calledOnce(screenshare.off);
|
||||
sinon.assert.calledOnce(screenshare.destroy);
|
||||
expect(driver.screenshare).to.equal(undefined);
|
||||
expect(driver._mockScreenSharePreviewEl).to.equal(undefined);
|
||||
});
|
||||
|
||||
it("should dispatch ConnectionFailure if an error occurred", function() {
|
||||
sdk.initPublisher.callArgWith(2, { code: 123, message: "FAKE" });
|
||||
|
||||
sinon.assert.calledTwice(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.ScreenSharingState({
|
||||
state: SCREEN_SHARE_STATES.INACTIVE
|
||||
}));
|
||||
});
|
||||
|
||||
it("should notify metrics if an error occurred", function() {
|
||||
sdk.initPublisher.callArgWith(2, { code: 123, message: "FAKE" });
|
||||
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.ConnectionStatus({
|
||||
event: "sdk.exception.screen.123.FAKE",
|
||||
state: "starting",
|
||||
connections: 0,
|
||||
sendStreams: 0,
|
||||
recvStreams: 0
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Screenshare Access Denied", function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(screenshare, "off");
|
||||
sandbox.stub(driver, "_noteSharingState");
|
||||
var options = {
|
||||
videoSource: "browser",
|
||||
constraints: {
|
||||
@@ -220,8 +312,23 @@ describe("loop.OTSdkDriver", function() {
|
||||
}
|
||||
};
|
||||
driver.startScreenShare(options);
|
||||
sdk.initPublisher.callArg(2);
|
||||
driver.screenshare.trigger("accessDenied");
|
||||
});
|
||||
|
||||
sinon.assert.calledWithExactly(driver._noteSharingState, "browser", true);
|
||||
it("should clean up publisher", function() {
|
||||
sinon.assert.calledOnce(screenshare.off);
|
||||
sinon.assert.calledOnce(screenshare.destroy);
|
||||
expect(driver.screenshare).to.equal(undefined);
|
||||
expect(driver._mockScreenSharePreviewEl).to.equal(undefined);
|
||||
});
|
||||
|
||||
it("should dispatch ConnectionFailure", function() {
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.ScreenSharingState({
|
||||
state: SCREEN_SHARE_STATES.INACTIVE
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1678,69 +1785,6 @@ describe("loop.OTSdkDriver", function() {
|
||||
}));
|
||||
});
|
||||
|
||||
describe("Unable to publish (not GetUserMedia)", function() {
|
||||
it("should notify metrics", function() {
|
||||
sdk.trigger("exception", {
|
||||
code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
|
||||
message: "Fake",
|
||||
title: "Connect Failed"
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.ConnectionStatus({
|
||||
event: "sdk.exception." + OT.ExceptionCodes.UNABLE_TO_PUBLISH + ".Fake",
|
||||
state: "starting",
|
||||
connections: 0,
|
||||
sendStreams: 0,
|
||||
recvStreams: 0
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Unable to publish (GetUserMedia)", function() {
|
||||
it("should destroy the publisher", function() {
|
||||
sdk.trigger("exception", {
|
||||
code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
|
||||
message: "GetUserMedia"
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(publisher.destroy);
|
||||
});
|
||||
|
||||
// XXX We should remove this when we stop being unable to publish as a
|
||||
// workaround for knowing if the user has video as well as audio devices
|
||||
// installed (bug 1138851).
|
||||
it("should not notify metrics", function() {
|
||||
sdk.trigger("exception", {
|
||||
code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
|
||||
message: "GetUserMedia"
|
||||
});
|
||||
|
||||
sinon.assert.neverCalledWith(dispatcher.dispatch,
|
||||
new sharedActions.ConnectionStatus({
|
||||
event: "sdk.exception." + OT.ExceptionCodes.UNABLE_TO_PUBLISH,
|
||||
state: "starting",
|
||||
connections: 0,
|
||||
sendStreams: 0,
|
||||
recvStreams: 0
|
||||
}));
|
||||
});
|
||||
|
||||
it("should dispatch a ConnectionFailure action", function() {
|
||||
sdk.trigger("exception", {
|
||||
code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
|
||||
message: "GetUserMedia"
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.ConnectionFailure({
|
||||
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("ToS Failure", function() {
|
||||
it("should dispatch a ConnectionFailure action", function() {
|
||||
sdk.trigger("exception", {
|
||||
|
||||
@@ -10,7 +10,7 @@ class TestSharedUnits(BaseTestFrontendUnits):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSharedUnits, self).setUp()
|
||||
self.set_server_prefix(".")
|
||||
self.set_server_prefix("../../../../")
|
||||
|
||||
def test_units(self):
|
||||
self.check_page("index.html")
|
||||
self.check_page("chrome/content/shared/test/index.html")
|
||||
|
||||
@@ -86,6 +86,34 @@ describe("loop.store.TextChatStore", function() {
|
||||
}]);
|
||||
});
|
||||
|
||||
it("should add the context tile to the list", function() {
|
||||
store.receivedTextChatMessage({
|
||||
type: CHAT_MESSAGE_TYPES.SENT,
|
||||
contentType: CHAT_CONTENT_TYPES.CONTEXT_TILE,
|
||||
message: "fake",
|
||||
extraData: {
|
||||
roomToken: "fakeRoomToken",
|
||||
newRoomThumbnail: "favicon",
|
||||
newRoomURL: "https://www.fakeurl.com"
|
||||
},
|
||||
sentTimestamp: "2015-06-24T23:58:53.848Z",
|
||||
receivedTimestamp: "1970-01-01T00:00:00.000Z"
|
||||
});
|
||||
|
||||
expect(store.getStoreState("messageList")).eql([{
|
||||
type: CHAT_MESSAGE_TYPES.RECEIVED,
|
||||
contentType: CHAT_CONTENT_TYPES.CONTEXT_TILE,
|
||||
message: "fake",
|
||||
extraData: {
|
||||
roomToken: "fakeRoomToken",
|
||||
newRoomThumbnail: "favicon",
|
||||
newRoomURL: "https://www.fakeurl.com"
|
||||
},
|
||||
sentTimestamp: "2015-06-24T23:58:53.848Z",
|
||||
receivedTimestamp: "1970-01-01T00:00:00.000Z"
|
||||
}]);
|
||||
});
|
||||
|
||||
it("should not add messages for unknown content types", function() {
|
||||
store.receivedTextChatMessage({
|
||||
contentType: "invalid type",
|
||||
@@ -257,4 +285,53 @@ describe("loop.store.TextChatStore", function() {
|
||||
sinon.assert.notCalled(window.dispatchEvent);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#updateRoomContext", function() {
|
||||
beforeEach(function() {
|
||||
store.setStoreState({ messageList: [] });
|
||||
store.updateRoomContext(new sharedActions.UpdateRoomContext({
|
||||
newRoomDescription: "fake",
|
||||
newRoomThumbnail: "favicon",
|
||||
newRoomURL: "https://www.fakeurl.com",
|
||||
roomToken: "fakeRoomToken"
|
||||
}));
|
||||
});
|
||||
|
||||
it("should add the room context to the list", function() {
|
||||
expect(store.getStoreState("messageList")).eql([{
|
||||
type: CHAT_MESSAGE_TYPES.SENT,
|
||||
contentType: CHAT_CONTENT_TYPES.CONTEXT_TILE,
|
||||
message: "fake",
|
||||
extraData: {
|
||||
roomToken: "fakeRoomToken",
|
||||
newRoomThumbnail: "favicon",
|
||||
newRoomURL: "https://www.fakeurl.com"
|
||||
},
|
||||
sentTimestamp: "1970-01-01T00:00:00.000Z",
|
||||
receivedTimestamp: undefined
|
||||
}]);
|
||||
});
|
||||
|
||||
it("should not add the room context if the last tile has the same domain", function() {
|
||||
store.updateRoomContext(new sharedActions.UpdateRoomContext({
|
||||
newRoomDescription: "fake",
|
||||
newRoomThumbnail: "favicon",
|
||||
newRoomURL: "https://www.fakeurl.com/test/same_domain",
|
||||
roomToken: "fakeRoomToken"
|
||||
}));
|
||||
|
||||
expect(store.getStoreState("messageList").length).eql(1);
|
||||
});
|
||||
|
||||
it("should add the room context if the last tile has not the same domain", function() {
|
||||
store.updateRoomContext(new sharedActions.UpdateRoomContext({
|
||||
newRoomDescription: "fake",
|
||||
newRoomThumbnail: "favicon",
|
||||
newRoomURL: "https://www.myfakeurl.com",
|
||||
roomToken: "fakeRoomToken"
|
||||
}));
|
||||
|
||||
expect(store.getStoreState("messageList").length).eql(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,18 +6,17 @@ describe("loop.shared.views.TextChatView", function() {
|
||||
|
||||
var expect = chai.expect;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
var sharedViews = loop.shared.views;
|
||||
var TestUtils = React.addons.TestUtils;
|
||||
var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
|
||||
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
|
||||
var fixtures = document.querySelector("#fixtures");
|
||||
var mozL10n = navigator.mozL10n || document.mozL10n;
|
||||
|
||||
var dispatcher, fakeSdkDriver, sandbox, store, fakeClock;
|
||||
var dispatcher, fakeSdkDriver, originalLanguage, sandbox, store;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = LoopMochaUtils.createSandbox();
|
||||
fakeClock = sandbox.useFakeTimers();
|
||||
sandbox.useFakeTimers();
|
||||
|
||||
dispatcher = new loop.Dispatcher();
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
@@ -33,11 +32,23 @@ describe("loop.shared.views.TextChatView", function() {
|
||||
loop.store.StoreMixin.register({
|
||||
textChatStore: store
|
||||
});
|
||||
|
||||
originalLanguage = mozL10n.language;
|
||||
|
||||
mozL10n.language = {
|
||||
code: "en-US",
|
||||
direction: "rtl"
|
||||
};
|
||||
|
||||
sandbox.stub(mozL10n, "get", function(string) {
|
||||
return string;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
React.unmountComponentAtNode(fixtures);
|
||||
mozL10n.language = originalLanguage;
|
||||
});
|
||||
|
||||
describe("TextChatEntriesView", function() {
|
||||
@@ -411,10 +422,6 @@ describe("loop.shared.views.TextChatView", function() {
|
||||
// Fake server to catch all XHR requests.
|
||||
fakeServer = sinon.fakeServer.create();
|
||||
store.setStoreState({ textChatEnabled: true });
|
||||
|
||||
sandbox.stub(navigator.mozL10n, "get", function(string) {
|
||||
return string;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
|
||||
@@ -6,6 +6,7 @@ describe("loop.shared.utils", function() {
|
||||
"use strict";
|
||||
|
||||
var expect = chai.expect;
|
||||
var mozL10n = navigator.mozL10n || document.mozL10n;
|
||||
var sandbox;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
|
||||
@@ -333,7 +334,7 @@ describe("loop.shared.utils", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
// fake mozL10n
|
||||
sandbox.stub(navigator.mozL10n, "get", function(id) {
|
||||
sandbox.stub(mozL10n, "get", function(id) {
|
||||
switch (id) {
|
||||
case "share_email_subject7":
|
||||
return "subject";
|
||||
@@ -635,45 +636,4 @@ describe("loop.shared.utils", function() {
|
||||
expect(obj).to.eql({ prop1: "null", prop3: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe("#truncate", function() {
|
||||
describe("ltr support", function() {
|
||||
it("should default to 72 chars", function() {
|
||||
var output = sharedUtils.truncate(new Array(75).join());
|
||||
|
||||
expect(output.length).to.eql(73); // 72 + …
|
||||
});
|
||||
|
||||
it("should take a max size argument", function() {
|
||||
var output = sharedUtils.truncate(new Array(73).join(), 20);
|
||||
|
||||
expect(output.length).to.eql(21); // 20 + …
|
||||
});
|
||||
});
|
||||
|
||||
describe("rtl support", function() {
|
||||
var directionStub;
|
||||
|
||||
beforeEach(function() {
|
||||
// XXX should use sandbox
|
||||
// https://github.com/cjohansen/Sinon.JS/issues/781
|
||||
directionStub = sinon.stub(navigator.mozL10n.language, "direction", {
|
||||
get: function() {
|
||||
return "rtl";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
directionStub.restore();
|
||||
});
|
||||
|
||||
it("should support RTL", function() {
|
||||
var output = sharedUtils.truncate(new Array(73).join(), 20);
|
||||
|
||||
expect(output.length).to.eql(21); // 20 + …
|
||||
expect(output.substr(0, 1)).to.eql("…");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
371
browser/extensions/loop/chrome/content/shared/test/vendor/chai-as-promised.js
vendored
Normal file
371
browser/extensions/loop/chrome/content/shared/test/vendor/chai-as-promised.js
vendored
Normal file
@@ -0,0 +1,371 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Module systems magic dance.
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
|
||||
// NodeJS
|
||||
module.exports = chaiAsPromised;
|
||||
} else if (typeof define === "function" && define.amd) {
|
||||
// AMD
|
||||
define(function () {
|
||||
return chaiAsPromised;
|
||||
});
|
||||
} else {
|
||||
/*global self: false */
|
||||
|
||||
// Other environment (usually <script> tag): plug in to global chai instance directly.
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
// Expose as a property of the global object so that consumers can configure the `transferPromiseness` property.
|
||||
self.chaiAsPromised = chaiAsPromised;
|
||||
}
|
||||
|
||||
chaiAsPromised.transferPromiseness = function (assertion, promise) {
|
||||
assertion.then = promise.then.bind(promise);
|
||||
};
|
||||
|
||||
chaiAsPromised.transformAsserterArgs = function (values) {
|
||||
return values;
|
||||
};
|
||||
|
||||
function chaiAsPromised(chai, utils) {
|
||||
var Assertion = chai.Assertion;
|
||||
var assert = chai.assert;
|
||||
|
||||
function isJQueryPromise(thenable) {
|
||||
return typeof thenable.always === "function" &&
|
||||
typeof thenable.done === "function" &&
|
||||
typeof thenable.fail === "function" &&
|
||||
typeof thenable.pipe === "function" &&
|
||||
typeof thenable.progress === "function" &&
|
||||
typeof thenable.state === "function";
|
||||
}
|
||||
|
||||
function assertIsAboutPromise(assertion) {
|
||||
if (typeof assertion._obj.then !== "function") {
|
||||
throw new TypeError(utils.inspect(assertion._obj) + " is not a thenable.");
|
||||
}
|
||||
if (isJQueryPromise(assertion._obj)) {
|
||||
throw new TypeError("Chai as Promised is incompatible with jQuery's thenables, sorry! Please use a " +
|
||||
"Promises/A+ compatible library (see http://promisesaplus.com/).");
|
||||
}
|
||||
}
|
||||
|
||||
function method(name, asserter) {
|
||||
utils.addMethod(Assertion.prototype, name, function () {
|
||||
assertIsAboutPromise(this);
|
||||
return asserter.apply(this, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
function property(name, asserter) {
|
||||
utils.addProperty(Assertion.prototype, name, function () {
|
||||
assertIsAboutPromise(this);
|
||||
return asserter.apply(this, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
function doNotify(promise, done) {
|
||||
promise.then(function () { done(); }, done);
|
||||
}
|
||||
|
||||
// These are for clarity and to bypass Chai refusing to allow `undefined` as actual when used with `assert`.
|
||||
function assertIfNegated(assertion, message, extra) {
|
||||
assertion.assert(true, null, message, extra.expected, extra.actual);
|
||||
}
|
||||
|
||||
function assertIfNotNegated(assertion, message, extra) {
|
||||
assertion.assert(false, message, null, extra.expected, extra.actual);
|
||||
}
|
||||
|
||||
function getBasePromise(assertion) {
|
||||
// We need to chain subsequent asserters on top of ones in the chain already (consider
|
||||
// `eventually.have.property("foo").that.equals("bar")`), only running them after the existing ones pass.
|
||||
// So the first base-promise is `assertion._obj`, but after that we use the assertions themselves, i.e.
|
||||
// previously derived promises, to chain off of.
|
||||
return typeof assertion.then === "function" ? assertion : assertion._obj;
|
||||
}
|
||||
|
||||
// Grab these first, before we modify `Assertion.prototype`.
|
||||
|
||||
var propertyNames = Object.getOwnPropertyNames(Assertion.prototype);
|
||||
|
||||
var propertyDescs = {};
|
||||
propertyNames.forEach(function (name) {
|
||||
propertyDescs[name] = Object.getOwnPropertyDescriptor(Assertion.prototype, name);
|
||||
});
|
||||
|
||||
property("fulfilled", function () {
|
||||
var that = this;
|
||||
var derivedPromise = getBasePromise(that).then(
|
||||
function (value) {
|
||||
that._obj = value;
|
||||
assertIfNegated(that,
|
||||
"expected promise not to be fulfilled but it was fulfilled with #{act}",
|
||||
{ actual: value });
|
||||
return value;
|
||||
},
|
||||
function (reason) {
|
||||
assertIfNotNegated(that,
|
||||
"expected promise to be fulfilled but it was rejected with #{act}",
|
||||
{ actual: reason });
|
||||
}
|
||||
);
|
||||
|
||||
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
||||
});
|
||||
|
||||
property("rejected", function () {
|
||||
var that = this;
|
||||
var derivedPromise = getBasePromise(that).then(
|
||||
function (value) {
|
||||
that._obj = value;
|
||||
assertIfNotNegated(that,
|
||||
"expected promise to be rejected but it was fulfilled with #{act}",
|
||||
{ actual: value });
|
||||
return value;
|
||||
},
|
||||
function (reason) {
|
||||
assertIfNegated(that,
|
||||
"expected promise not to be rejected but it was rejected with #{act}",
|
||||
{ actual: reason });
|
||||
|
||||
// Return the reason, transforming this into a fulfillment, to allow further assertions, e.g.
|
||||
// `promise.should.be.rejected.and.eventually.equal("reason")`.
|
||||
return reason;
|
||||
}
|
||||
);
|
||||
|
||||
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
||||
});
|
||||
|
||||
method("rejectedWith", function (Constructor, message) {
|
||||
var desiredReason = null;
|
||||
var constructorName = null;
|
||||
|
||||
if (Constructor instanceof RegExp || typeof Constructor === "string") {
|
||||
message = Constructor;
|
||||
Constructor = null;
|
||||
} else if (Constructor && Constructor instanceof Error) {
|
||||
desiredReason = Constructor;
|
||||
Constructor = null;
|
||||
message = null;
|
||||
} else if (typeof Constructor === "function") {
|
||||
constructorName = (new Constructor()).name;
|
||||
} else {
|
||||
Constructor = null;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
var derivedPromise = getBasePromise(that).then(
|
||||
function (value) {
|
||||
var assertionMessage = null;
|
||||
var expected = null;
|
||||
|
||||
if (Constructor) {
|
||||
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
|
||||
"#{act}";
|
||||
expected = constructorName;
|
||||
} else if (message) {
|
||||
var verb = message instanceof RegExp ? "matching" : "including";
|
||||
assertionMessage = "expected promise to be rejected with an error " + verb + " #{exp} but it " +
|
||||
"was fulfilled with #{act}";
|
||||
expected = message;
|
||||
} else if (desiredReason) {
|
||||
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
|
||||
"#{act}";
|
||||
expected = desiredReason;
|
||||
}
|
||||
|
||||
that._obj = value;
|
||||
|
||||
assertIfNotNegated(that, assertionMessage, { expected: expected, actual: value });
|
||||
},
|
||||
function (reason) {
|
||||
if (Constructor) {
|
||||
that.assert(reason instanceof Constructor,
|
||||
"expected promise to be rejected with #{exp} but it was rejected with #{act}",
|
||||
"expected promise not to be rejected with #{exp} but it was rejected with #{act}",
|
||||
constructorName,
|
||||
reason);
|
||||
}
|
||||
|
||||
var reasonMessage = utils.type(reason) === "object" && "message" in reason ?
|
||||
reason.message :
|
||||
"" + reason;
|
||||
if (message && reasonMessage !== null && reasonMessage !== undefined) {
|
||||
if (message instanceof RegExp) {
|
||||
that.assert(message.test(reasonMessage),
|
||||
"expected promise to be rejected with an error matching #{exp} but got #{act}",
|
||||
"expected promise not to be rejected with an error matching #{exp}",
|
||||
message,
|
||||
reasonMessage);
|
||||
}
|
||||
if (typeof message === "string") {
|
||||
that.assert(reasonMessage.indexOf(message) !== -1,
|
||||
"expected promise to be rejected with an error including #{exp} but got #{act}",
|
||||
"expected promise not to be rejected with an error including #{exp}",
|
||||
message,
|
||||
reasonMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (desiredReason) {
|
||||
that.assert(reason === desiredReason,
|
||||
"expected promise to be rejected with #{exp} but it was rejected with #{act}",
|
||||
"expected promise not to be rejected with #{exp}",
|
||||
desiredReason,
|
||||
reason);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
||||
});
|
||||
|
||||
property("eventually", function () {
|
||||
utils.flag(this, "eventually", true);
|
||||
});
|
||||
|
||||
method("notify", function (done) {
|
||||
doNotify(getBasePromise(this), done);
|
||||
});
|
||||
|
||||
method("become", function (value) {
|
||||
return this.eventually.deep.equal(value);
|
||||
});
|
||||
|
||||
////////
|
||||
// `eventually`
|
||||
|
||||
// We need to be careful not to trigger any getters, thus `Object.getOwnPropertyDescriptor` usage.
|
||||
var methodNames = propertyNames.filter(function (name) {
|
||||
return name !== "assert" && typeof propertyDescs[name].value === "function";
|
||||
});
|
||||
|
||||
methodNames.forEach(function (methodName) {
|
||||
Assertion.overwriteMethod(methodName, function (originalMethod) {
|
||||
return function () {
|
||||
doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
var getterNames = propertyNames.filter(function (name) {
|
||||
return name !== "_obj" && typeof propertyDescs[name].get === "function";
|
||||
});
|
||||
|
||||
getterNames.forEach(function (getterName) {
|
||||
// Chainable methods are things like `an`, which can work both for `.should.be.an.instanceOf` and as
|
||||
// `should.be.an("object")`. We need to handle those specially.
|
||||
var isChainableMethod = Assertion.prototype.__methods.hasOwnProperty(getterName);
|
||||
|
||||
if (isChainableMethod) {
|
||||
Assertion.overwriteChainableMethod(
|
||||
getterName,
|
||||
function (originalMethod) {
|
||||
return function() {
|
||||
doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
||||
};
|
||||
},
|
||||
function (originalGetter) {
|
||||
return function() {
|
||||
doAsserterAsyncAndAddThen(originalGetter, this);
|
||||
};
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Assertion.overwriteProperty(getterName, function (originalGetter) {
|
||||
return function () {
|
||||
doAsserterAsyncAndAddThen(originalGetter, this);
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function doAsserterAsyncAndAddThen(asserter, assertion, args) {
|
||||
// Since we're intercepting all methods/properties, we need to just pass through if they don't want
|
||||
// `eventually`, or if we've already fulfilled the promise (see below).
|
||||
if (!utils.flag(assertion, "eventually")) {
|
||||
return asserter.apply(assertion, args);
|
||||
}
|
||||
|
||||
var derivedPromise = getBasePromise(assertion).then(function (value) {
|
||||
// Set up the environment for the asserter to actually run: `_obj` should be the fulfillment value, and
|
||||
// now that we have the value, we're no longer in "eventually" mode, so we won't run any of this code,
|
||||
// just the base Chai code that we get to via the short-circuit above.
|
||||
assertion._obj = value;
|
||||
utils.flag(assertion, "eventually", false);
|
||||
|
||||
return args ? chaiAsPromised.transformAsserterArgs(args) : args;
|
||||
}).then(function (args) {
|
||||
asserter.apply(assertion, args);
|
||||
|
||||
// Because asserters, for example `property`, can change the value of `_obj` (i.e. change the "object"
|
||||
// flag), we need to communicate this value change to subsequent chained asserters. Since we build a
|
||||
// promise chain paralleling the asserter chain, we can use it to communicate such changes.
|
||||
return assertion._obj;
|
||||
});
|
||||
|
||||
chaiAsPromised.transferPromiseness(assertion, derivedPromise);
|
||||
}
|
||||
|
||||
///////
|
||||
// Now use the `Assertion` framework to build an `assert` interface.
|
||||
var originalAssertMethods = Object.getOwnPropertyNames(assert).filter(function (propName) {
|
||||
return typeof assert[propName] === "function";
|
||||
});
|
||||
|
||||
assert.isFulfilled = function (promise, message) {
|
||||
return (new Assertion(promise, message)).to.be.fulfilled;
|
||||
};
|
||||
|
||||
assert.isRejected = function (promise, toTestAgainst, message) {
|
||||
if (typeof toTestAgainst === "string") {
|
||||
message = toTestAgainst;
|
||||
toTestAgainst = undefined;
|
||||
}
|
||||
|
||||
var assertion = (new Assertion(promise, message));
|
||||
return toTestAgainst !== undefined ? assertion.to.be.rejectedWith(toTestAgainst) : assertion.to.be.rejected;
|
||||
};
|
||||
|
||||
assert.becomes = function (promise, value, message) {
|
||||
return assert.eventually.deepEqual(promise, value, message);
|
||||
};
|
||||
|
||||
assert.doesNotBecome = function (promise, value, message) {
|
||||
return assert.eventually.notDeepEqual(promise, value, message);
|
||||
};
|
||||
|
||||
assert.eventually = {};
|
||||
originalAssertMethods.forEach(function (assertMethodName) {
|
||||
assert.eventually[assertMethodName] = function (promise) {
|
||||
var otherArgs = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
var customRejectionHandler;
|
||||
var message = arguments[assert[assertMethodName].length - 1];
|
||||
if (typeof message === "string") {
|
||||
customRejectionHandler = function (reason) {
|
||||
throw new chai.AssertionError(message + "\n\nOriginal reason: " + utils.inspect(reason));
|
||||
};
|
||||
}
|
||||
|
||||
var returnedPromise = promise.then(
|
||||
function (fulfillmentValue) {
|
||||
return assert[assertMethodName].apply(assert, [fulfillmentValue].concat(otherArgs));
|
||||
},
|
||||
customRejectionHandler
|
||||
);
|
||||
|
||||
returnedPromise.notify = function (done) {
|
||||
doNotify(returnedPromise, done);
|
||||
};
|
||||
|
||||
return returnedPromise;
|
||||
};
|
||||
});
|
||||
}
|
||||
}());
|
||||
5352
browser/extensions/loop/chrome/content/shared/test/vendor/chai.js
vendored
Normal file
5352
browser/extensions/loop/chrome/content/shared/test/vendor/chai.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
270
browser/extensions/loop/chrome/content/shared/test/vendor/mocha.css
vendored
Normal file
270
browser/extensions/loop/chrome/content/shared/test/vendor/mocha.css
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
@charset "utf-8";
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
}
|
||||
|
||||
#mocha {
|
||||
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
margin: 60px 50px;
|
||||
}
|
||||
|
||||
#mocha ul,
|
||||
#mocha li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#mocha ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#mocha h1,
|
||||
#mocha h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#mocha h1 {
|
||||
margin-top: 15px;
|
||||
font-size: 1em;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
#mocha h1 a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#mocha h1 a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#mocha .suite .suite h1 {
|
||||
margin-top: 0;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
#mocha .hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#mocha .suite {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
#mocha .test {
|
||||
margin-left: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#mocha .test.pending:hover h2::after {
|
||||
content: '(pending)';
|
||||
font-family: arial, sans-serif;
|
||||
}
|
||||
|
||||
#mocha .test.pass.medium .duration {
|
||||
background: #c09853;
|
||||
}
|
||||
|
||||
#mocha .test.pass.slow .duration {
|
||||
background: #b94a48;
|
||||
}
|
||||
|
||||
#mocha .test.pass::before {
|
||||
content: '✓';
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
color: #00d6b2;
|
||||
}
|
||||
|
||||
#mocha .test.pass .duration {
|
||||
font-size: 9px;
|
||||
margin-left: 5px;
|
||||
padding: 2px 5px;
|
||||
color: #fff;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-ms-border-radius: 5px;
|
||||
-o-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#mocha .test.pass.fast .duration {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha .test.pending {
|
||||
color: #0b97c4;
|
||||
}
|
||||
|
||||
#mocha .test.pending::before {
|
||||
content: '◦';
|
||||
color: #0b97c4;
|
||||
}
|
||||
|
||||
#mocha .test.fail {
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test.fail pre {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#mocha .test.fail::before {
|
||||
content: '✖';
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test pre.error {
|
||||
color: #c00;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* (1): approximate for browsers not supporting calc
|
||||
* (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
|
||||
* ^^ seriously
|
||||
*/
|
||||
#mocha .test pre {
|
||||
display: block;
|
||||
float: left;
|
||||
clear: left;
|
||||
font: 12px/1.5 monaco, monospace;
|
||||
margin: 5px;
|
||||
padding: 15px;
|
||||
border: 1px solid #eee;
|
||||
max-width: 85%; /*(1)*/
|
||||
max-width: calc(100% - 42px); /*(2)*/
|
||||
word-wrap: break-word;
|
||||
border-bottom-color: #ddd;
|
||||
-webkit-border-radius: 3px;
|
||||
-webkit-box-shadow: 0 1px 3px #eee;
|
||||
-moz-border-radius: 3px;
|
||||
-moz-box-shadow: 0 1px 3px #eee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#mocha .test h2 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#mocha .test a.replay {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 0;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
background: #eee;
|
||||
font-size: 15px;
|
||||
-moz-border-radius: 15px;
|
||||
border-radius: 15px;
|
||||
-webkit-transition: opacity 200ms;
|
||||
-moz-transition: opacity 200ms;
|
||||
transition: opacity 200ms;
|
||||
opacity: 0.3;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#mocha .test:hover a.replay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#mocha-report.pass .test.fail {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha-report.fail .test.pass {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha-report.pending .test.pass,
|
||||
#mocha-report.pending .test.fail {
|
||||
display: none;
|
||||
}
|
||||
#mocha-report.pending .test.pass.pending {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#mocha-error {
|
||||
color: #c00;
|
||||
font-size: 1.5em;
|
||||
font-weight: 100;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
#mocha-stats {
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
right: 10px;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
color: #888;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#mocha-stats .progress {
|
||||
float: right;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#mocha-stats em {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#mocha-stats a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#mocha-stats a:hover {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#mocha-stats li {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
list-style: none;
|
||||
padding-top: 11px;
|
||||
}
|
||||
|
||||
#mocha-stats canvas {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#mocha code .comment { color: #ddd; }
|
||||
#mocha code .init { color: #2f6fad; }
|
||||
#mocha code .string { color: #5890ad; }
|
||||
#mocha code .keyword { color: #8a6343; }
|
||||
#mocha code .number { color: #2f6fad; }
|
||||
|
||||
@media screen and (max-device-width: 480px) {
|
||||
#mocha {
|
||||
margin: 60px 0px;
|
||||
}
|
||||
|
||||
#mocha #stats {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
6564
browser/extensions/loop/chrome/content/shared/test/vendor/mocha.js
vendored
Normal file
6564
browser/extensions/loop/chrome/content/shared/test/vendor/mocha.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6194
browser/extensions/loop/chrome/content/shared/test/vendor/sinon.js
vendored
Normal file
6194
browser/extensions/loop/chrome/content/shared/test/vendor/sinon.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,12 +11,11 @@ describe("loop.shared.views", function() {
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedModels = loop.shared.models;
|
||||
var sharedViews = loop.shared.views;
|
||||
var getReactElementByClass = TestUtils.findRenderedDOMComponentWithClass;
|
||||
var sandbox, fakeAudioXHR, dispatcher, OS, OSVersion;
|
||||
var sandbox, clock, dispatcher, OS, OSVersion;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = LoopMochaUtils.createSandbox();
|
||||
sandbox.useFakeTimers(); // exposes sandbox.clock as a fake timer
|
||||
clock = sandbox.useFakeTimers(); // exposes sandbox.clock as a fake timer
|
||||
sandbox.stub(l10n, "get", function(x) {
|
||||
return "translated:" + x;
|
||||
});
|
||||
@@ -28,20 +27,6 @@ describe("loop.shared.views", function() {
|
||||
dispatcher = new loop.Dispatcher();
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
|
||||
fakeAudioXHR = {
|
||||
open: sinon.spy(),
|
||||
send: function() {},
|
||||
abort: function() {},
|
||||
getResponseHeader: function(header) {
|
||||
if (header === "Content-Type") {
|
||||
return "audio/ogg";
|
||||
}
|
||||
},
|
||||
responseType: null,
|
||||
response: new ArrayBuffer(10),
|
||||
onload: null
|
||||
};
|
||||
|
||||
OS = "mac";
|
||||
OSVersion = { major: 10, minor: 10 };
|
||||
sandbox.stub(loop.shared.utils, "getOS", function() {
|
||||
@@ -108,177 +93,12 @@ describe("loop.shared.views", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("SettingsControlButton", function() {
|
||||
var requestStubs;
|
||||
var support_url = "https://support.com";
|
||||
|
||||
beforeEach(function() {
|
||||
LoopMochaUtils.stubLoopRequest(requestStubs = {
|
||||
OpenURL: sandbox.stub(),
|
||||
SetLoopPref: sandbox.stub(),
|
||||
GetLoopPref: function(prefName) {
|
||||
switch (prefName) {
|
||||
case "support_url":
|
||||
return support_url;
|
||||
default:
|
||||
return prefName;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sandbox.stub(console, "error");
|
||||
});
|
||||
|
||||
function mountTestComponent(props) {
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(sharedViews.SettingsControlButton, props));
|
||||
}
|
||||
|
||||
it("should render a visible button", function() {
|
||||
var settingsMenuItems = [{ id: "help" }];
|
||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
||||
|
||||
var node = comp.getDOMNode().querySelector(".btn-settings");
|
||||
expect(node.classList.contains("hide")).eql(false);
|
||||
});
|
||||
|
||||
it("should not render anything", function() {
|
||||
var comp = mountTestComponent();
|
||||
expect(comp.getDOMNode()).to.eql(null);
|
||||
});
|
||||
|
||||
it("should not show an undefined menu option", function() {
|
||||
var settingsMenuItems = [
|
||||
{ id: "not Defined" },
|
||||
{ id: "help" }
|
||||
];
|
||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
||||
var menuItems = comp.getDOMNode().querySelectorAll(".settings-menu > li");
|
||||
expect(menuItems).to.have.length.of(1);
|
||||
});
|
||||
|
||||
it("should log an error for an undefined menu option", function() {
|
||||
var settingsMenuItems = [
|
||||
{ id: "not Defined" },
|
||||
{ id: "help" }
|
||||
];
|
||||
|
||||
mountTestComponent({ menuItems: settingsMenuItems });
|
||||
|
||||
sinon.assert.calledOnce(console.error);
|
||||
sinon.assert.calledWithMatch(console.error, "Invalid");
|
||||
});
|
||||
|
||||
it("should not render anything if not exists any valid item to show", function() {
|
||||
var settingsMenuItems = [
|
||||
{ id: "not Defined" },
|
||||
{ id: "another wrong menu item" }
|
||||
];
|
||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
||||
expect(comp.getDOMNode()).to.eql(null);
|
||||
});
|
||||
|
||||
it("should show the settings dropdown on click", function() {
|
||||
var settingsMenuItems = [{ id: "help" }];
|
||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
||||
|
||||
expect(comp.state.showMenu).eql(false);
|
||||
TestUtils.Simulate.click(comp.getDOMNode().querySelector(".btn-settings"));
|
||||
|
||||
expect(comp.state.showMenu).eql(true);
|
||||
});
|
||||
|
||||
it("should have a `menu-below` class on the dropdown when the prop is set.", function() {
|
||||
var settingsMenuItems = [
|
||||
{ id: "help" }
|
||||
];
|
||||
var comp = mountTestComponent({
|
||||
menuBelow: true,
|
||||
menuItems: settingsMenuItems
|
||||
});
|
||||
var menuItems = comp.getDOMNode().querySelector(".settings-menu");
|
||||
|
||||
expect(menuItems.classList.contains("menu-below")).eql(true);
|
||||
});
|
||||
|
||||
it("should not have a `menu-below` class on the dropdown when the prop is not set.", function() {
|
||||
var settingsMenuItems = [
|
||||
{ id: "help" }
|
||||
];
|
||||
var comp = mountTestComponent({
|
||||
menuItems: settingsMenuItems
|
||||
});
|
||||
var menuItems = comp.getDOMNode().querySelector(".settings-menu");
|
||||
|
||||
expect(menuItems.classList.contains("menu-below")).eql(false);
|
||||
});
|
||||
|
||||
it("should show edit Context on menu when the option is enabled", function() {
|
||||
var settingsMenuItems = [
|
||||
{
|
||||
id: "edit",
|
||||
enabled: true,
|
||||
visible: true,
|
||||
onClick: function() {}
|
||||
}
|
||||
];
|
||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
||||
|
||||
var node = comp.getDOMNode().querySelector(".settings-menu > li.entry-settings-edit");
|
||||
expect(node.classList.contains("hide")).eql(false);
|
||||
});
|
||||
|
||||
it("should hide edit Context on menu when the option is not visible", function() {
|
||||
var settingsMenuItems = [
|
||||
{
|
||||
id: "edit",
|
||||
enabled: false,
|
||||
visible: false,
|
||||
onClick: function() {}
|
||||
}
|
||||
];
|
||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
||||
|
||||
var node = comp.getDOMNode().querySelector(".settings-menu > li.entry-settings-edit");
|
||||
expect(node.classList.contains("hide")).eql(true);
|
||||
});
|
||||
|
||||
it("should call onClick method when the edit context menu item is clicked", function() {
|
||||
var onClickCalled = false;
|
||||
var settingsMenuItems = [
|
||||
{
|
||||
id: "edit",
|
||||
enabled: true,
|
||||
visible: true,
|
||||
onClick: sandbox.stub()
|
||||
}
|
||||
];
|
||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
||||
|
||||
TestUtils.Simulate.click(comp.getDOMNode().querySelector(".settings-menu > li.entry-settings-edit"));
|
||||
sinon.assert.calledOnce(settingsMenuItems[0].onClick);
|
||||
});
|
||||
|
||||
it("should open a tab to the support url when the support menu item is clicked", function() {
|
||||
var settingsMenuItems = [
|
||||
{ id: "help" }
|
||||
];
|
||||
var comp = mountTestComponent({ menuItems: settingsMenuItems });
|
||||
|
||||
TestUtils.Simulate.click(comp.getDOMNode().querySelector(".settings-menu > li:last-child"));
|
||||
|
||||
sinon.assert.calledOnce(requestStubs.OpenURL);
|
||||
sinon.assert.calledWithExactly(requestStubs.OpenURL, support_url);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ConversationToolbar", function() {
|
||||
var clock, hangup, publishStream;
|
||||
var hangup, publishStream;
|
||||
|
||||
function mountTestComponent(props) {
|
||||
props = _.extend({
|
||||
dispatcher: dispatcher,
|
||||
show: true
|
||||
dispatcher: dispatcher
|
||||
}, props || {});
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(sharedViews.ConversationToolbar, props));
|
||||
@@ -287,21 +107,6 @@ describe("loop.shared.views", function() {
|
||||
beforeEach(function() {
|
||||
hangup = sandbox.stub();
|
||||
publishStream = sandbox.stub();
|
||||
clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it("should not render the component when 'show' is false", function() {
|
||||
var comp = mountTestComponent({
|
||||
hangup: hangup,
|
||||
publishStream: publishStream,
|
||||
show: false
|
||||
});
|
||||
|
||||
expect(comp.getDOMNode()).to.eql(null);
|
||||
});
|
||||
|
||||
it("should start no idle", function() {
|
||||
@@ -743,7 +548,7 @@ describe("loop.shared.views", function() {
|
||||
expect(element.className).eql("no-video");
|
||||
});
|
||||
|
||||
it("should display a video element if a source object is supplied", function() {
|
||||
it("should display a video element if a source object is supplied", function(done) {
|
||||
view = mountTestComponent({
|
||||
displayAvatar: false,
|
||||
mediaType: "local",
|
||||
@@ -758,7 +563,18 @@ describe("loop.shared.views", function() {
|
||||
|
||||
expect(element).not.eql(null);
|
||||
expect(element.className).eql("local-video");
|
||||
|
||||
// Google Chrome doesn't seem to set "muted" on the element at creation
|
||||
// time, so we need to do the test as async.
|
||||
clock.restore();
|
||||
setTimeout(function() {
|
||||
try {
|
||||
expect(element.muted).eql(true);
|
||||
done();
|
||||
} catch (ex) {
|
||||
done(ex);
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
|
||||
// We test this function by itself, as otherwise we'd be into creating fake
|
||||
|
||||
1873
browser/extensions/loop/chrome/content/shared/vendor/backbone.js
vendored
Normal file
1873
browser/extensions/loop/chrome/content/shared/vendor/backbone.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
48
browser/extensions/loop/chrome/content/shared/vendor/classnames.js
vendored
Normal file
48
browser/extensions/loop/chrome/content/shared/vendor/classnames.js
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
/*!
|
||||
Copyright (c) 2015 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
/* global define */
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var hasOwn = {}.hasOwnProperty;
|
||||
|
||||
function classNames () {
|
||||
var classes = '';
|
||||
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var arg = arguments[i];
|
||||
if (!arg) continue;
|
||||
|
||||
var argType = typeof arg;
|
||||
|
||||
if (argType === 'string' || argType === 'number') {
|
||||
classes += ' ' + arg;
|
||||
} else if (Array.isArray(arg)) {
|
||||
classes += ' ' + classNames.apply(null, arg);
|
||||
} else if (argType === 'object') {
|
||||
for (var key in arg) {
|
||||
if (hasOwn.call(arg, key) && arg[key]) {
|
||||
classes += ' ' + key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return classes.substr(1);
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = classNames;
|
||||
} else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
|
||||
// register as 'classnames', consistent with npm package name
|
||||
define('classnames', function () {
|
||||
return classNames;
|
||||
});
|
||||
} else {
|
||||
window.classNames = classNames;
|
||||
}
|
||||
}());
|
||||
12235
browser/extensions/loop/chrome/content/shared/vendor/lodash.js
vendored
Normal file
12235
browser/extensions/loop/chrome/content/shared/vendor/lodash.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
18
browser/extensions/loop/chrome/content/shared/vendor/react-prod.js
vendored
Normal file
18
browser/extensions/loop/chrome/content/shared/vendor/react-prod.js
vendored
Normal file
File diff suppressed because one or more lines are too long
21642
browser/extensions/loop/chrome/content/shared/vendor/react.js
vendored
Normal file
21642
browser/extensions/loop/chrome/content/shared/vendor/react.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
246
browser/extensions/loop/chrome/locale/en-US/loop.properties
Normal file
246
browser/extensions/loop/chrome/locale/en-US/loop.properties
Normal file
@@ -0,0 +1,246 @@
|
||||
# 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/.
|
||||
|
||||
# Panel Strings
|
||||
|
||||
clientSuperShortname=Hello
|
||||
|
||||
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
|
||||
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
|
||||
## use "..." if \u2026 doesn't suit traditions in your locale.
|
||||
loopMenuItem_label=Start a conversation…
|
||||
loopMenuItem_accesskey=t
|
||||
|
||||
## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
|
||||
## These are displayed together at the top of the panel when a user is needed to
|
||||
## sign-in again. The emphesis is on the first line to get the user to sign-in again,
|
||||
## and this is displayed in slightly larger font. Please arrange as necessary for
|
||||
## your locale.
|
||||
## {{clientShortname2}} will be replaced by the brand name for either string.
|
||||
sign_in_again_title_line_one=Please sign in again
|
||||
sign_in_again_title_line_two2=to continue using {{clientShortname2}}
|
||||
sign_in_again_button=Sign In
|
||||
## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
|
||||
## will be replaced by the super short brandname.
|
||||
sign_in_again_use_as_guest_button2=Use {{clientSuperShortname}} as a Guest
|
||||
|
||||
panel_browse_with_friend_button=Browse this page with a friend
|
||||
panel_stop_sharing_tabs_button=Stop sharing your tabs
|
||||
|
||||
## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
|
||||
## user to create his or her first conversation.
|
||||
first_time_experience_subheading2=Click the Hello button to browse Web pages with a friend.
|
||||
|
||||
## LOCALIZATION_NOTE(first_time_experience_content): Message describing
|
||||
## ways to use Hello project.
|
||||
first_time_experience_content=Use it to plan together, work together, laugh together.
|
||||
first_time_experience_button_label2=See how it works
|
||||
|
||||
invite_header_text_bold=Invite someone to browse this page with you!
|
||||
invite_header_text3=It takes two to use Firefox Hello, so send a friend a link to browse the Web with you!
|
||||
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
|
||||
## invite_email_link_button, invite_facebook_button2): These labels appear under
|
||||
## an iconic button for the invite view.
|
||||
invite_copy_link_button=Copy Link
|
||||
invite_copied_link_button=Copied!
|
||||
invite_email_link_button=Email Link
|
||||
invite_facebook_button3=Facebook
|
||||
invite_your_link=Your link:
|
||||
|
||||
# Status text
|
||||
display_name_guest=Guest
|
||||
|
||||
# Error bars
|
||||
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
|
||||
## These may be displayed at the top of the panel.
|
||||
session_expired_error_description=Session expired. All URLs you have previously created and shared will no longer work.
|
||||
could_not_authenticate=Could Not Authenticate
|
||||
password_changed_question=Did you change your password?
|
||||
try_again_later=Please try again later
|
||||
could_not_connect=Could Not Connect To The Server
|
||||
check_internet_connection=Please check your internet connection
|
||||
login_expired=Your Login Has Expired
|
||||
service_not_available=Service Unavailable At This Time
|
||||
problem_accessing_account=There Was A Problem Accessing Your Account
|
||||
|
||||
## LOCALIZATION NOTE(retry_button): Displayed when there is an error to retry
|
||||
## the appropriate action.
|
||||
retry_button=Retry
|
||||
|
||||
share_email_subject7=Your invitation to browse the Web together
|
||||
## LOCALIZATION NOTE (share_email_body7): In this item, don't translate the
|
||||
## part between {{..}} and leave the \n\n part alone
|
||||
share_email_body7=A friend is waiting for you on Firefox Hello. Click the link to connect and browse the Web together: {{callUrl}}
|
||||
## LOCALIZATION NOTE (share_email_body_context3): In this item, don't translate
|
||||
## the part between {{..}} and leave the \n\n part alone.
|
||||
share_email_body_context3=A friend is waiting for you on Firefox Hello. Click the link to connect and browse {{title}} together: {{callUrl}}
|
||||
## LOCALIZATION NOTE (share_email_footer2): Common footer content for both email types
|
||||
share_email_footer2=\n\n____________\nFirefox Hello lets you browse the Web with your friends. Use it when you want to get things done: plan together, work together, laugh together. Learn more at http://www.firefox.com/hello
|
||||
## LOCALIZATION NOTE (share_tweeet): In this item, don't translate the part
|
||||
## between {{..}}. Please keep the text below 117 characters to make sure it fits
|
||||
## in a tweet.
|
||||
share_tweet=Join me for a video conversation on {{clientShortname2}}!
|
||||
|
||||
share_add_service_button=Add a Service
|
||||
|
||||
## LOCALIZATION NOTE (copy_link_menuitem, email_link_menuitem, delete_conversation_menuitem):
|
||||
## These menu items are displayed from a panel's context menu for a conversation.
|
||||
copy_link_menuitem=Copy Link
|
||||
email_link_menuitem=Email Link
|
||||
delete_conversation_menuitem2=Delete
|
||||
|
||||
panel_footer_signin_or_signup_link=Sign In or Sign Up
|
||||
|
||||
settings_menu_item_account=Account
|
||||
settings_menu_item_settings=Settings
|
||||
settings_menu_item_signout=Sign Out
|
||||
settings_menu_item_signin=Sign In
|
||||
settings_menu_item_turnnotificationson=Turn Notifications On
|
||||
settings_menu_item_turnnotificationsoff=Turn Notifications Off
|
||||
settings_menu_item_feedback=Submit Feedback
|
||||
settings_menu_button_tooltip=Settings
|
||||
|
||||
|
||||
# Conversation Window Strings
|
||||
|
||||
initiate_call_button_label2=Ready to start your conversation?
|
||||
incoming_call_title2=Conversation Request
|
||||
incoming_call_block_button=Block
|
||||
hangup_button_title=Hang up
|
||||
hangup_button_caption2=Exit
|
||||
|
||||
|
||||
## LOCALIZATION NOTE (call_with_contact_title): The title displayed
|
||||
## when calling a contact. Don't translate the part between {{..}} because
|
||||
## this will be replaced by the contact's name.
|
||||
call_with_contact_title=Conversation with {{contactName}}
|
||||
|
||||
# Outgoing conversation
|
||||
|
||||
outgoing_call_title=Start conversation?
|
||||
initiate_audio_video_call_button2=Start
|
||||
initiate_audio_video_call_tooltip2=Start a video conversation
|
||||
initiate_audio_call_button2=Voice conversation
|
||||
|
||||
peer_ended_conversation2=The person you were calling has ended the conversation.
|
||||
restart_call=Rejoin
|
||||
|
||||
## LOCALIZATION NOTE (contact_offline_title): Title which is displayed when the
|
||||
## contact is offline.
|
||||
contact_offline_title=This person is not online
|
||||
## LOCALIZATION NOTE (call_timeout_notification_text): Title which is displayed
|
||||
## when the call didn't go through.
|
||||
call_timeout_notification_text=Your call did not go through.
|
||||
|
||||
## LOCALIZATION NOTE (cancel_button):
|
||||
## This button is displayed when a call has failed.
|
||||
cancel_button=Cancel
|
||||
rejoin_button=Rejoin Conversation
|
||||
|
||||
cannot_start_call_session_not_ready=Can't start call, session is not ready.
|
||||
network_disconnected=The network connection terminated abruptly.
|
||||
connection_error_see_console_notification=Call failed; see console for details.
|
||||
no_media_failure_message=No camera or microphone found.
|
||||
ice_failure_message=Connection failed. Your firewall may be blocking calls.
|
||||
|
||||
## LOCALIZATION NOTE (legal_text_and_links3): In this item, don't translate the
|
||||
## parts between {{..}} because these will be replaced with links with the labels
|
||||
## from legal_text_tos and legal_text_privacy. clientShortname will be replaced
|
||||
## by the brand name.
|
||||
legal_text_and_links3=By using {{clientShortname}} you agree to the {{terms_of_use}} and {{privacy_notice}}.
|
||||
legal_text_tos=Terms of Use
|
||||
legal_text_privacy=Privacy Notice
|
||||
|
||||
## LOCALIZATION NOTE (powered_by_beforeLogo, powered_by_afterLogo):
|
||||
## These 2 strings are displayed before and after a 'Telefonica'
|
||||
## logo.
|
||||
powered_by_beforeLogo=Powered by
|
||||
powered_by_afterLogo=
|
||||
|
||||
## LOCALIZATION_NOTE (feedback_rejoin_button): Displayed on the feedback form after
|
||||
## a signed-in to signed-in user call.
|
||||
feedback_rejoin_button=Rejoin
|
||||
## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
|
||||
## an abusive user.
|
||||
feedback_report_user_button=Report User
|
||||
feedback_window_heading=How was your conversation?
|
||||
feedback_request_button=Leave Feedback
|
||||
|
||||
tour_label=Tour
|
||||
|
||||
rooms_list_recently_browsed2=Recently browsed
|
||||
rooms_list_currently_browsing2=Currently browsing
|
||||
rooms_signout_alert=Open conversations will be closed
|
||||
room_name_untitled_page=Untitled Page
|
||||
|
||||
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
|
||||
door_hanger_return=See you later! You can return to this shared session at any time through the Hello panel.
|
||||
door_hanger_prompt_name=Would you like to give it a name that's easier to remember? Current name:
|
||||
door_hanger_button=OK
|
||||
|
||||
# Infobar strings
|
||||
|
||||
infobar_screenshare_browser_message2=You are sharing your tabs. Any tab you click on can be seen by your friends
|
||||
infobar_screenshare_paused_browser_message=Tab sharing is paused
|
||||
infobar_button_gotit_label=Got it!
|
||||
infobar_button_gotit_accesskey=G
|
||||
infobar_button_pause_label=Pause
|
||||
infobar_button_pause_accesskey=P
|
||||
infobar_button_restart_label=Restart
|
||||
infobar_button_restart_accesskey=e
|
||||
infobar_button_resume_label=Resume
|
||||
infobar_button_resume_accesskey=R
|
||||
infobar_button_stop_label=Stop
|
||||
infobar_button_stop_accesskey=S
|
||||
infobar_menuitem_dontshowagain_label=Don't show this again
|
||||
infobar_menuitem_dontshowagain_accesskey=D
|
||||
|
||||
# E10s not supported strings
|
||||
|
||||
e10s_not_supported_button_label=Launch New Window
|
||||
e10s_not_supported_subheading={{brandShortname}} doesn't work in a multi-process window.
|
||||
# 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/.
|
||||
|
||||
## LOCALIZATION NOTE: In this file, don't translate the part between {{..}}
|
||||
|
||||
# Text chat strings
|
||||
chat_textbox_placeholder=Type here…
|
||||
|
||||
## LOCALIZATION NOTE(clientShortname2): This should not be localized and
|
||||
## should remain "Firefox Hello" for all locales.
|
||||
clientShortname2=Firefox Hello
|
||||
|
||||
conversation_has_ended=Your conversation has ended.
|
||||
generic_failure_message=We're having technical difficulties…
|
||||
|
||||
generic_failure_no_reason2=Would you like to try again?
|
||||
|
||||
help_label=Help
|
||||
|
||||
mute_local_audio_button_title=Mute your audio
|
||||
unmute_local_audio_button_title=Unmute your audio
|
||||
mute_local_video_button_title2=Disable video
|
||||
unmute_local_video_button_title2=Enable video
|
||||
|
||||
## LOCALIZATION NOTE (retry_call_button):
|
||||
## This button is displayed when a call has failed.
|
||||
retry_call_button=Retry
|
||||
|
||||
rooms_leave_button_label=Leave
|
||||
|
||||
rooms_panel_title=Choose a conversation or start a new one
|
||||
|
||||
rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
|
||||
rooms_room_full_call_to_action_nonFx_label=Download {{brandShortname}} to start your own
|
||||
rooms_room_full_label=There are already two people in this conversation.
|
||||
rooms_room_join_label=Join the conversation
|
||||
rooms_room_joined_label=Someone has joined the conversation!
|
||||
|
||||
self_view_hidden_message=Self-view hidden but still being sent; resize window to show
|
||||
|
||||
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
|
||||
## as this will be replaced by clientShortname2.
|
||||
tos_failure_message={{clientShortname}} is not available in your country.
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"extends": "../../.eslintrc-gecko",
|
||||
"globals": {
|
||||
// General test items.
|
||||
"add_task": false,
|
||||
|
||||
@@ -3,7 +3,7 @@ support-files =
|
||||
head.js
|
||||
loop_fxa.sjs
|
||||
test_loopLinkClicker_channel.html
|
||||
../../../../base/content/test/general/browser_fxa_oauth_with_keys.html
|
||||
../../../../../base/content/test/general/browser_fxa_oauth_with_keys.html
|
||||
|
||||
[browser_fxa_login.js]
|
||||
[browser_loop_fxa_server.js]
|
||||
|
||||
@@ -12,7 +12,7 @@ var { WebChannel } = Cu.import("resource://gre/modules/WebChannel.jsm", {});
|
||||
var { Chat } = Cu.import("resource:///modules/Chat.jsm", {});
|
||||
|
||||
const TEST_URI =
|
||||
"example.com/browser/browser/extensions/loop/test/mochitest/test_loopLinkClicker_channel.html";
|
||||
"example.com/browser/browser/extensions/loop/chrome/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);
|
||||
|
||||
@@ -54,7 +54,7 @@ var gBadBackChannel;
|
||||
// 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, channel, hash) {
|
||||
let waitForChannelPromise = new Promise((resolve, reject) => {
|
||||
let waitForChannelPromise = new Promise((resolve) => {
|
||||
if (channel.receivedData) {
|
||||
let data = channel.receivedData;
|
||||
channel.receivedData = null;
|
||||
|
||||
@@ -52,7 +52,7 @@ add_task(function* setup() {
|
||||
}
|
||||
});
|
||||
registerCleanupFunction(function* () {
|
||||
info("cleanup time");
|
||||
info("cleanup specific to setup test");
|
||||
yield promiseDeletedOAuthParams(BASE_URL);
|
||||
Services.prefs.clearUserPref("loop.gettingStarted.latestFTUVersion");
|
||||
MozLoopServiceInternal.mocks.pushHandler = undefined;
|
||||
@@ -218,7 +218,7 @@ add_task(function* registrationWithInvalidState() {
|
||||
Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
|
||||
|
||||
let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
|
||||
yield tokenPromise.then(body => {
|
||||
yield tokenPromise.then(() => {
|
||||
ok(false, "Promise should have rejected");
|
||||
},
|
||||
error => {
|
||||
@@ -241,7 +241,7 @@ add_task(function* registrationWith401() {
|
||||
yield promiseOAuthParamsSetup(BASE_URL, params);
|
||||
|
||||
let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
|
||||
yield tokenPromise.then(body => {
|
||||
yield tokenPromise.then(() => {
|
||||
ok(false, "Promise should have rejected");
|
||||
},
|
||||
error => {
|
||||
@@ -357,7 +357,7 @@ add_task(function* loginWithParams401() {
|
||||
yield MozLoopService.promiseRegisteredWithServers();
|
||||
|
||||
let loginPromise = MozLoopService.logInToFxA();
|
||||
yield loginPromise.then(tokenData => {
|
||||
yield loginPromise.then(() => {
|
||||
ok(false, "Promise should have rejected");
|
||||
},
|
||||
error => {
|
||||
@@ -379,7 +379,7 @@ add_task(function* logoutWithIncorrectPushURL() {
|
||||
is(registrationResponse.response.simplePushURLs.rooms, pushURL, "Check registered push URL");
|
||||
MozLoopServiceInternal.pushURLs.get(LOOP_SESSION_TYPE.FXA).rooms = "http://www.example.com/invalid";
|
||||
let caught = false;
|
||||
yield MozLoopService.logOutFromFxA().catch((error) => {
|
||||
yield MozLoopService.logOutFromFxA().catch(() => {
|
||||
caught = true;
|
||||
});
|
||||
ok(caught, "Should have caught an error logging out with a mismatched push URL");
|
||||
@@ -418,7 +418,7 @@ add_task(function* loginWithRegistration401() {
|
||||
yield promiseOAuthParamsSetup(BASE_URL, params);
|
||||
|
||||
let loginPromise = MozLoopService.logInToFxA();
|
||||
yield loginPromise.then(tokenData => {
|
||||
yield loginPromise.then(() => {
|
||||
ok(false, "Promise should have rejected");
|
||||
},
|
||||
error => {
|
||||
@@ -437,6 +437,17 @@ add_task(function* openFxASettings() {
|
||||
// blank tab.
|
||||
gBrowser.selectedTab = gBrowser.addTab(BASE_URL);
|
||||
|
||||
let fxASampleToken = {
|
||||
token_type: "bearer",
|
||||
access_token: "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",
|
||||
scope: "profile"
|
||||
};
|
||||
|
||||
let fxASampleProfile = {
|
||||
email: "test@example.com",
|
||||
uid: "abcd1234"
|
||||
};
|
||||
|
||||
let params = {
|
||||
client_id: "client_id",
|
||||
content_uri: BASE_URL + "/content",
|
||||
@@ -445,9 +456,20 @@ add_task(function* openFxASettings() {
|
||||
state: "state",
|
||||
test_error: "token_401"
|
||||
};
|
||||
|
||||
Services.prefs.setCharPref("loop.fxa_oauth.profile", JSON.stringify(fxASampleProfile));
|
||||
Services.prefs.setCharPref("loop.fxa_oauth.tokendata", JSON.stringify(fxASampleToken));
|
||||
|
||||
yield promiseOAuthParamsSetup(BASE_URL, params);
|
||||
|
||||
yield new Promise((resolve, reject) => {
|
||||
registerCleanupFunction(function* () {
|
||||
info("cleanup specific to openFxASettings test");
|
||||
yield promiseDeletedOAuthParams(BASE_URL);
|
||||
Services.prefs.clearUserPref("loop.fxa_oauth.profile");
|
||||
Services.prefs.clearUserPref("loop.fxa_oauth.tokendata");
|
||||
});
|
||||
|
||||
yield new Promise((resolve) => {
|
||||
let progressListener = {
|
||||
onLocationChange: function onLocationChange(aBrowser) {
|
||||
if (aBrowser.currentURI.spec == BASE_URL) {
|
||||
@@ -456,7 +478,7 @@ add_task(function* openFxASettings() {
|
||||
}
|
||||
gBrowser.removeTabsProgressListener(progressListener);
|
||||
let contentURI = Services.io.newURI(params.content_uri, null, null);
|
||||
is(aBrowser.currentURI.spec, Services.io.newURI("/settings", null, contentURI).spec,
|
||||
is(aBrowser.currentURI.spec, Services.io.newURI("/settings?uid=abcd1234", null, contentURI).spec,
|
||||
"Check settings tab URL");
|
||||
resolve();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/loop/test/mochitest/loop_fxa.sjs?";
|
||||
const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/loop/chrome/test/mochitest/loop_fxa.sjs?";
|
||||
|
||||
registerCleanupFunction(function* () {
|
||||
yield promiseDeletedOAuthParams(BASE_URL);
|
||||
|
||||
@@ -130,7 +130,6 @@ add_task(function* test_multipleListener() {
|
||||
});
|
||||
|
||||
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";
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"use strict";
|
||||
|
||||
Components.utils.import("resource://gre/modules/Promise.jsm", this);
|
||||
const { LoopRoomsInternal } = Components.utils.import("chrome://loop/content/modules/LoopRooms.jsm", {});
|
||||
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 1);
|
||||
|
||||
const fxASampleToken = {
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported HAWK_TOKEN_LENGTH, LoopRooms, promiseWaitForCondition,
|
||||
loadLoopPanel, promiseOAuthParamsSetup, resetFxA, checkLoggedOutState,
|
||||
promiseDeletedOAuthParams, promiseOAuthGetRegistration,
|
||||
getLoopString, mockPushHandler, channelID, mockDb, LoopAPI */
|
||||
|
||||
const HAWK_TOKEN_LENGTH = 64;
|
||||
const {
|
||||
LOOP_SESSION_TYPE,
|
||||
@@ -18,7 +23,7 @@ const { LoopRooms } = Cu.import("chrome://loop/content/modules/LoopRooms.jsm", {
|
||||
const WAS_OFFLINE = Services.io.offline;
|
||||
|
||||
function promisePanelLoaded() {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
let loopPanel = document.getElementById("loop-notification-panel");
|
||||
let btn = document.getElementById("loop-button");
|
||||
|
||||
@@ -43,7 +48,7 @@ function promisePanelLoaded() {
|
||||
iframe.contentDocument.readyState == "complete") {
|
||||
resolve();
|
||||
} else {
|
||||
iframe.addEventListener("load", function panelOnLoad(e) {
|
||||
iframe.addEventListener("load", function panelOnLoad() {
|
||||
iframe.removeEventListener("load", panelOnLoad, true);
|
||||
// We do this in an execute soon to allow any other event listeners to
|
||||
// be handled, just in case.
|
||||
@@ -89,7 +94,7 @@ function waitForCondition(condition, nextTest, errorMsg) {
|
||||
}
|
||||
|
||||
function promiseWaitForCondition(aConditionFn) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
waitForCondition(aConditionFn, resolve, "Condition didn't pass.");
|
||||
});
|
||||
}
|
||||
@@ -175,7 +180,7 @@ function promiseDeletedOAuthParams(baseURL) {
|
||||
}
|
||||
|
||||
function promiseObserverNotified(aTopic, aExpectedData = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
Services.obs.addObserver(function onNotification(aSubject, topic, aData) {
|
||||
Services.obs.removeObserver(onNotification, topic);
|
||||
is(aData, aExpectedData, "observer data should match expected data");
|
||||
@@ -235,7 +240,7 @@ var mockPushHandler = {
|
||||
setTimeout(registerCallback(this.registrationResult, this.registeredChannels[channelId], channelId), 0);
|
||||
},
|
||||
|
||||
unregister: function(channelID) {
|
||||
unregister: function() {
|
||||
return;
|
||||
},
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"extends": "../../.eslintrc-gecko",
|
||||
"globals": {
|
||||
// General xpcshell-test functions
|
||||
"HttpServer": false,
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported Cr, LoopRoomsInternal, timerHandlers, kMockWebSocketChannelName,
|
||||
kWebSocketChannelContractID, kServerPushUrl, kLoopServerUrl,
|
||||
setupFakeLoopServer, setupFakeFxAUserProfile, waitForCondition,
|
||||
getLoopString, extend */
|
||||
|
||||
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
// Initialize this before the imports, as some of them need it.
|
||||
@@ -159,7 +164,7 @@ var mockPushHandler = {
|
||||
registerCallback(this.registrationResult, this.registrationPushURL, channelId);
|
||||
},
|
||||
|
||||
unregister: function(channelID) {
|
||||
unregister: function() {
|
||||
return;
|
||||
},
|
||||
|
||||
@@ -183,7 +188,7 @@ MockWebSocketChannel.prototype = {
|
||||
|
||||
initRegStatus: 0,
|
||||
|
||||
defaultMsgHandler: function(msg) {
|
||||
defaultMsgHandler: function() {
|
||||
// Treat as a ping
|
||||
this.listener.onMessageAvailable(this.context,
|
||||
JSON.stringify({}));
|
||||
@@ -231,7 +236,7 @@ MockWebSocketChannel.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
close: function(aCode, aReason) {
|
||||
close: function(aCode) {
|
||||
this.stop(aCode);
|
||||
},
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
|
||||
const [LoopAPIInternal] = LoopAPI.inspect();
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
var dummyCallback = () => {};
|
||||
var mockWebSocket = new MockWebSocketChannel();
|
||||
var pushServerRequestCount = 0;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("chrome://loop/content/modules/LoopRooms.jsm");
|
||||
Cu.import("resource:///modules/Chat.jsm");
|
||||
@@ -169,12 +171,6 @@ const kCreateRoomProps = {
|
||||
maxSize: 2
|
||||
};
|
||||
|
||||
const kCreateRoomUnencryptedProps = {
|
||||
roomName: "UX Discussion",
|
||||
roomOwner: "Alexis",
|
||||
maxSize: 2
|
||||
};
|
||||
|
||||
const kCreateRoomData = {
|
||||
roomToken: "_nxD4V4FflQ",
|
||||
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
|
||||
@@ -261,7 +257,7 @@ const onRoomLeft = function(e, room, participant) {
|
||||
}
|
||||
};
|
||||
|
||||
const onRefresh = function(e) {
|
||||
const onRefresh = function() {
|
||||
Assert.ok(gExpectedRefresh, "A refresh event should've been expected");
|
||||
gExpectedRefresh = false;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
timerHandlers.startTimer = callback => callback();
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
@@ -184,7 +186,7 @@ add_task(function* setup_server() {
|
||||
|
||||
// Test if getting rooms saves unknown keys correctly.
|
||||
add_task(function* test_get_rooms_saves_unknown_keys() {
|
||||
let rooms = yield LoopRooms.promise("getAll");
|
||||
yield LoopRooms.promise("getAll");
|
||||
|
||||
// Check that we've saved the encryption keys correctly.
|
||||
let roomsCache = yield readRoomsCache();
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("chrome://loop/content/modules/LoopRooms.jsm");
|
||||
Cu.import("resource:///modules/Chat.jsm");
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource:///modules/Chat.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
const loopCrypto = Cu.import("chrome://loop/content/shared/js/crypto.js", {}).LoopCrypto;
|
||||
const { LOOP_ROOMS_CACHE_FILENAME } = Cu.import("chrome://loop/content/modules/LoopRoomsCache.jsm", {});
|
||||
|
||||
var gTimerArgs = [];
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
|
||||
"resource:///modules/Chat.jsm");
|
||||
var openChatOrig = Chat.open;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
const { INVALID_AUTH_TOKEN } = Cu.import("chrome://loop/content/modules/MozLoopService.jsm");
|
||||
|
||||
/**
|
||||
@@ -61,7 +63,7 @@ add_task(function* guest_401() {
|
||||
Services.prefs.setCharPref("loop.hawk-session-token.fxa", "fxa");
|
||||
yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.GUEST, "/401", "POST").then(
|
||||
() => Assert.ok(false, "Should have rejected"),
|
||||
(error) => {
|
||||
() => {
|
||||
Assert.strictEqual(Services.prefs.getPrefType("loop.hawk-session-token"),
|
||||
Services.prefs.PREF_INVALID,
|
||||
"Guest session token should have been cleared");
|
||||
@@ -85,7 +87,7 @@ add_task(function* fxa_401() {
|
||||
Services.prefs.setCharPref("loop.hawk-session-token.fxa", "fxa");
|
||||
yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.FXA, "/401", "POST").then(
|
||||
() => Assert.ok(false, "Should have rejected"),
|
||||
(error) => {
|
||||
() => {
|
||||
Assert.strictEqual(Services.prefs.getCharPref("loop.hawk-session-token"),
|
||||
"guest",
|
||||
"Guest session token should NOT have been cleared");
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
add_task(function* request_with_unicode() {
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
var startTimerCalled = false;
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
function test_locale() {
|
||||
// Set the pref to something controlled.
|
||||
Services.prefs.setCharPref("general.useragent.locale", "ab-CD");
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
var fakeCharPrefName = "color";
|
||||
var fakeBoolPrefName = "boolean";
|
||||
var fakePrefValue = "green";
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
const FAKE_FXA_TOKEN_DATA = JSON.stringify({
|
||||
"token_type": "bearer",
|
||||
"access_token": "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
const LOOP_HAWK_PREF = "loop.hawk-session-token";
|
||||
const fakeSessionToken1 = "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1751";
|
||||
const fakeSessionToken2 = "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1750";
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
/**
|
||||
* Test that things behave reasonably when a reasonable Hawk-Session-Token
|
||||
* header is returned with the registration response.
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
add_test(function test_registration_uses_hawk_session_token() {
|
||||
Services.prefs.setCharPref("loop.hawk-session-token",
|
||||
"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1750");
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported run_test */
|
||||
|
||||
// XXX should report error if Hawk-Session-Token is lexically invalid
|
||||
// (not a string of 64 hex digits) to help resist other possible injection
|
||||
// attacks. For now, however, we're just checking if it's the right length.
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Firefox Hello</em:name>
|
||||
<em:name>Firefox Hello Beta</em:name>
|
||||
<em:description>Web sharing for Firefox</em:description>
|
||||
<em:creator>Mozilla</em:creator>
|
||||
</Description>
|
||||
|
||||
@@ -26,139 +26,20 @@
|
||||
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-aero@2x.png os=WINNT osversion=6.1
|
||||
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-win10@2x.png os=WINNT osversion=6.2
|
||||
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-win10@2x.png os=WINNT osversion=6.3
|
||||
skin/ (skin/*)
|
||||
content/modules/ (content/modules/*)
|
||||
* content/preferences/prefs.js (content/preferences/prefs.js)
|
||||
|
||||
# Desktop html files
|
||||
content/panels/conversation.html (content/panels/conversation.html)
|
||||
content/panels/panel.html (content/panels/panel.html)
|
||||
|
||||
# Desktop vendor (see bottom of this file for TokBox sdk assets)
|
||||
content/panels/vendor/l10n.js (content/panels/vendor/l10n.js)
|
||||
|
||||
# Desktop script
|
||||
content/panels/js/conversation.js (content/panels/js/conversation.js)
|
||||
content/panels/js/conversationAppStore.js (content/panels/js/conversationAppStore.js)
|
||||
content/panels/js/otconfig.js (content/panels/js/otconfig.js)
|
||||
content/panels/js/panel.js (content/panels/js/panel.js)
|
||||
content/panels/js/roomStore.js (content/panels/js/roomStore.js)
|
||||
content/panels/js/roomViews.js (content/panels/js/roomViews.js)
|
||||
content/panels/js/feedbackViews.js (content/panels/js/feedbackViews.js)
|
||||
|
||||
# Desktop styles
|
||||
content/panels/css/panel.css (content/panels/css/panel.css)
|
||||
|
||||
# Shared styles
|
||||
content/shared/css/reset.css (content/shared/css/reset.css)
|
||||
content/shared/css/common.css (content/shared/css/common.css)
|
||||
content/shared/css/conversation.css (content/shared/css/conversation.css)
|
||||
|
||||
# Shared images
|
||||
content/shared/img/helloicon.svg (content/shared/img/helloicon.svg)
|
||||
content/shared/img/icon_32.png (content/shared/img/icon_32.png)
|
||||
content/shared/img/icon_64.png (content/shared/img/icon_64.png)
|
||||
content/shared/img/spinner.svg (content/shared/img/spinner.svg)
|
||||
# XXX could get rid of the png spinner usages and replace them with the svg
|
||||
# one?
|
||||
content/shared/img/spinner.png (content/shared/img/spinner.png)
|
||||
content/shared/img/spinner@2x.png (content/shared/img/spinner@2x.png)
|
||||
content/shared/img/sad_hello_icon_64x64.svg (content/shared/img/sad_hello_icon_64x64.svg)
|
||||
content/shared/img/chatbubble-arrow-left.svg (content/shared/img/chatbubble-arrow-left.svg)
|
||||
content/shared/img/chatbubble-arrow-right.svg (content/shared/img/chatbubble-arrow-right.svg)
|
||||
content/shared/img/facemute-14x14.png (content/shared/img/facemute-14x14.png)
|
||||
content/shared/img/facemute-14x14@2x.png (content/shared/img/facemute-14x14@2x.png)
|
||||
content/shared/img/hangup-inverse-14x14.png (content/shared/img/hangup-inverse-14x14.png)
|
||||
content/shared/img/hangup-inverse-14x14@2x.png (content/shared/img/hangup-inverse-14x14@2x.png)
|
||||
content/shared/img/mute-inverse-14x14.png (content/shared/img/mute-inverse-14x14.png)
|
||||
content/shared/img/mute-inverse-14x14@2x.png (content/shared/img/mute-inverse-14x14@2x.png)
|
||||
content/shared/img/glyph-email-16x16.svg (content/shared/img/glyph-email-16x16.svg)
|
||||
content/shared/img/glyph-facebook-16x16.svg (content/shared/img/glyph-facebook-16x16.svg)
|
||||
content/shared/img/glyph-help-16x16.svg (content/shared/img/glyph-help-16x16.svg)
|
||||
content/shared/img/glyph-link-16x16.svg (content/shared/img/glyph-link-16x16.svg)
|
||||
content/shared/img/glyph-user-16x16.svg (content/shared/img/glyph-user-16x16.svg)
|
||||
content/shared/img/exit.svg (content/shared/img/exit.svg)
|
||||
content/shared/img/audio.svg (content/shared/img/audio.svg)
|
||||
content/shared/img/audio-hover.svg (content/shared/img/audio-hover.svg)
|
||||
content/shared/img/audio-mute.svg (content/shared/img/audio-mute.svg)
|
||||
content/shared/img/audio-mute-hover.svg (content/shared/img/audio-mute-hover.svg)
|
||||
content/shared/img/video.svg (content/shared/img/video.svg)
|
||||
content/shared/img/video-hover.svg (content/shared/img/video-hover.svg)
|
||||
content/shared/img/video-mute.svg (content/shared/img/video-mute.svg)
|
||||
content/shared/img/video-mute-hover.svg (content/shared/img/video-mute-hover.svg)
|
||||
content/shared/img/settings.svg (content/shared/img/settings.svg)
|
||||
content/shared/img/settings-hover.svg (content/shared/img/settings-hover.svg)
|
||||
content/shared/img/sharing.svg (content/shared/img/sharing.svg)
|
||||
content/shared/img/sharing-active.svg (content/shared/img/sharing-active.svg)
|
||||
content/shared/img/sharing-pending.svg (content/shared/img/sharing-pending.svg)
|
||||
content/shared/img/sharing-hover.svg (content/shared/img/sharing-hover.svg)
|
||||
content/shared/img/media-group.svg (content/shared/img/media-group.svg)
|
||||
content/shared/img/media-group-left-hover.svg (content/shared/img/media-group-left-hover.svg)
|
||||
content/shared/img/media-group-right-hover.svg (content/shared/img/media-group-right-hover.svg)
|
||||
content/shared/img/audio-call-avatar.svg (content/shared/img/audio-call-avatar.svg)
|
||||
content/shared/img/beta-ribbon.svg (content/shared/img/beta-ribbon.svg)
|
||||
content/shared/img/check.svg (content/shared/img/check.svg)
|
||||
content/shared/img/icons-10x10.svg (content/shared/img/icons-10x10.svg)
|
||||
content/shared/img/icons-14x14.svg (content/shared/img/icons-14x14.svg)
|
||||
content/shared/img/icons-16x16.svg (content/shared/img/icons-16x16.svg)
|
||||
content/shared/img/movistar.png (content/shared/img/movistar.png)
|
||||
content/shared/img/movistar@2x.png (content/shared/img/movistar@2x.png)
|
||||
content/shared/img/vivo.png (content/shared/img/vivo.png)
|
||||
content/shared/img/vivo@2x.png (content/shared/img/vivo@2x.png)
|
||||
content/shared/img/02.png (content/shared/img/02.png)
|
||||
content/shared/img/02@2x.png (content/shared/img/02@2x.png)
|
||||
content/shared/img/telefonica-logo.svg (content/shared/img/telefonica-logo.svg)
|
||||
content/shared/img/hello_logo.svg (content/shared/img/hello_logo.svg)
|
||||
content/shared/img/hello-web-share.svg (content/shared/img/hello-web-share.svg)
|
||||
content/shared/img/ellipsis-v.svg (content/shared/img/ellipsis-v.svg)
|
||||
content/shared/img/empty_conversations.svg (content/shared/img/empty_conversations.svg)
|
||||
content/shared/img/empty_search.svg (content/shared/img/empty_search.svg)
|
||||
content/shared/img/animated-spinner.svg (content/shared/img/animated-spinner.svg)
|
||||
content/shared/img/avatars.svg (content/shared/img/avatars.svg)
|
||||
content/shared/img/firefox-avatar.svg (content/shared/img/firefox-avatar.svg)
|
||||
content/shared/img/pause-12x12.svg (content/shared/img/pause-12x12.svg)
|
||||
content/shared/img/play-12x12.svg (content/shared/img/play-12x12.svg)
|
||||
content/shared/img/stop-12x12.svg (content/shared/img/stop-12x12.svg)
|
||||
|
||||
# Shared scripts
|
||||
content/shared/js/actions.js (content/shared/js/actions.js)
|
||||
content/shared/js/crypto.js (content/shared/js/crypto.js)
|
||||
content/shared/js/store.js (content/shared/js/store.js)
|
||||
content/shared/js/activeRoomStore.js (content/shared/js/activeRoomStore.js)
|
||||
content/shared/js/dispatcher.js (content/shared/js/dispatcher.js)
|
||||
content/shared/js/linkifiedTextView.js (content/shared/js/linkifiedTextView.js)
|
||||
content/shared/js/loopapi-client.js (content/shared/js/loopapi-client.js)
|
||||
content/shared/js/models.js (content/shared/js/models.js)
|
||||
content/shared/js/mixins.js (content/shared/js/mixins.js)
|
||||
content/shared/js/otSdkDriver.js (content/shared/js/otSdkDriver.js)
|
||||
content/shared/js/views.js (content/shared/js/views.js)
|
||||
content/shared/js/textChatStore.js (content/shared/js/textChatStore.js)
|
||||
content/shared/js/textChatView.js (content/shared/js/textChatView.js)
|
||||
content/shared/js/urlRegExps.js (content/shared/js/urlRegExps.js)
|
||||
content/shared/js/utils.js (content/shared/js/utils.js)
|
||||
content/shared/js/validate.js (content/shared/js/validate.js)
|
||||
|
||||
# Shared libs
|
||||
#ifdef DEBUG
|
||||
content/shared/vendor/react-0.13.3.js (content/shared/vendor/react-0.13.3.js)
|
||||
#else
|
||||
content/shared/vendor/react-0.13.3.js (content/shared/vendor/react-0.13.3-prod.js)
|
||||
skin/ (chrome/skin/*)
|
||||
content/modules/ (chrome/content/modules/*)
|
||||
# We don't package the test/ directory for panels, so do these separately.
|
||||
content/panels/ (chrome/content/panels/*.html)
|
||||
content/panels/css/ (chrome/content/panels/css/*)
|
||||
content/panels/js/ (chrome/content/panels/js/*)
|
||||
content/panels/vendor/ (chrome/content/panels/vendor/*)
|
||||
* content/preferences/prefs.js (chrome/content/preferences/prefs.js)
|
||||
# We don't package the test/ directory for shared, so do these separately.
|
||||
content/shared/css/ (chrome/content/shared/css/*)
|
||||
content/shared/img/ (chrome/content/shared/img/*)
|
||||
content/shared/js/ (chrome/content/shared/js/*)
|
||||
content/shared/sounds/ (chrome/content/shared/sounds/*)
|
||||
content/shared/vendor/ (chrome/content/shared/vendor/*)
|
||||
#ifndef DEBUG
|
||||
+ content/shared/vendor/react.js (chrome/content/shared/vendor/react-prod.js)
|
||||
#endif
|
||||
content/shared/vendor/lodash-3.9.3.js (content/shared/vendor/lodash-3.9.3.js)
|
||||
content/shared/vendor/backbone-1.2.1.js (content/shared/vendor/backbone-1.2.1.js)
|
||||
content/shared/vendor/classnames-2.2.0.js (content/shared/vendor/classnames-2.2.0.js)
|
||||
|
||||
# Shared sounds
|
||||
content/shared/sounds/ringtone.ogg (content/shared/sounds/ringtone.ogg)
|
||||
content/shared/sounds/connecting.ogg (content/shared/sounds/connecting.ogg)
|
||||
content/shared/sounds/connected.ogg (content/shared/sounds/connected.ogg)
|
||||
content/shared/sounds/terminated.ogg (content/shared/sounds/terminated.ogg)
|
||||
content/shared/sounds/room-joined.ogg (content/shared/sounds/room-joined.ogg)
|
||||
content/shared/sounds/room-joined-in.ogg (content/shared/sounds/room-joined-in.ogg)
|
||||
content/shared/sounds/room-left.ogg (content/shared/sounds/room-left.ogg)
|
||||
content/shared/sounds/failure.ogg (content/shared/sounds/failure.ogg)
|
||||
content/shared/sounds/message.ogg (content/shared/sounds/message.ogg)
|
||||
|
||||
# Partner SDK assets
|
||||
content/shared/vendor/sdk.js (content/shared/vendor/sdk.js)
|
||||
content/sdk-content/js/dynamic_config.min.js (content/shared/vendor/sdk-content/js/dynamic_config.min.js)
|
||||
|
||||
Reference in New Issue
Block a user