mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
Merge fx-team to m-c. a=merge
This commit is contained in:
commit
50905e0270
@ -4,3 +4,4 @@
|
||||
|
||||
MOZ_APP_DISPLAYNAME=FirefoxDeveloperEdition
|
||||
MOZ_APP_REMOTINGNAME=firefox-dev
|
||||
MOZ_DEV_EDITION=1
|
||||
|
@ -417,6 +417,8 @@ loop.contacts = (function(_, mozL10n) {
|
||||
},
|
||||
|
||||
render: function() {
|
||||
let cx = React.addons.classSet;
|
||||
|
||||
let viewForItem = item => {
|
||||
return ContactDetail({key: item._guid, contact: item,
|
||||
handleContactAction: this.handleContactAction})
|
||||
@ -444,7 +446,6 @@ loop.contacts = (function(_, mozL10n) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: bug 1076767 - add a spinner whilst importing contacts.
|
||||
return (
|
||||
React.DOM.div(null,
|
||||
React.DOM.div({className: "content-area"},
|
||||
@ -453,7 +454,11 @@ loop.contacts = (function(_, mozL10n) {
|
||||
? mozL10n.get("importing_contacts_progress_button")
|
||||
: mozL10n.get("import_contacts_button"),
|
||||
disabled: this.state.importBusy,
|
||||
onClick: this.handleImportButtonClick}),
|
||||
onClick: this.handleImportButtonClick},
|
||||
React.DOM.div({className: cx({"contact-import-spinner": true,
|
||||
spinner: true,
|
||||
busy: this.state.importBusy})})
|
||||
),
|
||||
Button({caption: mozL10n.get("new_contact_button"),
|
||||
onClick: this.handleAddContactButtonClick})
|
||||
),
|
||||
|
@ -417,6 +417,8 @@ loop.contacts = (function(_, mozL10n) {
|
||||
},
|
||||
|
||||
render: function() {
|
||||
let cx = React.addons.classSet;
|
||||
|
||||
let viewForItem = item => {
|
||||
return <ContactDetail key={item._guid} contact={item}
|
||||
handleContactAction={this.handleContactAction} />
|
||||
@ -444,7 +446,6 @@ loop.contacts = (function(_, mozL10n) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: bug 1076767 - add a spinner whilst importing contacts.
|
||||
return (
|
||||
<div>
|
||||
<div className="content-area">
|
||||
@ -453,7 +454,11 @@ loop.contacts = (function(_, mozL10n) {
|
||||
? mozL10n.get("importing_contacts_progress_button")
|
||||
: mozL10n.get("import_contacts_button")}
|
||||
disabled={this.state.importBusy}
|
||||
onClick={this.handleImportButtonClick} />
|
||||
onClick={this.handleImportButtonClick}>
|
||||
<div className={cx({"contact-import-spinner": true,
|
||||
spinner: true,
|
||||
busy: this.state.importBusy})} />
|
||||
</Button>
|
||||
<Button caption={mozL10n.get("new_contact_button")}
|
||||
onClick={this.handleAddContactButtonClick} />
|
||||
</ButtonGroup>
|
||||
|
@ -18,7 +18,7 @@ loop.conversation = (function(mozL10n) {
|
||||
|
||||
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
|
||||
var CallIdentifierView = loop.conversationViews.CallIdentifierView;
|
||||
var DesktopRoomControllerView = loop.roomViews.DesktopRoomControllerView;
|
||||
var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
|
||||
|
||||
var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
|
||||
mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
|
||||
@ -584,10 +584,10 @@ loop.conversation = (function(mozL10n) {
|
||||
));
|
||||
}
|
||||
case "room": {
|
||||
return (DesktopRoomControllerView({
|
||||
mozLoop: navigator.mozLoop,
|
||||
return (DesktopRoomConversationView({
|
||||
dispatcher: this.props.dispatcher,
|
||||
roomStore: this.props.roomStore}
|
||||
roomStore: this.props.roomStore,
|
||||
dispatcher: this.props.dispatcher}
|
||||
));
|
||||
}
|
||||
case "failed": {
|
||||
@ -643,7 +643,8 @@ loop.conversation = (function(mozL10n) {
|
||||
});
|
||||
var activeRoomStore = new loop.store.ActiveRoomStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: navigator.mozLoop
|
||||
mozLoop: navigator.mozLoop,
|
||||
sdkDriver: sdkDriver
|
||||
});
|
||||
var roomStore = new loop.store.RoomStore({
|
||||
dispatcher: dispatcher,
|
||||
|
@ -18,7 +18,7 @@ loop.conversation = (function(mozL10n) {
|
||||
|
||||
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
|
||||
var CallIdentifierView = loop.conversationViews.CallIdentifierView;
|
||||
var DesktopRoomControllerView = loop.roomViews.DesktopRoomControllerView;
|
||||
var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
|
||||
|
||||
var IncomingCallView = React.createClass({
|
||||
mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
|
||||
@ -584,10 +584,10 @@ loop.conversation = (function(mozL10n) {
|
||||
/>);
|
||||
}
|
||||
case "room": {
|
||||
return (<DesktopRoomControllerView
|
||||
mozLoop={navigator.mozLoop}
|
||||
return (<DesktopRoomConversationView
|
||||
dispatcher={this.props.dispatcher}
|
||||
roomStore={this.props.roomStore}
|
||||
dispatcher={this.props.dispatcher}
|
||||
/>);
|
||||
}
|
||||
case "failed": {
|
||||
@ -643,7 +643,8 @@ loop.conversation = (function(mozL10n) {
|
||||
});
|
||||
var activeRoomStore = new loop.store.ActiveRoomStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: navigator.mozLoop
|
||||
mozLoop: navigator.mozLoop,
|
||||
sdkDriver: sdkDriver
|
||||
});
|
||||
var roomStore = new loop.store.RoomStore({
|
||||
dispatcher: dispatcher,
|
||||
|
@ -399,18 +399,21 @@ loop.panel = (function(_, mozL10n) {
|
||||
// readOnly attr will suppress a warning regarding this issue
|
||||
// from the react lib.
|
||||
var cx = React.addons.classSet;
|
||||
var inputCSSClass = cx({
|
||||
"pending": this.state.pending,
|
||||
// Used in functional testing, signals that
|
||||
// call url was received from loop server
|
||||
"callUrl": !this.state.pending
|
||||
});
|
||||
return (
|
||||
React.DOM.div({className: "generate-url"},
|
||||
React.DOM.header(null, __("share_link_header_text")),
|
||||
React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true",
|
||||
onCopy: this.handleLinkExfiltration,
|
||||
className: inputCSSClass}),
|
||||
React.DOM.div({className: "generate-url-stack"},
|
||||
React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true",
|
||||
onCopy: this.handleLinkExfiltration,
|
||||
className: cx({"generate-url-input": true,
|
||||
pending: this.state.pending,
|
||||
// Used in functional testing, signals that
|
||||
// call url was received from loop server
|
||||
callUrl: !this.state.pending})}),
|
||||
React.DOM.div({className: cx({"generate-url-spinner": true,
|
||||
spinner: true,
|
||||
busy: this.state.pending})})
|
||||
),
|
||||
ButtonGroup({additionalClass: "url-actions"},
|
||||
Button({additionalClass: "button-email",
|
||||
disabled: !this.state.callUrl,
|
||||
|
@ -399,18 +399,21 @@ loop.panel = (function(_, mozL10n) {
|
||||
// readOnly attr will suppress a warning regarding this issue
|
||||
// from the react lib.
|
||||
var cx = React.addons.classSet;
|
||||
var inputCSSClass = cx({
|
||||
"pending": this.state.pending,
|
||||
// Used in functional testing, signals that
|
||||
// call url was received from loop server
|
||||
"callUrl": !this.state.pending
|
||||
});
|
||||
return (
|
||||
<div className="generate-url">
|
||||
<header>{__("share_link_header_text")}</header>
|
||||
<input type="url" value={this.state.callUrl} readOnly="true"
|
||||
onCopy={this.handleLinkExfiltration}
|
||||
className={inputCSSClass} />
|
||||
<div className="generate-url-stack">
|
||||
<input type="url" value={this.state.callUrl} readOnly="true"
|
||||
onCopy={this.handleLinkExfiltration}
|
||||
className={cx({"generate-url-input": true,
|
||||
pending: this.state.pending,
|
||||
// Used in functional testing, signals that
|
||||
// call url was received from loop server
|
||||
callUrl: !this.state.pending})} />
|
||||
<div className={cx({"generate-url-spinner": true,
|
||||
spinner: true,
|
||||
busy: this.state.pending})} />
|
||||
</div>
|
||||
<ButtonGroup additionalClass="url-actions">
|
||||
<Button additionalClass="button-email"
|
||||
disabled={!this.state.callUrl}
|
||||
|
@ -11,6 +11,7 @@ var loop = loop || {};
|
||||
loop.roomViews = (function(mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
@ -37,11 +38,20 @@ loop.roomViews = (function(mozL10n) {
|
||||
},
|
||||
|
||||
_onActiveRoomStateChanged: function() {
|
||||
this.setState(this.props.roomStore.getStoreState("activeRoom"));
|
||||
// Only update the state if we're mounted, to avoid the problem where
|
||||
// stopListening doesn't nuke the active listeners during a event
|
||||
// processing.
|
||||
if (this.isMounted()) {
|
||||
this.setState(this.props.roomStore.getStoreState("activeRoom"));
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.props.roomStore.getStoreState("activeRoom");
|
||||
var storeState = this.props.roomStore.getStoreState("activeRoom");
|
||||
return _.extend(storeState, {
|
||||
// Used by the UI showcase.
|
||||
roomState: this.props.roomState || storeState.roomState
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -72,25 +82,22 @@ loop.roomViews = (function(mozL10n) {
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.div({className: "room-conversation-wrapper"},
|
||||
React.DOM.div({className: "room-invitation-overlay"},
|
||||
React.DOM.form({onSubmit: this.handleFormSubmit},
|
||||
React.DOM.input({type: "text", ref: "roomName",
|
||||
placeholder: mozL10n.get("rooms_name_this_room_label")})
|
||||
),
|
||||
React.DOM.p(null, mozL10n.get("invite_header_text")),
|
||||
React.DOM.div({className: "btn-group call-action-group"},
|
||||
React.DOM.button({className: "btn btn-info btn-email",
|
||||
onClick: this.handleEmailButtonClick},
|
||||
mozL10n.get("share_button2")
|
||||
),
|
||||
React.DOM.button({className: "btn btn-info btn-copy",
|
||||
onClick: this.handleCopyButtonClick},
|
||||
mozL10n.get("copy_url_button2")
|
||||
)
|
||||
)
|
||||
React.DOM.div({className: "room-invitation-overlay"},
|
||||
React.DOM.form({onSubmit: this.handleFormSubmit},
|
||||
React.DOM.input({type: "text", ref: "roomName",
|
||||
placeholder: mozL10n.get("rooms_name_this_room_label")})
|
||||
),
|
||||
DesktopRoomConversationView({roomStore: this.props.roomStore})
|
||||
React.DOM.p(null, mozL10n.get("invite_header_text")),
|
||||
React.DOM.div({className: "btn-group call-action-group"},
|
||||
React.DOM.button({className: "btn btn-info btn-email",
|
||||
onClick: this.handleEmailButtonClick},
|
||||
mozL10n.get("share_button2")
|
||||
),
|
||||
React.DOM.button({className: "btn btn-info btn-copy",
|
||||
onClick: this.handleCopyButtonClick},
|
||||
mozL10n.get("copy_url_button2")
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -100,105 +107,150 @@ loop.roomViews = (function(mozL10n) {
|
||||
* Desktop room conversation view.
|
||||
*/
|
||||
var DesktopRoomConversationView = React.createClass({displayName: 'DesktopRoomConversationView',
|
||||
mixins: [ActiveRoomStoreMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
video: React.PropTypes.object,
|
||||
audio: React.PropTypes.object,
|
||||
displayInvitation: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
video: {enabled: true, visible: true},
|
||||
audio: {enabled: true, visible: true}
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var localStreamClasses = React.addons.classSet({
|
||||
local: true,
|
||||
"local-stream": true,
|
||||
"local-stream-audio": !this.props.video.enabled
|
||||
});
|
||||
return (
|
||||
React.DOM.div({className: "room-conversation-wrapper"},
|
||||
React.DOM.div({className: "video-layout-wrapper"},
|
||||
React.DOM.div({className: "conversation room-conversation"},
|
||||
React.DOM.div({className: "media nested"},
|
||||
React.DOM.div({className: "video_wrapper remote_wrapper"},
|
||||
React.DOM.div({className: "video_inner remote"})
|
||||
),
|
||||
React.DOM.div({className: localStreamClasses})
|
||||
),
|
||||
sharedViews.ConversationToolbar({
|
||||
video: this.props.video,
|
||||
audio: this.props.audio,
|
||||
publishStream: noop,
|
||||
hangup: noop})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Desktop room controller view.
|
||||
*/
|
||||
var DesktopRoomControllerView = React.createClass({displayName: 'DesktopRoomControllerView',
|
||||
mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
_renderInvitationOverlay: function() {
|
||||
if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
|
||||
return DesktopRoomInvitationView({
|
||||
roomStore: this.props.roomStore,
|
||||
dispatcher: this.props.dispatcher}
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
/**
|
||||
* OT inserts inline styles into the markup. Using a listener for
|
||||
* resize events helps us trigger a full width/height on the element
|
||||
* so that they update to the correct dimensions.
|
||||
* XXX: this should be factored as a mixin.
|
||||
*/
|
||||
window.addEventListener('orientationchange', this.updateVideoContainer);
|
||||
window.addEventListener('resize', this.updateVideoContainer);
|
||||
|
||||
// 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.
|
||||
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||
publisherConfig: this._getPublisherConfig(),
|
||||
getLocalElementFunc: this._getElement.bind(this, ".local"),
|
||||
getRemoteElementFunc: this._getElement.bind(this, ".remote")
|
||||
}));
|
||||
},
|
||||
|
||||
_getPublisherConfig: function() {
|
||||
// height set to 100%" to fix video layout on Google Chrome
|
||||
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
|
||||
return {
|
||||
insertMode: "append",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
publishVideo: !this.state.videoMuted,
|
||||
style: {
|
||||
audioLevelDisplayMode: "off",
|
||||
bugDisplayMode: "off",
|
||||
buttonDisplayMode: "off",
|
||||
nameDisplayMode: "off",
|
||||
videoDisabledDisplayMode: "off"
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to update the video container whenever the orientation or size of the
|
||||
* display area changes.
|
||||
*/
|
||||
updateVideoContainer: function() {
|
||||
var localStreamParent = this._getElement('.local .OT_publisher');
|
||||
var remoteStreamParent = this._getElement('.remote .OT_subscriber');
|
||||
if (localStreamParent) {
|
||||
localStreamParent.style.width = "100%";
|
||||
}
|
||||
if (remoteStreamParent) {
|
||||
remoteStreamParent.style.height = "100%";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns either the required DOMNode
|
||||
*
|
||||
* @param {String} className The name of the class to get the element for.
|
||||
*/
|
||||
_getElement: function(className) {
|
||||
return this.getDOMNode().querySelector(className);
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the window if the cancel button is pressed in the generic failure view.
|
||||
*/
|
||||
closeWindow: function() {
|
||||
window.close();
|
||||
},
|
||||
|
||||
_renderRoomView: function(roomState) {
|
||||
switch (roomState) {
|
||||
case ROOM_STATES.FAILED: {
|
||||
return loop.conversation.GenericFailureView({
|
||||
cancelCall: this.closeWindow}
|
||||
);
|
||||
}
|
||||
case ROOM_STATES.INIT:
|
||||
case ROOM_STATES.GATHER:
|
||||
case ROOM_STATES.READY:
|
||||
case ROOM_STATES.JOINED: {
|
||||
return DesktopRoomInvitationView({
|
||||
dispatcher: this.props.dispatcher,
|
||||
roomStore: this.props.roomStore}
|
||||
);
|
||||
}
|
||||
// XXX needs bug 1074686/1074702
|
||||
case ROOM_STATES.HAS_PARTICIPANTS: {
|
||||
return DesktopRoomConversationView({
|
||||
dispatcher: this.props.dispatcher,
|
||||
roomStore: this.props.roomStore}
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Used to control publishing a stream - i.e. to mute a stream
|
||||
*
|
||||
* @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({
|
||||
type: type,
|
||||
enabled: enabled
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.roomName) {
|
||||
this.setTitle(this.state.roomName);
|
||||
}
|
||||
return (
|
||||
React.DOM.div({className: "room-conversation-wrapper"},
|
||||
this._renderRoomView(this.state.roomState)
|
||||
)
|
||||
);
|
||||
|
||||
var localStreamClasses = React.addons.classSet({
|
||||
local: true,
|
||||
"local-stream": true,
|
||||
"local-stream-audio": !this.state.videoMuted
|
||||
});
|
||||
|
||||
switch(this.state.roomState) {
|
||||
case ROOM_STATES.FAILED: {
|
||||
return loop.conversation.GenericFailureView({
|
||||
cancelCall: this.closeWindow}
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
React.DOM.div({className: "room-conversation-wrapper"},
|
||||
this._renderInvitationOverlay(),
|
||||
React.DOM.div({className: "video-layout-wrapper"},
|
||||
React.DOM.div({className: "conversation room-conversation"},
|
||||
React.DOM.div({className: "media nested"},
|
||||
React.DOM.div({className: "video_wrapper remote_wrapper"},
|
||||
React.DOM.div({className: "video_inner remote"})
|
||||
),
|
||||
React.DOM.div({className: localStreamClasses})
|
||||
),
|
||||
sharedViews.ConversationToolbar({
|
||||
video: {enabled: !this.state.videoMuted, visible: true},
|
||||
audio: {enabled: !this.state.audioMuted, visible: true},
|
||||
publishStream: this.publishStream,
|
||||
hangup: noop})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
ActiveRoomStoreMixin: ActiveRoomStoreMixin,
|
||||
DesktopRoomControllerView: DesktopRoomControllerView,
|
||||
DesktopRoomConversationView: DesktopRoomConversationView,
|
||||
DesktopRoomInvitationView: DesktopRoomInvitationView
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ var loop = loop || {};
|
||||
loop.roomViews = (function(mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
@ -37,11 +38,20 @@ loop.roomViews = (function(mozL10n) {
|
||||
},
|
||||
|
||||
_onActiveRoomStateChanged: function() {
|
||||
this.setState(this.props.roomStore.getStoreState("activeRoom"));
|
||||
// Only update the state if we're mounted, to avoid the problem where
|
||||
// stopListening doesn't nuke the active listeners during a event
|
||||
// processing.
|
||||
if (this.isMounted()) {
|
||||
this.setState(this.props.roomStore.getStoreState("activeRoom"));
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.props.roomStore.getStoreState("activeRoom");
|
||||
var storeState = this.props.roomStore.getStoreState("activeRoom");
|
||||
return _.extend(storeState, {
|
||||
// Used by the UI showcase.
|
||||
roomState: this.props.roomState || storeState.roomState
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -72,25 +82,22 @@ loop.roomViews = (function(mozL10n) {
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="room-conversation-wrapper">
|
||||
<div className="room-invitation-overlay">
|
||||
<form onSubmit={this.handleFormSubmit}>
|
||||
<input type="text" ref="roomName"
|
||||
placeholder={mozL10n.get("rooms_name_this_room_label")} />
|
||||
</form>
|
||||
<p>{mozL10n.get("invite_header_text")}</p>
|
||||
<div className="btn-group call-action-group">
|
||||
<button className="btn btn-info btn-email"
|
||||
onClick={this.handleEmailButtonClick}>
|
||||
{mozL10n.get("share_button2")}
|
||||
</button>
|
||||
<button className="btn btn-info btn-copy"
|
||||
onClick={this.handleCopyButtonClick}>
|
||||
{mozL10n.get("copy_url_button2")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="room-invitation-overlay">
|
||||
<form onSubmit={this.handleFormSubmit}>
|
||||
<input type="text" ref="roomName"
|
||||
placeholder={mozL10n.get("rooms_name_this_room_label")} />
|
||||
</form>
|
||||
<p>{mozL10n.get("invite_header_text")}</p>
|
||||
<div className="btn-group call-action-group">
|
||||
<button className="btn btn-info btn-email"
|
||||
onClick={this.handleEmailButtonClick}>
|
||||
{mozL10n.get("share_button2")}
|
||||
</button>
|
||||
<button className="btn btn-info btn-copy"
|
||||
onClick={this.handleCopyButtonClick}>
|
||||
{mozL10n.get("copy_url_button2")}
|
||||
</button>
|
||||
</div>
|
||||
<DesktopRoomConversationView roomStore={this.props.roomStore} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -100,105 +107,150 @@ loop.roomViews = (function(mozL10n) {
|
||||
* Desktop room conversation view.
|
||||
*/
|
||||
var DesktopRoomConversationView = React.createClass({
|
||||
mixins: [ActiveRoomStoreMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
video: React.PropTypes.object,
|
||||
audio: React.PropTypes.object,
|
||||
displayInvitation: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
video: {enabled: true, visible: true},
|
||||
audio: {enabled: true, visible: true}
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var localStreamClasses = React.addons.classSet({
|
||||
local: true,
|
||||
"local-stream": true,
|
||||
"local-stream-audio": !this.props.video.enabled
|
||||
});
|
||||
return (
|
||||
<div className="room-conversation-wrapper">
|
||||
<div className="video-layout-wrapper">
|
||||
<div className="conversation room-conversation">
|
||||
<div className="media nested">
|
||||
<div className="video_wrapper remote_wrapper">
|
||||
<div className="video_inner remote"></div>
|
||||
</div>
|
||||
<div className={localStreamClasses}></div>
|
||||
</div>
|
||||
<sharedViews.ConversationToolbar
|
||||
video={this.props.video}
|
||||
audio={this.props.audio}
|
||||
publishStream={noop}
|
||||
hangup={noop} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Desktop room controller view.
|
||||
*/
|
||||
var DesktopRoomControllerView = React.createClass({
|
||||
mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
_renderInvitationOverlay: function() {
|
||||
if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
|
||||
return <DesktopRoomInvitationView
|
||||
roomStore={this.props.roomStore}
|
||||
dispatcher={this.props.dispatcher}
|
||||
/>;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
/**
|
||||
* OT inserts inline styles into the markup. Using a listener for
|
||||
* resize events helps us trigger a full width/height on the element
|
||||
* so that they update to the correct dimensions.
|
||||
* XXX: this should be factored as a mixin.
|
||||
*/
|
||||
window.addEventListener('orientationchange', this.updateVideoContainer);
|
||||
window.addEventListener('resize', this.updateVideoContainer);
|
||||
|
||||
// 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.
|
||||
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||
publisherConfig: this._getPublisherConfig(),
|
||||
getLocalElementFunc: this._getElement.bind(this, ".local"),
|
||||
getRemoteElementFunc: this._getElement.bind(this, ".remote")
|
||||
}));
|
||||
},
|
||||
|
||||
_getPublisherConfig: function() {
|
||||
// height set to 100%" to fix video layout on Google Chrome
|
||||
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
|
||||
return {
|
||||
insertMode: "append",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
publishVideo: !this.state.videoMuted,
|
||||
style: {
|
||||
audioLevelDisplayMode: "off",
|
||||
bugDisplayMode: "off",
|
||||
buttonDisplayMode: "off",
|
||||
nameDisplayMode: "off",
|
||||
videoDisabledDisplayMode: "off"
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to update the video container whenever the orientation or size of the
|
||||
* display area changes.
|
||||
*/
|
||||
updateVideoContainer: function() {
|
||||
var localStreamParent = this._getElement('.local .OT_publisher');
|
||||
var remoteStreamParent = this._getElement('.remote .OT_subscriber');
|
||||
if (localStreamParent) {
|
||||
localStreamParent.style.width = "100%";
|
||||
}
|
||||
if (remoteStreamParent) {
|
||||
remoteStreamParent.style.height = "100%";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns either the required DOMNode
|
||||
*
|
||||
* @param {String} className The name of the class to get the element for.
|
||||
*/
|
||||
_getElement: function(className) {
|
||||
return this.getDOMNode().querySelector(className);
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the window if the cancel button is pressed in the generic failure view.
|
||||
*/
|
||||
closeWindow: function() {
|
||||
window.close();
|
||||
},
|
||||
|
||||
_renderRoomView: function(roomState) {
|
||||
switch (roomState) {
|
||||
case ROOM_STATES.FAILED: {
|
||||
return <loop.conversation.GenericFailureView
|
||||
cancelCall={this.closeWindow}
|
||||
/>;
|
||||
}
|
||||
case ROOM_STATES.INIT:
|
||||
case ROOM_STATES.GATHER:
|
||||
case ROOM_STATES.READY:
|
||||
case ROOM_STATES.JOINED: {
|
||||
return <DesktopRoomInvitationView
|
||||
dispatcher={this.props.dispatcher}
|
||||
roomStore={this.props.roomStore}
|
||||
/>;
|
||||
}
|
||||
// XXX needs bug 1074686/1074702
|
||||
case ROOM_STATES.HAS_PARTICIPANTS: {
|
||||
return <DesktopRoomConversationView
|
||||
dispatcher={this.props.dispatcher}
|
||||
roomStore={this.props.roomStore}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Used to control publishing a stream - i.e. to mute a stream
|
||||
*
|
||||
* @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({
|
||||
type: type,
|
||||
enabled: enabled
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.roomName) {
|
||||
this.setTitle(this.state.roomName);
|
||||
}
|
||||
return (
|
||||
<div className="room-conversation-wrapper">{
|
||||
this._renderRoomView(this.state.roomState)
|
||||
}</div>
|
||||
);
|
||||
|
||||
var localStreamClasses = React.addons.classSet({
|
||||
local: true,
|
||||
"local-stream": true,
|
||||
"local-stream-audio": !this.state.videoMuted
|
||||
});
|
||||
|
||||
switch(this.state.roomState) {
|
||||
case ROOM_STATES.FAILED: {
|
||||
return <loop.conversation.GenericFailureView
|
||||
cancelCall={this.closeWindow}
|
||||
/>;
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<div className="room-conversation-wrapper">
|
||||
{this._renderInvitationOverlay()}
|
||||
<div className="video-layout-wrapper">
|
||||
<div className="conversation room-conversation">
|
||||
<div className="media nested">
|
||||
<div className="video_wrapper remote_wrapper">
|
||||
<div className="video_inner remote"></div>
|
||||
</div>
|
||||
<div className={localStreamClasses}></div>
|
||||
</div>
|
||||
<sharedViews.ConversationToolbar
|
||||
video={{enabled: !this.state.videoMuted, visible: true}}
|
||||
audio={{enabled: !this.state.audioMuted, visible: true}}
|
||||
publishStream={this.publishStream}
|
||||
hangup={noop} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
ActiveRoomStoreMixin: ActiveRoomStoreMixin,
|
||||
DesktopRoomControllerView: DesktopRoomControllerView,
|
||||
DesktopRoomConversationView: DesktopRoomConversationView,
|
||||
DesktopRoomInvitationView: DesktopRoomInvitationView
|
||||
};
|
||||
|
@ -2,6 +2,16 @@
|
||||
* 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/. */
|
||||
|
||||
.contact-import-spinner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.contact-import-spinner.busy {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
-moz-margin-start: 10px;
|
||||
}
|
||||
|
||||
.content-area input.contact-filter {
|
||||
margin-top: 14px;
|
||||
border-radius: 10000px;
|
||||
|
@ -46,7 +46,7 @@ body {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
-moz-border-end: 1px solid #ccc;
|
||||
padding: 0 10px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
@ -56,7 +56,7 @@ body {
|
||||
}
|
||||
|
||||
.tab-view > li:last-child {
|
||||
border-right-style: none;
|
||||
-moz-border-end-style: none;
|
||||
}
|
||||
|
||||
.tab-view > li[data-tab-name="call"],
|
||||
@ -307,6 +307,10 @@ body {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.button > .button-caption {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #ebebeb;
|
||||
}
|
||||
@ -372,10 +376,41 @@ body[dir=rtl] .dropdown-menu-item {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
/* Spinner */
|
||||
|
||||
@keyframes spinnerRotate {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
.spinner.busy {
|
||||
background-image: url(../img/spinner.png);
|
||||
animation-name: spinnerRotate;
|
||||
animation-duration: 1s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
.spinner.busy {
|
||||
background-image: url(../img/spinner@2x.png);
|
||||
}
|
||||
}
|
||||
|
||||
/* Share tab */
|
||||
|
||||
.generate-url input {
|
||||
.generate-url-stack {
|
||||
margin: 14px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.generate-url-input {
|
||||
outline: 0;
|
||||
border: 1px solid #ccc; /* Overriding background style for a text input (see
|
||||
below) resets its borders to a weird beveled style;
|
||||
@ -386,10 +421,18 @@ body[dir=rtl] .dropdown-menu-item {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.generate-url input.pending {
|
||||
background-image: url(../img/loading-icon.gif);
|
||||
background-repeat: no-repeat;
|
||||
background-position: right;
|
||||
.generate-url-spinner {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
top: 4px;
|
||||
left: auto;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
body[dir=rtl] .generate-url-spinner {
|
||||
left: 4px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.generate-url .button {
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB |
BIN
browser/components/loop/content/shared/img/spinner.png
Normal file
BIN
browser/components/loop/content/shared/img/spinner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 724 B |
BIN
browser/components/loop/content/shared/img/spinner@2x.png
Normal file
BIN
browser/components/loop/content/shared/img/spinner@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
@ -109,9 +109,12 @@ loop.shared.actions = (function() {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used to indicate the peer hung up the call.
|
||||
* Used to indicate the remote peer was disconnected for some reason.
|
||||
*
|
||||
* peerHungup is true if the peer intentionally disconnected, false otherwise.
|
||||
*/
|
||||
PeerHungupCall: Action.define("peerHungupCall", {
|
||||
RemotePeerDisconnected: Action.define("remotePeerDisconnected", {
|
||||
peerHungup: Boolean
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -132,6 +135,18 @@ loop.shared.actions = (function() {
|
||||
reason: String
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used to notify that the sdk session is now connected to the servers.
|
||||
*/
|
||||
ConnectedToSdkServers: Action.define("connectedToSdkServers", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used to notify that a remote peer has connected to the room.
|
||||
*/
|
||||
RemotePeerConnected: Action.define("remotePeerConnected", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used by the ongoing views to notify stores about the elements
|
||||
* required for the sdk.
|
||||
|
@ -20,10 +20,12 @@ loop.store.ActiveRoomStore = (function() {
|
||||
READY: "room-ready",
|
||||
// The room is known to be joined on the loop-server
|
||||
JOINED: "room-joined",
|
||||
// The room is connected to the sdk server.
|
||||
SESSION_CONNECTED: "room-session-connected",
|
||||
// There are participants in the room.
|
||||
HAS_PARTICIPANTS: "room-has-participants",
|
||||
// There was an issue with the room
|
||||
FAILED: "room-failed",
|
||||
// XXX to be implemented in bug 1074686/1074702
|
||||
HAS_PARTICIPANTS: "room-has-participants"
|
||||
FAILED: "room-failed"
|
||||
};
|
||||
|
||||
/**
|
||||
@ -51,15 +53,18 @@ loop.store.ActiveRoomStore = (function() {
|
||||
}
|
||||
this._mozLoop = options.mozLoop;
|
||||
|
||||
if (!options.sdkDriver) {
|
||||
throw new Error("Missing option sdkDriver");
|
||||
}
|
||||
this._sdkDriver = options.sdkDriver;
|
||||
|
||||
// XXX Further actions are registered in setupWindowData and
|
||||
// fetchServerData when we know what window type this is. At some stage,
|
||||
// we might want to consider store mixins or some alternative which
|
||||
// means the stores would only be created when we want them.
|
||||
this._dispatcher.register(this, [
|
||||
"roomFailure",
|
||||
"setupWindowData",
|
||||
"fetchServerData",
|
||||
"updateRoomInfo",
|
||||
"joinRoom",
|
||||
"joinedRoom",
|
||||
"windowUnload",
|
||||
"leaveRoom"
|
||||
"fetchServerData"
|
||||
]);
|
||||
|
||||
/**
|
||||
@ -75,7 +80,9 @@ loop.store.ActiveRoomStore = (function() {
|
||||
* otherwise it will be unset.
|
||||
*/
|
||||
this._storeState = {
|
||||
roomState: ROOM_STATES.INIT
|
||||
roomState: ROOM_STATES.INIT,
|
||||
audioMuted: false,
|
||||
videoMuted: false
|
||||
};
|
||||
}
|
||||
|
||||
@ -113,6 +120,26 @@ loop.store.ActiveRoomStore = (function() {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers the actions with the dispatcher that this store is interested
|
||||
* in.
|
||||
*/
|
||||
_registerActions: function() {
|
||||
this._dispatcher.register(this, [
|
||||
"roomFailure",
|
||||
"updateRoomInfo",
|
||||
"joinRoom",
|
||||
"joinedRoom",
|
||||
"connectedToSdkServers",
|
||||
"connectionFailure",
|
||||
"setMute",
|
||||
"remotePeerDisconnected",
|
||||
"remotePeerConnected",
|
||||
"windowUnload",
|
||||
"leaveRoom"
|
||||
]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute setupWindowData event action from the dispatcher. This gets
|
||||
* the room data from the mozLoop api, and dispatches an UpdateRoomInfo event.
|
||||
@ -127,6 +154,8 @@ loop.store.ActiveRoomStore = (function() {
|
||||
return;
|
||||
}
|
||||
|
||||
this._registerActions();
|
||||
|
||||
this.setStoreState({
|
||||
roomState: ROOM_STATES.GATHER
|
||||
});
|
||||
@ -169,6 +198,8 @@ loop.store.ActiveRoomStore = (function() {
|
||||
return;
|
||||
}
|
||||
|
||||
this._registerActions();
|
||||
|
||||
this.setStoreState({
|
||||
roomToken: actionData.token,
|
||||
roomState: ROOM_STATES.READY
|
||||
@ -228,6 +259,57 @@ loop.store.ActiveRoomStore = (function() {
|
||||
});
|
||||
|
||||
this._setRefreshTimeout(actionData.expires);
|
||||
this._sdkDriver.connectSession(actionData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles recording when the sdk has connected to the servers.
|
||||
*/
|
||||
connectedToSdkServers: function() {
|
||||
this.setStoreState({
|
||||
roomState: ROOM_STATES.SESSION_CONNECTED
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles disconnection of this local client from the sdk servers.
|
||||
*/
|
||||
connectionFailure: function() {
|
||||
// Treat all reasons as something failed. In theory, clientDisconnected
|
||||
// could be a success case, but there's no way we should be intentionally
|
||||
// sending that and still have the window open.
|
||||
this._leaveRoom(ROOM_STATES.FAILED);
|
||||
},
|
||||
|
||||
/**
|
||||
* Records the mute state for the stream.
|
||||
*
|
||||
* @param {sharedActions.setMute} actionData The mute state for the stream type.
|
||||
*/
|
||||
setMute: function(actionData) {
|
||||
var muteState = {};
|
||||
muteState[actionData.type + "Muted"] = !actionData.enabled;
|
||||
this.setStoreState(muteState);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles recording when a remote peer has connected to the servers.
|
||||
*/
|
||||
remotePeerConnected: function() {
|
||||
this.setStoreState({
|
||||
roomState: ROOM_STATES.HAS_PARTICIPANTS
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles a remote peer disconnecting from the session.
|
||||
*/
|
||||
remotePeerDisconnected: function() {
|
||||
// As we only support two users at the moment, we just set this
|
||||
// back to joined.
|
||||
this.setStoreState({
|
||||
roomState: ROOM_STATES.SESSION_CONNECTED
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -275,22 +357,27 @@ loop.store.ActiveRoomStore = (function() {
|
||||
/**
|
||||
* Handles leaving a room. Clears any membership timeouts, then
|
||||
* signals to the server the leave of the room.
|
||||
*
|
||||
* @param {ROOM_STATES} nextState Optional; the next state to switch to.
|
||||
* Switches to READY if undefined.
|
||||
*/
|
||||
_leaveRoom: function() {
|
||||
if (this._storeState.roomState !== ROOM_STATES.JOINED) {
|
||||
return;
|
||||
}
|
||||
_leaveRoom: function(nextState) {
|
||||
this._sdkDriver.disconnectSession();
|
||||
|
||||
if (this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
delete this._timeout;
|
||||
}
|
||||
|
||||
this._mozLoop.rooms.leave(this._storeState.roomToken,
|
||||
this._storeState.sessionToken);
|
||||
if (this._storeState.roomState === ROOM_STATES.JOINED ||
|
||||
this._storeState.roomState === ROOM_STATES.SESSION_CONNECTED ||
|
||||
this._storeState.roomState === ROOM_STATES.HAS_PARTICIPANTS) {
|
||||
this._mozLoop.rooms.leave(this._storeState.roomToken,
|
||||
this._storeState.sessionToken);
|
||||
}
|
||||
|
||||
this.setStoreState({
|
||||
roomState: ROOM_STATES.READY
|
||||
roomState: nextState ? nextState : ROOM_STATES.READY
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -118,18 +118,12 @@ loop.store.ConversationStore = (function() {
|
||||
this.dispatcher = options.dispatcher;
|
||||
this.sdkDriver = options.sdkDriver;
|
||||
|
||||
// XXX Further actions are registered in setupWindowData when
|
||||
// we know what window type this is. At some stage, we might want to
|
||||
// consider store mixins or some alternative which means the stores
|
||||
// would only be created when we want them.
|
||||
this.dispatcher.register(this, [
|
||||
"connectionFailure",
|
||||
"connectionProgress",
|
||||
"setupWindowData",
|
||||
"connectCall",
|
||||
"hangupCall",
|
||||
"peerHungupCall",
|
||||
"cancelCall",
|
||||
"retryCall",
|
||||
"mediaConnected",
|
||||
"setMute",
|
||||
"fetchEmailLink"
|
||||
"setupWindowData"
|
||||
]);
|
||||
},
|
||||
|
||||
@ -196,6 +190,19 @@ loop.store.ConversationStore = (function() {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dispatcher.register(this, [
|
||||
"connectionFailure",
|
||||
"connectionProgress",
|
||||
"connectCall",
|
||||
"hangupCall",
|
||||
"remotePeerDisconnected",
|
||||
"cancelCall",
|
||||
"retryCall",
|
||||
"mediaConnected",
|
||||
"setMute",
|
||||
"fetchEmailLink"
|
||||
]);
|
||||
|
||||
this.set({
|
||||
contact: actionData.contact,
|
||||
outgoing: windowType === "outgoing",
|
||||
@ -236,11 +243,23 @@ loop.store.ConversationStore = (function() {
|
||||
},
|
||||
|
||||
/**
|
||||
* The peer hungup the call.
|
||||
* The remote peer disconnected from the session.
|
||||
*
|
||||
* @param {sharedActions.RemotePeerDisconnected} actionData
|
||||
*/
|
||||
peerHungupCall: function() {
|
||||
remotePeerDisconnected: function(actionData) {
|
||||
this._endSession();
|
||||
this.set({callState: CALL_STATES.FINISHED});
|
||||
|
||||
// If the peer hungup, we end normally, otherwise
|
||||
// we treat this as a call failure.
|
||||
if (actionData.peerHungup) {
|
||||
this.set({callState: CALL_STATES.FINISHED});
|
||||
} else {
|
||||
this.set({
|
||||
callState: CALL_STATES.TERMINATED,
|
||||
callStateReason: "peerNetworkDisconnected"
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -79,6 +79,7 @@ loop.OTSdkDriver = (function() {
|
||||
connectSession: function(sessionData) {
|
||||
this.session = this.sdk.initSession(sessionData.sessionId);
|
||||
|
||||
this.session.on("connectionCreated", this._onConnectionCreated.bind(this));
|
||||
this.session.on("streamCreated", this._onRemoteStreamCreated.bind(this));
|
||||
this.session.on("connectionDestroyed",
|
||||
this._onConnectionDestroyed.bind(this));
|
||||
@ -130,6 +131,7 @@ loop.OTSdkDriver = (function() {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectedToSdkServers());
|
||||
this._sessionConnected = true;
|
||||
this._maybePublishLocalStream();
|
||||
},
|
||||
@ -141,18 +143,9 @@ loop.OTSdkDriver = (function() {
|
||||
* https://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html
|
||||
*/
|
||||
_onConnectionDestroyed: function(event) {
|
||||
var action;
|
||||
if (event.reason === "clientDisconnected") {
|
||||
action = new sharedActions.PeerHungupCall();
|
||||
} else {
|
||||
// Strictly speaking this isn't a failure on our part, but since our
|
||||
// flow requires a full reconnection, then we just treat this as
|
||||
// if a failure of our end had occurred.
|
||||
action = new sharedActions.ConnectionFailure({
|
||||
reason: "peerNetworkDisconnected"
|
||||
});
|
||||
}
|
||||
this.dispatcher.dispatch(action);
|
||||
this.dispatcher.dispatch(new sharedActions.RemotePeerDisconnected({
|
||||
peerHungup: event.reason === "clientDisconnected"
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -171,6 +164,14 @@ loop.OTSdkDriver = (function() {
|
||||
}
|
||||
},
|
||||
|
||||
_onConnectionCreated: function(event) {
|
||||
if (this.session.connection.id === event.connection.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dispatcher.dispatch(new sharedActions.RemotePeerConnected());
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the event when the remote stream is created.
|
||||
*
|
||||
|
@ -728,7 +728,8 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
React.DOM.button({onClick: this.props.onClick,
|
||||
disabled: this.props.disabled,
|
||||
className: cx(classObject)},
|
||||
this.props.caption
|
||||
React.DOM.span({className: "button-caption"}, this.props.caption),
|
||||
this.props.children
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -728,7 +728,8 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
<button onClick={this.props.onClick}
|
||||
disabled={this.props.disabled}
|
||||
className={cx(classObject)}>
|
||||
{this.props.caption}
|
||||
<span className="button-caption">{this.props.caption}</span>
|
||||
{this.props.children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ browser.jar:
|
||||
content/browser/loop/shared/img/sad.png (content/shared/img/sad.png)
|
||||
content/browser/loop/shared/img/icon_32.png (content/shared/img/icon_32.png)
|
||||
content/browser/loop/shared/img/icon_64.png (content/shared/img/icon_64.png)
|
||||
content/browser/loop/shared/img/loading-icon.gif (content/shared/img/loading-icon.gif)
|
||||
content/browser/loop/shared/img/spinner.png (content/shared/img/spinner.png)
|
||||
content/browser/loop/shared/img/spinner@2x.png (content/shared/img/spinner@2x.png)
|
||||
content/browser/loop/shared/img/audio-inverse-14x14.png (content/shared/img/audio-inverse-14x14.png)
|
||||
content/browser/loop/shared/img/audio-inverse-14x14@2x.png (content/shared/img/audio-inverse-14x14@2x.png)
|
||||
content/browser/loop/shared/img/facemute-14x14.png (content/shared/img/facemute-14x14.png)
|
||||
|
@ -96,6 +96,7 @@
|
||||
<script type="text/javascript" src="shared/js/validate.js"></script>
|
||||
<script type="text/javascript" src="shared/js/dispatcher.js"></script>
|
||||
<script type="text/javascript" src="shared/js/websocket.js"></script>
|
||||
<script type="text/javascript" src="shared/js/otSdkDriver.js"></script>
|
||||
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
|
||||
<script type="text/javascript" src="js/standaloneAppStore.js"></script>
|
||||
<script type="text/javascript" src="js/standaloneClient.js"></script>
|
||||
|
@ -987,6 +987,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
var client = new loop.StandaloneClient({
|
||||
baseServerUrl: loop.config.serverUrl
|
||||
});
|
||||
var sdkDriver = new loop.OTSdkDriver({
|
||||
dispatcher: dispatcher,
|
||||
sdk: OT
|
||||
});
|
||||
|
||||
var standaloneAppStore = new loop.store.StandaloneAppStore({
|
||||
conversation: conversation,
|
||||
@ -996,7 +1000,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
});
|
||||
var activeRoomStore = new loop.store.ActiveRoomStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: standaloneMozLoop
|
||||
mozLoop: standaloneMozLoop,
|
||||
sdkDriver: sdkDriver
|
||||
});
|
||||
|
||||
window.addEventListener("unload", function() {
|
||||
|
@ -987,6 +987,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
var client = new loop.StandaloneClient({
|
||||
baseServerUrl: loop.config.serverUrl
|
||||
});
|
||||
var sdkDriver = new loop.OTSdkDriver({
|
||||
dispatcher: dispatcher,
|
||||
sdk: OT
|
||||
});
|
||||
|
||||
var standaloneAppStore = new loop.store.StandaloneAppStore({
|
||||
conversation: conversation,
|
||||
@ -996,7 +1000,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
});
|
||||
var activeRoomStore = new loop.store.ActiveRoomStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: standaloneMozLoop
|
||||
mozLoop: standaloneMozLoop,
|
||||
sdkDriver: sdkDriver
|
||||
});
|
||||
|
||||
window.addEventListener("unload", function() {
|
||||
|
@ -143,7 +143,8 @@ describe("loop.conversation", function() {
|
||||
roomStore: roomStore,
|
||||
sdk: {},
|
||||
conversationStore: conversationStore,
|
||||
conversationAppStore: conversationAppStore
|
||||
conversationAppStore: conversationAppStore,
|
||||
dispatcher: dispatcher
|
||||
}));
|
||||
}
|
||||
|
||||
@ -214,7 +215,7 @@ describe("loop.conversation", function() {
|
||||
ccView = mountTestComponent();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(ccView,
|
||||
loop.roomViews.DesktopRoomControllerView);
|
||||
loop.roomViews.DesktopRoomConversationView);
|
||||
});
|
||||
|
||||
it("should display the GenericFailureView for failures", function() {
|
||||
|
@ -32,7 +32,8 @@ describe("loop.roomViews", function () {
|
||||
|
||||
activeRoomStore = new loop.store.ActiveRoomStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: {}
|
||||
mozLoop: {},
|
||||
sdkDriver: {}
|
||||
});
|
||||
roomStore = new loop.store.RoomStore({
|
||||
dispatcher: dispatcher,
|
||||
@ -62,6 +63,8 @@ describe("loop.roomViews", function () {
|
||||
|
||||
expect(testView.state).eql({
|
||||
roomState: ROOM_STATES.INIT,
|
||||
audioMuted: false,
|
||||
videoMuted: false,
|
||||
foo: "bar"
|
||||
});
|
||||
});
|
||||
@ -77,21 +80,90 @@ describe("loop.roomViews", function () {
|
||||
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
|
||||
|
||||
expect(testView.state).eql({roomState: ROOM_STATES.READY});
|
||||
expect(testView.state.roomState).eql(ROOM_STATES.READY);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DesktopRoomControllerView", function() {
|
||||
describe("DesktopRoomConversationView", function() {
|
||||
var view;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
});
|
||||
|
||||
function mountTestComponent() {
|
||||
return TestUtils.renderIntoDocument(
|
||||
new loop.roomViews.DesktopRoomControllerView({
|
||||
mozLoop: {},
|
||||
new loop.roomViews.DesktopRoomConversationView({
|
||||
dispatcher: dispatcher,
|
||||
roomStore: roomStore
|
||||
}));
|
||||
}
|
||||
|
||||
it("should dispatch a setupStreamElements action when the view is created",
|
||||
function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "setupStreamElements"));
|
||||
});
|
||||
|
||||
it("should dispatch a setMute action when the audio mute button is pressed",
|
||||
function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
view.setState({audioMuted: true});
|
||||
|
||||
var muteBtn = view.getDOMNode().querySelector('.btn-mute-audio');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(muteBtn);
|
||||
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "setMute"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("enabled", true));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("type", "audio"));
|
||||
});
|
||||
|
||||
it("should dispatch a setMute action when the video mute button is pressed",
|
||||
function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
view.setState({videoMuted: false});
|
||||
|
||||
var muteBtn = view.getDOMNode().querySelector('.btn-mute-video');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(muteBtn);
|
||||
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "setMute"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("enabled", false));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("type", "video"));
|
||||
});
|
||||
|
||||
it("should set the mute button as mute off", function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
view.setState({videoMuted: false});
|
||||
|
||||
var muteBtn = view.getDOMNode().querySelector('.btn-mute-video');
|
||||
|
||||
expect(muteBtn.classList.contains("muted")).eql(false);
|
||||
});
|
||||
|
||||
it("should set the mute button as mute on", function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
view.setState({audioMuted: true});
|
||||
|
||||
var muteBtn = view.getDOMNode().querySelector('.btn-mute-audio');
|
||||
|
||||
expect(muteBtn.classList.contains("muted")).eql(true);
|
||||
});
|
||||
|
||||
describe("#render", function() {
|
||||
it("should set document.title to store.serverData.roomName", function() {
|
||||
mountTestComponent();
|
||||
|
@ -7,7 +7,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
"use strict";
|
||||
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var sandbox, dispatcher, store, fakeMozLoop;
|
||||
var sandbox, dispatcher, store, fakeMozLoop, fakeSdkDriver;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@ -25,8 +25,16 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
}
|
||||
};
|
||||
|
||||
store = new loop.store.ActiveRoomStore(
|
||||
{mozLoop: fakeMozLoop, dispatcher: dispatcher});
|
||||
fakeSdkDriver = {
|
||||
connectSession: sandbox.stub(),
|
||||
disconnectSession: sandbox.stub()
|
||||
};
|
||||
|
||||
store = new loop.store.ActiveRoomStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: fakeMozLoop,
|
||||
sdkDriver: fakeSdkDriver
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@ -45,6 +53,12 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
new loop.store.ActiveRoomStore({dispatcher: dispatcher});
|
||||
}).to.Throw(/mozLoop/);
|
||||
});
|
||||
|
||||
it("should throw an error if sdkDriver is missing", function() {
|
||||
expect(function() {
|
||||
new loop.store.ActiveRoomStore({dispatcher: dispatcher, mozLoop: {}});
|
||||
}).to.Throw(/sdkDriver/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#roomFailure", function() {
|
||||
@ -281,6 +295,16 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
expect(state.sessionId).eql(fakeJoinedData.sessionId);
|
||||
});
|
||||
|
||||
it("should start the session connection with the sdk", function() {
|
||||
var actionData = new sharedActions.JoinedRoom(fakeJoinedData);
|
||||
|
||||
store.joinedRoom(actionData);
|
||||
|
||||
sinon.assert.calledOnce(fakeSdkDriver.connectSession);
|
||||
sinon.assert.calledWithExactly(fakeSdkDriver.connectSession,
|
||||
actionData);
|
||||
});
|
||||
|
||||
it("should call mozLoop.rooms.refreshMembership before the expiresTime",
|
||||
function() {
|
||||
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
|
||||
@ -330,6 +354,93 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#connectedToSdkServers", function() {
|
||||
it("should set the state to `SESSION_CONNECTED`", function() {
|
||||
store.connectedToSdkServers(new sharedActions.ConnectedToSdkServers());
|
||||
|
||||
expect(store.getStoreState().roomState).eql(ROOM_STATES.SESSION_CONNECTED);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#connectionFailure", function() {
|
||||
beforeEach(function() {
|
||||
store.setStoreState({
|
||||
roomState: ROOM_STATES.JOINED,
|
||||
roomToken: "fakeToken",
|
||||
sessionToken: "1627384950"
|
||||
});
|
||||
});
|
||||
|
||||
it("should disconnect from the servers via the sdk", function() {
|
||||
store.connectionFailure();
|
||||
|
||||
sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
|
||||
});
|
||||
|
||||
it("should clear any existing timeout", function() {
|
||||
sandbox.stub(window, "clearTimeout");
|
||||
store._timeout = {};
|
||||
|
||||
store.connectionFailure();
|
||||
|
||||
sinon.assert.calledOnce(clearTimeout);
|
||||
});
|
||||
|
||||
it("should call mozLoop.rooms.leave", function() {
|
||||
store.connectionFailure();
|
||||
|
||||
sinon.assert.calledOnce(fakeMozLoop.rooms.leave);
|
||||
sinon.assert.calledWithExactly(fakeMozLoop.rooms.leave,
|
||||
"fakeToken", "1627384950");
|
||||
});
|
||||
|
||||
it("should set the state to `FAILED`", function() {
|
||||
store.connectionFailure();
|
||||
|
||||
expect(store.getStoreState().roomState).eql(ROOM_STATES.FAILED);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setMute", function() {
|
||||
it("should save the mute state for the audio stream", function() {
|
||||
store.setStoreState({audioMuted: false});
|
||||
|
||||
store.setMute(new sharedActions.SetMute({
|
||||
type: "audio",
|
||||
enabled: true
|
||||
}));
|
||||
|
||||
expect(store.getStoreState().audioMuted).eql(false);
|
||||
});
|
||||
|
||||
it("should save the mute state for the video stream", function() {
|
||||
store.setStoreState({videoMuted: true});
|
||||
|
||||
store.setMute(new sharedActions.SetMute({
|
||||
type: "video",
|
||||
enabled: false
|
||||
}));
|
||||
|
||||
expect(store.getStoreState().videoMuted).eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#remotePeerConnected", function() {
|
||||
it("should set the state to `HAS_PARTICIPANTS`", function() {
|
||||
store.remotePeerConnected();
|
||||
|
||||
expect(store.getStoreState().roomState).eql(ROOM_STATES.HAS_PARTICIPANTS);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#remotePeerDisconnected", function() {
|
||||
it("should set the state to `SESSION_CONNECTED`", function() {
|
||||
store.remotePeerDisconnected();
|
||||
|
||||
expect(store.getStoreState().roomState).eql(ROOM_STATES.SESSION_CONNECTED);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#windowUnload", function() {
|
||||
beforeEach(function() {
|
||||
store.setStoreState({
|
||||
@ -339,6 +450,12 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it("should disconnect from the servers via the sdk", function() {
|
||||
store.windowUnload();
|
||||
|
||||
sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
|
||||
});
|
||||
|
||||
it("should clear any existing timeout", function() {
|
||||
sandbox.stub(window, "clearTimeout");
|
||||
store._timeout = {};
|
||||
@ -372,6 +489,12 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it("should disconnect from the servers via the sdk", function() {
|
||||
store.leaveRoom();
|
||||
|
||||
sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
|
||||
});
|
||||
|
||||
it("should clear any existing timeout", function() {
|
||||
sandbox.stub(window, "clearTimeout");
|
||||
store._timeout = {};
|
||||
|
@ -132,14 +132,14 @@ describe("loop.store.ConversationStore", function () {
|
||||
});
|
||||
|
||||
it("should disconnect the session", function() {
|
||||
dispatcher.dispatch(
|
||||
store.connectionFailure(
|
||||
new sharedActions.ConnectionFailure({reason: "fake"}));
|
||||
|
||||
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||
});
|
||||
|
||||
it("should ensure the websocket is closed", function() {
|
||||
dispatcher.dispatch(
|
||||
store.connectionFailure(
|
||||
new sharedActions.ConnectionFailure({reason: "fake"}));
|
||||
|
||||
sinon.assert.calledOnce(wsCloseSpy);
|
||||
@ -148,7 +148,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
it("should set the state to 'terminated'", function() {
|
||||
store.set({callState: CALL_STATES.ALERTING});
|
||||
|
||||
dispatcher.dispatch(
|
||||
store.connectionFailure(
|
||||
new sharedActions.ConnectionFailure({reason: "fake"}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.TERMINATED);
|
||||
@ -156,7 +156,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
});
|
||||
|
||||
it("should release mozLoop callsData", function() {
|
||||
dispatcher.dispatch(
|
||||
store.connectionFailure(
|
||||
new sharedActions.ConnectionFailure({reason: "fake"}));
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
|
||||
@ -170,7 +170,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
it("should change the state from 'gather' to 'connecting'", function() {
|
||||
store.set({callState: CALL_STATES.GATHER});
|
||||
|
||||
dispatcher.dispatch(
|
||||
store.connectionProgress(
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.INIT}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.CONNECTING);
|
||||
@ -181,7 +181,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
it("should change the state from 'gather' to 'alerting'", function() {
|
||||
store.set({callState: CALL_STATES.GATHER});
|
||||
|
||||
dispatcher.dispatch(
|
||||
store.connectionProgress(
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.ALERTING);
|
||||
@ -190,7 +190,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
it("should change the state from 'init' to 'alerting'", function() {
|
||||
store.set({callState: CALL_STATES.INIT});
|
||||
|
||||
dispatcher.dispatch(
|
||||
store.connectionProgress(
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.ALERTING);
|
||||
@ -203,7 +203,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
});
|
||||
|
||||
it("should change the state to 'ongoing'", function() {
|
||||
dispatcher.dispatch(
|
||||
store.connectionProgress(
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
||||
@ -212,7 +212,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
it("should connect the session", function() {
|
||||
store.set(fakeSessionData);
|
||||
|
||||
dispatcher.dispatch(
|
||||
store.connectionProgress(
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
||||
|
||||
sinon.assert.calledOnce(sdkDriver.connectSession);
|
||||
@ -386,7 +386,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
|
||||
describe("#connectCall", function() {
|
||||
it("should save the call session data", function() {
|
||||
dispatcher.dispatch(
|
||||
store.connectCall(
|
||||
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
||||
|
||||
expect(store.get("apiKey")).eql("fakeKey");
|
||||
@ -403,7 +403,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
on: sinon.spy()
|
||||
});
|
||||
|
||||
dispatcher.dispatch(
|
||||
store.connectCall(
|
||||
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
||||
|
||||
sinon.assert.calledOnce(loop.CallConnectionWebSocket);
|
||||
@ -415,7 +415,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
});
|
||||
|
||||
it("should connect the websocket to the server", function() {
|
||||
dispatcher.dispatch(
|
||||
store.connectCall(
|
||||
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
||||
|
||||
sinon.assert.calledOnce(store._websocket.promiseConnect);
|
||||
@ -423,7 +423,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
|
||||
describe("WebSocket connection result", function() {
|
||||
beforeEach(function() {
|
||||
dispatcher.dispatch(
|
||||
store.connectCall(
|
||||
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
||||
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
@ -480,31 +480,31 @@ describe("loop.store.ConversationStore", function () {
|
||||
});
|
||||
|
||||
it("should disconnect the session", function() {
|
||||
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||
store.hangupCall(new sharedActions.HangupCall());
|
||||
|
||||
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||
});
|
||||
|
||||
it("should send a media-fail message to the websocket if it is open", function() {
|
||||
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||
store.hangupCall(new sharedActions.HangupCall());
|
||||
|
||||
sinon.assert.calledOnce(wsMediaFailSpy);
|
||||
});
|
||||
|
||||
it("should ensure the websocket is closed", function() {
|
||||
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||
store.hangupCall(new sharedActions.HangupCall());
|
||||
|
||||
sinon.assert.calledOnce(wsCloseSpy);
|
||||
});
|
||||
|
||||
it("should set the callState to finished", function() {
|
||||
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||
store.hangupCall(new sharedActions.HangupCall());
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.FINISHED);
|
||||
});
|
||||
|
||||
it("should release mozLoop callsData", function() {
|
||||
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||
store.hangupCall(new sharedActions.HangupCall());
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
|
||||
sinon.assert.calledWithExactly(
|
||||
@ -512,7 +512,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#peerHungupCall", function() {
|
||||
describe("#remotePeerDisconnected", function() {
|
||||
var wsMediaFailSpy, wsCloseSpy;
|
||||
beforeEach(function() {
|
||||
wsMediaFailSpy = sinon.spy();
|
||||
@ -527,30 +527,56 @@ describe("loop.store.ConversationStore", function () {
|
||||
});
|
||||
|
||||
it("should disconnect the session", function() {
|
||||
dispatcher.dispatch(new sharedActions.PeerHungupCall());
|
||||
store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
|
||||
peerHungup: true
|
||||
}));
|
||||
|
||||
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||
});
|
||||
|
||||
it("should ensure the websocket is closed", function() {
|
||||
dispatcher.dispatch(new sharedActions.PeerHungupCall());
|
||||
store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
|
||||
peerHungup: true
|
||||
}));
|
||||
|
||||
sinon.assert.calledOnce(wsCloseSpy);
|
||||
});
|
||||
|
||||
it("should set the callState to finished", function() {
|
||||
dispatcher.dispatch(new sharedActions.PeerHungupCall());
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.FINISHED);
|
||||
});
|
||||
|
||||
it("should release mozLoop callsData", function() {
|
||||
dispatcher.dispatch(new sharedActions.PeerHungupCall());
|
||||
store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
|
||||
peerHungup: true
|
||||
}));
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
|
||||
sinon.assert.calledWithExactly(
|
||||
navigator.mozLoop.calls.clearCallInProgress, "42");
|
||||
});
|
||||
|
||||
it("should set the callState to finished if the peer hungup", function() {
|
||||
store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
|
||||
peerHungup: true
|
||||
}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.FINISHED);
|
||||
});
|
||||
|
||||
it("should set the callState to terminated if the peer was disconnected" +
|
||||
"for an unintentional reason", function() {
|
||||
store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
|
||||
peerHungup: false
|
||||
}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.TERMINATED);
|
||||
});
|
||||
|
||||
it("should set the reason to peerNetworkDisconnected if the peer was" +
|
||||
"disconnected for an unintentional reason", function() {
|
||||
store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
|
||||
peerHungup: false
|
||||
}));
|
||||
|
||||
expect(store.get("callStateReason")).eql("peerNetworkDisconnected");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#cancelCall", function() {
|
||||
@ -562,25 +588,25 @@ describe("loop.store.ConversationStore", function () {
|
||||
});
|
||||
|
||||
it("should disconnect the session", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
store.cancelCall(new sharedActions.CancelCall());
|
||||
|
||||
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||
});
|
||||
|
||||
it("should send a cancel message to the websocket if it is open", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
store.cancelCall(new sharedActions.CancelCall());
|
||||
|
||||
sinon.assert.calledOnce(wsCancelSpy);
|
||||
});
|
||||
|
||||
it("should ensure the websocket is closed", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
store.cancelCall(new sharedActions.CancelCall());
|
||||
|
||||
sinon.assert.calledOnce(wsCloseSpy);
|
||||
});
|
||||
|
||||
it("should set the state to close if the call is connecting", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
store.cancelCall(new sharedActions.CancelCall());
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
||||
});
|
||||
@ -588,13 +614,13 @@ describe("loop.store.ConversationStore", function () {
|
||||
it("should set the state to close if the call has terminated already", function() {
|
||||
store.set({callState: CALL_STATES.TERMINATED});
|
||||
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
store.cancelCall(new sharedActions.CancelCall());
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
||||
});
|
||||
|
||||
it("should release mozLoop callsData", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
store.cancelCall(new sharedActions.CancelCall());
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
|
||||
sinon.assert.calledWithExactly(
|
||||
@ -606,7 +632,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
it("should set the state to gather", function() {
|
||||
store.set({callState: CALL_STATES.TERMINATED});
|
||||
|
||||
dispatcher.dispatch(new sharedActions.RetryCall());
|
||||
store.retryCall(new sharedActions.RetryCall());
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.GATHER);
|
||||
});
|
||||
@ -619,7 +645,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
contact: contact
|
||||
});
|
||||
|
||||
dispatcher.dispatch(new sharedActions.RetryCall());
|
||||
store.retryCall(new sharedActions.RetryCall());
|
||||
|
||||
sinon.assert.calledOnce(client.setupOutgoingCall);
|
||||
sinon.assert.calledWith(client.setupOutgoingCall,
|
||||
@ -631,7 +657,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
it("should send mediaUp via the websocket", function() {
|
||||
store._websocket = fakeWebsocket;
|
||||
|
||||
dispatcher.dispatch(new sharedActions.MediaConnected());
|
||||
store.mediaConnected(new sharedActions.MediaConnected());
|
||||
|
||||
sinon.assert.calledOnce(wsMediaUpSpy);
|
||||
});
|
||||
@ -663,7 +689,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
|
||||
describe("#fetchEmailLink", function() {
|
||||
it("should request a new call url to the server", function() {
|
||||
dispatcher.dispatch(new sharedActions.FetchEmailLink());
|
||||
store.fetchEmailLink(new sharedActions.FetchEmailLink());
|
||||
|
||||
sinon.assert.calledOnce(client.requestCallUrl);
|
||||
sinon.assert.calledWith(client.requestCallUrl, "");
|
||||
@ -674,7 +700,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
client.requestCallUrl = function(callId, cb) {
|
||||
cb(null, {callUrl: "http://fake.invalid/"});
|
||||
};
|
||||
dispatcher.dispatch(new sharedActions.FetchEmailLink());
|
||||
store.fetchEmailLink(new sharedActions.FetchEmailLink());
|
||||
|
||||
expect(store.get("emailLink")).eql("http://fake.invalid/");
|
||||
});
|
||||
@ -685,7 +711,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
client.requestCallUrl = function(callId, cb) {
|
||||
cb("error");
|
||||
};
|
||||
dispatcher.dispatch(new sharedActions.FetchEmailLink());
|
||||
store.fetchEmailLink(new sharedActions.FetchEmailLink());
|
||||
|
||||
sinon.assert.calledOnce(trigger);
|
||||
sinon.assert.calledWithExactly(trigger, "error:emailLink");
|
||||
@ -695,7 +721,7 @@ describe("loop.store.ConversationStore", function () {
|
||||
describe("Events", function() {
|
||||
describe("Websocket progress", function() {
|
||||
beforeEach(function() {
|
||||
dispatcher.dispatch(
|
||||
store.connectCall(
|
||||
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
||||
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
|
@ -231,26 +231,30 @@ describe("loop.OTSdkDriver", function () {
|
||||
});
|
||||
|
||||
describe("connectionDestroyed", function() {
|
||||
it("should dispatch a peerHungupCall action if the client disconnected", function() {
|
||||
session.trigger("connectionDestroyed", {
|
||||
reason: "clientDisconnected"
|
||||
it("should dispatch a remotePeerDisconnected action if the client" +
|
||||
"disconnected", function() {
|
||||
session.trigger("connectionDestroyed", {
|
||||
reason: "clientDisconnected"
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "remotePeerDisconnected"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("peerHungup", true));
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "peerHungupCall"));
|
||||
});
|
||||
it("should dispatch a remotePeerDisconnected action if the connection" +
|
||||
"failed", function() {
|
||||
session.trigger("connectionDestroyed", {
|
||||
reason: "networkDisconnected"
|
||||
});
|
||||
|
||||
it("should dispatch a connectionFailure action if the connection failed", function() {
|
||||
session.trigger("connectionDestroyed", {
|
||||
reason: "networkDisconnected"
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "connectionFailure"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("reason", "peerNetworkDisconnected"));
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "remotePeerDisconnected"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("peerHungup", false));
|
||||
});
|
||||
});
|
||||
|
||||
@ -296,5 +300,33 @@ describe("loop.OTSdkDriver", function () {
|
||||
sinon.match.hasOwn("name", "mediaConnected"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("connectionCreated", function() {
|
||||
beforeEach(function() {
|
||||
session.connection = {
|
||||
id: "localUser"
|
||||
};
|
||||
});
|
||||
|
||||
it("should dispatch a RemotePeerConnected action if this is for a remote user",
|
||||
function() {
|
||||
session.trigger("connectionCreated", {
|
||||
connection: {id: "remoteUser"}
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.RemotePeerConnected());
|
||||
});
|
||||
|
||||
it("should not dispatch an action if this is for a local user",
|
||||
function() {
|
||||
session.trigger("connectionCreated", {
|
||||
connection: {id: "localUser"}
|
||||
});
|
||||
|
||||
sinon.assert.notCalled(dispatcher.dispatch);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -370,7 +370,8 @@ describe("loop.store.RoomStore", function () {
|
||||
beforeEach(function() {
|
||||
activeRoomStore = new loop.store.ActiveRoomStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: fakeMozLoop
|
||||
mozLoop: fakeMozLoop,
|
||||
sdkDriver: {}
|
||||
});
|
||||
store = new loop.store.RoomStore({
|
||||
dispatcher: dispatcher,
|
||||
|
@ -41,6 +41,7 @@
|
||||
<script src="../../content/shared/js/validate.js"></script>
|
||||
<script src="../../content/shared/js/dispatcher.js"></script>
|
||||
<script src="../../content/shared/js/activeRoomStore.js"></script>
|
||||
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
||||
<script src="../../standalone/content/js/multiplexGum.js"></script>
|
||||
<script src="../../standalone/content/js/standaloneAppStore.js"></script>
|
||||
<script src="../../standalone/content/js/standaloneClient.js"></script>
|
||||
|
@ -612,7 +612,8 @@ describe("loop.webapp", function() {
|
||||
dispatcher = new loop.Dispatcher();
|
||||
activeRoomStore = new loop.store.ActiveRoomStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: {}
|
||||
mozLoop: {},
|
||||
sdkDriver: {}
|
||||
});
|
||||
standaloneAppStore = new loop.store.StandaloneAppStore({
|
||||
dispatcher: dispatcher,
|
||||
|
@ -22,7 +22,6 @@
|
||||
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
|
||||
var CallFailedView = loop.conversationViews.CallFailedView;
|
||||
var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
|
||||
var DesktopRoomInvitationView = loop.roomViews.DesktopRoomInvitationView;
|
||||
|
||||
// 2. Standalone webapp
|
||||
var HomeView = loop.webapp.HomeView;
|
||||
@ -39,6 +38,9 @@
|
||||
var ConversationView = loop.shared.views.ConversationView;
|
||||
var FeedbackView = loop.shared.views.FeedbackView;
|
||||
|
||||
// Room constants
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
|
||||
// Local helpers
|
||||
function returnTrue() {
|
||||
return true;
|
||||
@ -61,7 +63,8 @@
|
||||
var dispatcher = new loop.Dispatcher();
|
||||
var activeRoomStore = new loop.store.ActiveRoomStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: navigator.mozLoop
|
||||
mozLoop: navigator.mozLoop,
|
||||
sdkDriver: {}
|
||||
});
|
||||
var roomStore = new loop.store.RoomStore({
|
||||
dispatcher: dispatcher,
|
||||
@ -533,20 +536,24 @@
|
||||
)
|
||||
),
|
||||
|
||||
Section({name: "DesktopRoomInvitationView"},
|
||||
Example({summary: "Desktop room invitation", dashed: "true",
|
||||
Section({name: "DesktopRoomConversationView"},
|
||||
Example({summary: "Desktop room conversation (invitation)", dashed: "true",
|
||||
style: {width: "260px", height: "265px"}},
|
||||
React.DOM.div({className: "fx-embedded"},
|
||||
DesktopRoomInvitationView({roomStore: roomStore})
|
||||
DesktopRoomConversationView({
|
||||
roomStore: roomStore,
|
||||
dispatcher: dispatcher,
|
||||
roomState: ROOM_STATES.INIT})
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
|
||||
Section({name: "DesktopRoomConversationView"},
|
||||
Example({summary: "Desktop room conversation", dashed: "true",
|
||||
style: {width: "260px", height: "265px"}},
|
||||
React.DOM.div({className: "fx-embedded"},
|
||||
DesktopRoomConversationView({roomStore: roomStore})
|
||||
DesktopRoomConversationView({
|
||||
roomStore: roomStore,
|
||||
dispatcher: dispatcher,
|
||||
roomState: ROOM_STATES.HAS_PARTICIPANTS})
|
||||
)
|
||||
)
|
||||
),
|
||||
|
@ -22,7 +22,6 @@
|
||||
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
|
||||
var CallFailedView = loop.conversationViews.CallFailedView;
|
||||
var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
|
||||
var DesktopRoomInvitationView = loop.roomViews.DesktopRoomInvitationView;
|
||||
|
||||
// 2. Standalone webapp
|
||||
var HomeView = loop.webapp.HomeView;
|
||||
@ -39,6 +38,9 @@
|
||||
var ConversationView = loop.shared.views.ConversationView;
|
||||
var FeedbackView = loop.shared.views.FeedbackView;
|
||||
|
||||
// Room constants
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
|
||||
// Local helpers
|
||||
function returnTrue() {
|
||||
return true;
|
||||
@ -61,7 +63,8 @@
|
||||
var dispatcher = new loop.Dispatcher();
|
||||
var activeRoomStore = new loop.store.ActiveRoomStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: navigator.mozLoop
|
||||
mozLoop: navigator.mozLoop,
|
||||
sdkDriver: {}
|
||||
});
|
||||
var roomStore = new loop.store.RoomStore({
|
||||
dispatcher: dispatcher,
|
||||
@ -533,20 +536,24 @@
|
||||
</Example>
|
||||
</Section>
|
||||
|
||||
<Section name="DesktopRoomInvitationView">
|
||||
<Example summary="Desktop room invitation" dashed="true"
|
||||
<Section name="DesktopRoomConversationView">
|
||||
<Example summary="Desktop room conversation (invitation)" dashed="true"
|
||||
style={{width: "260px", height: "265px"}}>
|
||||
<div className="fx-embedded">
|
||||
<DesktopRoomInvitationView roomStore={roomStore} />
|
||||
<DesktopRoomConversationView
|
||||
roomStore={roomStore}
|
||||
dispatcher={dispatcher}
|
||||
roomState={ROOM_STATES.INIT} />
|
||||
</div>
|
||||
</Example>
|
||||
</Section>
|
||||
|
||||
<Section name="DesktopRoomConversationView">
|
||||
<Example summary="Desktop room conversation" dashed="true"
|
||||
style={{width: "260px", height: "265px"}}>
|
||||
<div className="fx-embedded">
|
||||
<DesktopRoomConversationView roomStore={roomStore} />
|
||||
<DesktopRoomConversationView
|
||||
roomStore={roomStore}
|
||||
dispatcher={dispatcher}
|
||||
roomState={ROOM_STATES.HAS_PARTICIPANTS} />
|
||||
</div>
|
||||
</Example>
|
||||
</Section>
|
||||
|
@ -635,21 +635,8 @@ let SessionStoreInternal = {
|
||||
let uri = activePageData ? activePageData.url || null : null;
|
||||
browser.userTypedValue = uri;
|
||||
|
||||
// If the page has a title, set it.
|
||||
if (activePageData) {
|
||||
if (activePageData.title) {
|
||||
tab.label = activePageData.title;
|
||||
tab.crop = "end";
|
||||
} else if (activePageData.url != "about:blank") {
|
||||
tab.label = activePageData.url;
|
||||
tab.crop = "center";
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the tab icon.
|
||||
if ("image" in tabData) {
|
||||
win.gBrowser.setIcon(tab, tabData.image);
|
||||
}
|
||||
// Update tab label and icon again after the tab history was updated.
|
||||
this.updateTabLabelAndIcon(tab, tabData);
|
||||
|
||||
let event = win.document.createEvent("Events");
|
||||
event.initEvent("SSTabRestoring", true, false);
|
||||
@ -1860,6 +1847,26 @@ let SessionStoreInternal = {
|
||||
}
|
||||
},
|
||||
|
||||
updateTabLabelAndIcon(tab, tabData) {
|
||||
let activePageData = tabData.entries[tabData.index - 1] || null;
|
||||
|
||||
// If the page has a title, set it.
|
||||
if (activePageData) {
|
||||
if (activePageData.title) {
|
||||
tab.label = activePageData.title;
|
||||
tab.crop = "end";
|
||||
} else if (activePageData.url != "about:blank") {
|
||||
tab.label = activePageData.url;
|
||||
tab.crop = "center";
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the tab icon.
|
||||
if ("image" in tabData) {
|
||||
tab.ownerDocument.defaultView.gBrowser.setIcon(tab, tabData.image);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores the session state stored in LastSession. This will attempt
|
||||
* to merge data into the current session. If a window was opened at startup
|
||||
@ -2532,9 +2539,17 @@ let SessionStoreInternal = {
|
||||
this._windows[aWindow.__SSi].selected = aSelectTab;
|
||||
}
|
||||
|
||||
// If we restore the selected tab, make sure it goes first.
|
||||
let selectedIndex = aTabs.indexOf(tabbrowser.selectedTab);
|
||||
if (selectedIndex > -1) {
|
||||
this.restoreTab(tabbrowser.selectedTab, aTabData[selectedIndex]);
|
||||
}
|
||||
|
||||
// Restore all tabs.
|
||||
for (let t = 0; t < aTabs.length; t++) {
|
||||
this.restoreTab(aTabs[t], aTabData[t]);
|
||||
if (t != selectedIndex) {
|
||||
this.restoreTab(aTabs[t], aTabData[t]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -2640,6 +2655,10 @@ let SessionStoreInternal = {
|
||||
browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory",
|
||||
{tabData: tabData, epoch: epoch});
|
||||
|
||||
// Update tab label and icon to show something
|
||||
// while we wait for the messages to be processed.
|
||||
this.updateTabLabelAndIcon(tab, tabData);
|
||||
|
||||
// Restore tab attributes.
|
||||
if ("attributes" in tabData) {
|
||||
TabAttributes.set(tab, tabData.attributes);
|
||||
|
@ -761,6 +761,10 @@ Toolbox.prototype = {
|
||||
let tiltEnabled = !this.target.isMultiProcess &&
|
||||
Services.prefs.getBoolPref("devtools.command-button-tilt.enabled");
|
||||
let tiltButton = this.doc.getElementById("command-button-tilt");
|
||||
// Remote toolboxes don't add the button to the DOM at all
|
||||
if (!tiltButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tiltEnabled) {
|
||||
tiltButton.removeAttribute("hidden");
|
||||
|
@ -76,6 +76,14 @@ exports.showDoorhanger = Task.async(function *({ window, type, anchor }) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Call success function to set preferences/cleanup immediately,
|
||||
// so if triggered multiple times, only happens once (Windows/Linux)
|
||||
success();
|
||||
|
||||
// Wait 200ms to prevent flickering where the popup is displayed
|
||||
// before the underlying window (Windows 7, 64bit)
|
||||
yield wait(200);
|
||||
|
||||
let document = window.document;
|
||||
|
||||
let panel = document.createElementNS(XULNS, "panel");
|
||||
@ -108,9 +116,6 @@ exports.showDoorhanger = Task.async(function *({ window, type, anchor }) {
|
||||
close();
|
||||
});
|
||||
}
|
||||
|
||||
// Call success function to set preferences, etc.
|
||||
success();
|
||||
});
|
||||
|
||||
function setDoorhangerStyle (panel, frame) {
|
||||
@ -147,3 +152,9 @@ function onFrameLoad (frame) {
|
||||
function getGBrowser () {
|
||||
return getMostRecentBrowserWindow().gBrowser;
|
||||
}
|
||||
|
||||
function wait (n) {
|
||||
let { resolve, promise } = Promise.defer();
|
||||
setTimeout(resolve, n);
|
||||
return promise;
|
||||
}
|
||||
|
@ -145,6 +145,11 @@ Telemetry.prototype = {
|
||||
userHistogram: "DEVTOOLS_NETMONITOR_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_NETMONITOR_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
storage: {
|
||||
histogram: "DEVTOOLS_STORAGE_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_STORAGE_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_STORAGE_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
tilt: {
|
||||
histogram: "DEVTOOLS_TILT_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_TILT_OPENED_PER_USER_FLAG",
|
||||
|
@ -60,6 +60,7 @@ skip-if = e10s # Bug 1086492 - Disable tilt for e10s
|
||||
[browser_telemetry_toolboxtabs_netmonitor.js]
|
||||
[browser_telemetry_toolboxtabs_options.js]
|
||||
[browser_telemetry_toolboxtabs_shadereditor.js]
|
||||
[browser_telemetry_toolboxtabs_storage.js]
|
||||
[browser_telemetry_toolboxtabs_styleeditor.js]
|
||||
[browser_telemetry_toolboxtabs_webaudioeditor.js]
|
||||
[browser_telemetry_toolboxtabs_webconsole.js]
|
||||
|
@ -0,0 +1,119 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolboxtabs_storage.js</p>";
|
||||
|
||||
// Because we need to gather stats for the period of time that a tool has been
|
||||
// opened we make use of setTimeout() to create tool active times.
|
||||
const TOOL_DELAY = 200;
|
||||
|
||||
let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
|
||||
let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
|
||||
let STORAGE_PREF = "devtools.storage.enabled";
|
||||
Services.prefs.setBoolPref(STORAGE_PREF, true);
|
||||
|
||||
function init() {
|
||||
Telemetry.prototype.telemetryInfo = {};
|
||||
Telemetry.prototype._oldlog = Telemetry.prototype.log;
|
||||
Telemetry.prototype.log = function(histogramId, value) {
|
||||
if (!this.telemetryInfo) {
|
||||
// Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
|
||||
return;
|
||||
}
|
||||
if (histogramId) {
|
||||
if (!this.telemetryInfo[histogramId]) {
|
||||
this.telemetryInfo[histogramId] = [];
|
||||
}
|
||||
|
||||
this.telemetryInfo[histogramId].push(value);
|
||||
}
|
||||
}
|
||||
|
||||
openToolboxTabTwice("storage", false);
|
||||
}
|
||||
|
||||
function openToolboxTabTwice(id, secondPass) {
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
gDevTools.showToolbox(target, id).then(function(toolbox) {
|
||||
info("Toolbox tab " + id + " opened");
|
||||
|
||||
toolbox.once("destroyed", function() {
|
||||
if (secondPass) {
|
||||
checkResults();
|
||||
} else {
|
||||
openToolboxTabTwice(id, true);
|
||||
}
|
||||
});
|
||||
// We use a timeout to check the tools active time
|
||||
setTimeout(function() {
|
||||
gDevTools.closeToolbox(target);
|
||||
}, TOOL_DELAY);
|
||||
}).then(null, reportError);
|
||||
}
|
||||
|
||||
function checkResults() {
|
||||
let result = Telemetry.prototype.telemetryInfo;
|
||||
|
||||
for (let [histId, value] of Iterator(result)) {
|
||||
if (histId.endsWith("OPENED_PER_USER_FLAG")) {
|
||||
ok(value.length === 1 && value[0] === true,
|
||||
"Per user value " + histId + " has a single value of true");
|
||||
} else if (histId.endsWith("OPENED_BOOLEAN")) {
|
||||
ok(value.length > 1, histId + " has more than one entry");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return element === true;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " entries are === true");
|
||||
} else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
|
||||
ok(value.length > 1, histId + " has more than one entry");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return element > 0;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " entries have time > 0");
|
||||
}
|
||||
}
|
||||
|
||||
finishUp();
|
||||
}
|
||||
|
||||
function reportError(error) {
|
||||
let stack = " " + error.stack.replace(/\n?.*?@/g, "\n JS frame :: ");
|
||||
|
||||
ok(false, "ERROR: " + error + " at " + error.fileName + ":" +
|
||||
error.lineNumber + "\n\nStack trace:" + stack);
|
||||
finishUp();
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
Telemetry.prototype.log = Telemetry.prototype._oldlog;
|
||||
delete Telemetry.prototype._oldlog;
|
||||
delete Telemetry.prototype.telemetryInfo;
|
||||
|
||||
Services.prefs.clearUserPref(STORAGE_PREF);
|
||||
|
||||
TargetFactory = Services = promise = require = null;
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
waitForFocus(init, content);
|
||||
}, true);
|
||||
|
||||
content.location = TEST_URI;
|
||||
}
|
@ -59,7 +59,7 @@ StoragePanel.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the style editor.
|
||||
* Destroy the storage inspector.
|
||||
*/
|
||||
destroy: function() {
|
||||
if (!this._destroyed) {
|
||||
|
@ -21,6 +21,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "ViewHelpers",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
|
||||
"resource:///modules/devtools/VariablesView.jsm");
|
||||
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
|
||||
/**
|
||||
* Localization convenience methods.
|
||||
*/
|
||||
@ -85,6 +87,9 @@ this.StorageUI = function StorageUI(front, target, panelWin) {
|
||||
|
||||
this.handleKeypress = this.handleKeypress.bind(this);
|
||||
this._panelDoc.addEventListener("keypress", this.handleKeypress);
|
||||
|
||||
this._telemetry = new Telemetry();
|
||||
this._telemetry.toolOpened("storage");
|
||||
}
|
||||
|
||||
exports.StorageUI = StorageUI;
|
||||
@ -97,6 +102,7 @@ StorageUI.prototype = {
|
||||
destroy: function() {
|
||||
this.front.off("stores-update", this.onUpdate);
|
||||
this._panelDoc.removeEventListener("keypress", this.handleKeypress);
|
||||
this._telemetry.toolClosed("storage");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -285,7 +285,14 @@ let UI = {
|
||||
this.cancelBusyTimeout();
|
||||
this.unbusy();
|
||||
}, (e) => {
|
||||
let message = operationDescription + (e ? (": " + e) : "");
|
||||
let message;
|
||||
if (e.error && e.message) {
|
||||
// Some errors come from fronts that are not based on protocol.js.
|
||||
// Errors are not translated to strings.
|
||||
message = operationDescription + " (" + e.error + "): " + e.message;
|
||||
} else {
|
||||
message = operationDescription + (e ? (": " + e) : "");
|
||||
}
|
||||
this.cancelBusyTimeout();
|
||||
let operationCanceled = e && e.canceled;
|
||||
if (!operationCanceled) {
|
||||
@ -817,7 +824,8 @@ let UI = {
|
||||
playCmd.setAttribute("disabled", "true");
|
||||
stopCmd.setAttribute("disabled", "true");
|
||||
} else {
|
||||
if (AppManager.selectedProject.errorsCount == 0) {
|
||||
if (AppManager.selectedProject.errorsCount == 0 &&
|
||||
AppManager.runtimeCanHandleApps()) {
|
||||
playCmd.removeAttribute("disabled");
|
||||
} else {
|
||||
playCmd.setAttribute("disabled", "true");
|
||||
|
@ -106,18 +106,28 @@ let AppManager = exports.AppManager = {
|
||||
this._listTabsResponse = null;
|
||||
} else {
|
||||
this.connection.client.listTabs((response) => {
|
||||
let front = new AppActorFront(this.connection.client,
|
||||
response);
|
||||
front.on("install-progress", this.onInstallProgress);
|
||||
front.watchApps(() => this.checkIfProjectIsRunning())
|
||||
.then(() => front.fetchIcons())
|
||||
.then(() => {
|
||||
this._appsFront = front;
|
||||
this.checkIfProjectIsRunning();
|
||||
this.update("runtime-apps-found");
|
||||
});
|
||||
this._listTabsResponse = response;
|
||||
this.update("list-tabs-response");
|
||||
if (response.webappsActor) {
|
||||
let front = new AppActorFront(this.connection.client,
|
||||
response);
|
||||
front.on("install-progress", this.onInstallProgress);
|
||||
front.watchApps(() => this.checkIfProjectIsRunning())
|
||||
.then(() => {
|
||||
// This can't be done earlier as many operations
|
||||
// in the apps actor require watchApps to be called
|
||||
// first.
|
||||
this._appsFront = front;
|
||||
this._listTabsResponse = response;
|
||||
this.update("list-tabs-response");
|
||||
return front.fetchIcons();
|
||||
})
|
||||
.then(() => {
|
||||
this.checkIfProjectIsRunning();
|
||||
this.update("runtime-apps-found");
|
||||
});
|
||||
} else {
|
||||
this._listTabsResponse = response;
|
||||
this.update("list-tabs-response");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -416,6 +426,10 @@ let AppManager = exports.AppManager = {
|
||||
}
|
||||
},
|
||||
|
||||
runtimeCanHandleApps: function() {
|
||||
return !!this._appsFront;
|
||||
},
|
||||
|
||||
installAndRunProject: function() {
|
||||
let project = this.selectedProject;
|
||||
|
||||
@ -429,6 +443,11 @@ let AppManager = exports.AppManager = {
|
||||
return promise.reject("Can't install");
|
||||
}
|
||||
|
||||
if (!this._appsFront) {
|
||||
console.error("Runtime doesn't have a webappsActor");
|
||||
return promise.reject("Can't install");
|
||||
}
|
||||
|
||||
return Task.spawn(function* () {
|
||||
let self = AppManager;
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
||||
let fakeRuntime = {
|
||||
type: "USB",
|
||||
connect: function(connection) {
|
||||
ok(connection, win.AppManager.connection, "connection is valid");
|
||||
is(connection, win.AppManager.connection, "connection is valid");
|
||||
connection.host = null; // force connectPipe
|
||||
connection.connect();
|
||||
return promise.resolve();
|
||||
|
@ -51,7 +51,7 @@
|
||||
|
||||
win.AppManager.runtimeList.usb.push({
|
||||
connect: function(connection) {
|
||||
ok(connection, win.AppManager.connection, "connection is valid");
|
||||
is(connection, win.AppManager.connection, "connection is valid");
|
||||
connection.host = null; // force connectPipe
|
||||
connection.connect();
|
||||
return promise.resolve();
|
||||
@ -84,6 +84,8 @@
|
||||
|
||||
is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
|
||||
|
||||
yield waitForUpdate(win, "list-tabs-response");
|
||||
|
||||
ok(isPlayActive(), "play button is enabled 1");
|
||||
ok(!isStopActive(), "stop button is disabled 1");
|
||||
let oldProject = win.AppManager.selectedProject;
|
||||
|
@ -9,3 +9,5 @@ skip-if = debug # bug 1058695
|
||||
[browser_pdfjs_savedialog.js]
|
||||
[browser_pdfjs_views.js]
|
||||
skip-if = debug # bug 1058695
|
||||
[browser_pdfjs_zoom.js]
|
||||
skip-if = debug # bug 1058695
|
||||
|
173
browser/extensions/pdfjs/test/browser_pdfjs_zoom.js
Normal file
173
browser/extensions/pdfjs/test/browser_pdfjs_zoom.js
Normal file
@ -0,0 +1,173 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const RELATIVE_DIR = "browser/extensions/pdfjs/test/";
|
||||
const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
|
||||
|
||||
const TESTS = [
|
||||
{
|
||||
action: {
|
||||
selector: "button#zoomIn",
|
||||
event: "click"
|
||||
},
|
||||
expectedZoom: 1, // 1 - zoom in
|
||||
message: "Zoomed in using the '+' (zoom in) button"
|
||||
},
|
||||
|
||||
{
|
||||
action: {
|
||||
selector: "button#zoomOut",
|
||||
event: "click"
|
||||
},
|
||||
expectedZoom: -1, // -1 - zoom out
|
||||
message: "Zoomed out using the '-' (zoom out) button"
|
||||
},
|
||||
|
||||
{
|
||||
action: {
|
||||
keyboard: true,
|
||||
event: "+"
|
||||
},
|
||||
expectedZoom: 1, // 1 - zoom in
|
||||
message: "Zoomed in using the CTRL++ keys"
|
||||
},
|
||||
|
||||
{
|
||||
action: {
|
||||
keyboard: true,
|
||||
event: "-"
|
||||
},
|
||||
expectedZoom: -1, // -1 - zoom out
|
||||
message: "Zoomed out using the CTRL+- keys"
|
||||
},
|
||||
|
||||
{
|
||||
action: {
|
||||
selector: "select#scaleSelect",
|
||||
index: 5,
|
||||
event: "change"
|
||||
},
|
||||
expectedZoom: -1, // -1 - zoom out
|
||||
message: "Zoomed using the zoom picker"
|
||||
}
|
||||
];
|
||||
|
||||
let initialWidth; // the initial width of the PDF document
|
||||
var previousWidth; // the width of the PDF document at previous step/test
|
||||
|
||||
function test() {
|
||||
var tab;
|
||||
let handlerService = Cc["@mozilla.org/uriloader/handler-service;1"]
|
||||
.getService(Ci.nsIHandlerService);
|
||||
let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
|
||||
let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
|
||||
|
||||
// Make sure pdf.js is the default handler.
|
||||
is(handlerInfo.alwaysAskBeforeHandling, false,
|
||||
'pdf handler defaults to always-ask is false');
|
||||
is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.handleInternally,
|
||||
'pdf handler defaults to internal');
|
||||
|
||||
info('Pref action: ' + handlerInfo.preferredAction);
|
||||
|
||||
waitForExplicitFinish();
|
||||
registerCleanupFunction(function() {
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
tab = gBrowser.selectedTab = gBrowser.addTab(TESTROOT + "file_pdfjs_test.pdf");
|
||||
var newTabBrowser = gBrowser.getBrowserForTab(tab);
|
||||
|
||||
newTabBrowser.addEventListener("load", function eventHandler() {
|
||||
newTabBrowser.removeEventListener("load", eventHandler, true);
|
||||
|
||||
var document = newTabBrowser.contentDocument,
|
||||
window = newTabBrowser.contentWindow;
|
||||
|
||||
// Runs tests after all 'load' event handlers have fired off
|
||||
window.addEventListener("documentload", function() {
|
||||
initialWidth = parseInt(document.querySelector("div#pageContainer1").style.width);
|
||||
previousWidth = initialWidth;
|
||||
runTests(document, window, finish);
|
||||
}, false, true);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function runTests(document, window, callback) {
|
||||
// check that PDF is opened with internal viewer
|
||||
ok(document.querySelector('div#viewer'), "document content has viewer UI");
|
||||
ok('PDFJS' in window.wrappedJSObject, "window content has PDFJS object");
|
||||
|
||||
// Start the zooming tests after the document is loaded
|
||||
waitForDocumentLoad(document).then(function () {
|
||||
zoomPDF(document, window, TESTS.shift(), finish);
|
||||
});
|
||||
}
|
||||
|
||||
function waitForDocumentLoad(document) {
|
||||
var deferred = Promise.defer();
|
||||
var interval = setInterval(function () {
|
||||
if (document.querySelector("div#pageContainer1") != null){
|
||||
clearInterval(interval);
|
||||
deferred.resolve();
|
||||
}
|
||||
}, 500);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function zoomPDF(document, window, test, endCallback) {
|
||||
var renderedPage;
|
||||
|
||||
document.addEventListener("pagerendered", function onPageRendered(e) {
|
||||
if(e.detail.pageNumber !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.removeEventListener("pagerendered", onPageRendered, true);
|
||||
|
||||
var pageZoomScale = document.querySelector('select#scaleSelect');
|
||||
|
||||
// The zoom value displayed in the zoom select
|
||||
var zoomValue = pageZoomScale.options[pageZoomScale.selectedIndex].innerHTML;
|
||||
|
||||
let pageContainer = document.querySelector('div#pageContainer1');
|
||||
let actualWidth = parseInt(pageContainer.style.width);
|
||||
|
||||
// the actual zoom of the PDF document
|
||||
let computedZoomValue = parseInt(((actualWidth/initialWidth).toFixed(2))*100) + "%";
|
||||
is(computedZoomValue, zoomValue, "Content has correct zoom");
|
||||
|
||||
// Check that document zooms in the expected way (in/out)
|
||||
let zoom = (actualWidth - previousWidth) * test.expectedZoom;
|
||||
ok(zoom > 0, test.message);
|
||||
|
||||
// Go to next test (if there is any) or finish
|
||||
var nextTest = TESTS.shift();
|
||||
if (nextTest) {
|
||||
previousWidth = actualWidth;
|
||||
zoomPDF(document, window, nextTest, endCallback);
|
||||
}
|
||||
else
|
||||
endCallback();
|
||||
}, true);
|
||||
|
||||
// We zoom using an UI element
|
||||
if (test.action.selector) {
|
||||
// Get the element and trigger the action for changing the zoom
|
||||
var el = document.querySelector(test.action.selector);
|
||||
ok(el, "Element '" + test.action.selector + "' has been found");
|
||||
|
||||
if (test.action.index){
|
||||
el.selectedIndex = test.action.index;
|
||||
}
|
||||
|
||||
// Dispatch the event for changing the zoom
|
||||
el.dispatchEvent(new Event(test.action.event));
|
||||
}
|
||||
// We zoom using keyboard
|
||||
else {
|
||||
// Simulate key press
|
||||
EventUtils.synthesizeKey(test.action.event, { ctrlKey: true });
|
||||
}
|
||||
}
|
@ -8989,7 +8989,7 @@ if test "$ACCESSIBILITY" -a "$MOZ_ENABLE_GTK" ; then
|
||||
AC_DEFINE_UNQUOTED(ATK_REV_VERSION, $ATK_REV_VERSION)
|
||||
fi
|
||||
|
||||
if test -z "$RELEASE_BUILD" -a -z "$NIGHTLY_BUILD"; then
|
||||
if test -n "$MOZ_DEV_EDITION"; then
|
||||
AC_DEFINE(MOZ_DEV_EDITION)
|
||||
fi
|
||||
|
||||
|
@ -360,21 +360,27 @@ Promise::MaybeReject(const nsRefPtr<MediaStreamError>& aArg) {
|
||||
MaybeSomething(aArg, &Promise::MaybeReject);
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
Promise::PerformMicroTaskCheckpoint()
|
||||
{
|
||||
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
|
||||
nsTArray<nsRefPtr<nsIRunnable>>& microtaskQueue =
|
||||
runtime->GetPromiseMicroTaskQueue();
|
||||
|
||||
while (!microtaskQueue.IsEmpty()) {
|
||||
if (microtaskQueue.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
do {
|
||||
nsRefPtr<nsIRunnable> runnable = microtaskQueue.ElementAt(0);
|
||||
MOZ_ASSERT(runnable);
|
||||
|
||||
// This function can re-enter, so we remove the element before calling.
|
||||
microtaskQueue.RemoveElementAt(0);
|
||||
runnable->Run();
|
||||
}
|
||||
} while (!microtaskQueue.IsEmpty());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
|
@ -121,7 +121,8 @@ public:
|
||||
// the T values we support.
|
||||
|
||||
// Called by DOM to let us execute our callbacks. May be called recursively.
|
||||
static void PerformMicroTaskCheckpoint();
|
||||
// Returns true if at least one microtask was processed.
|
||||
static bool PerformMicroTaskCheckpoint();
|
||||
|
||||
// WebIDL
|
||||
|
||||
|
@ -4243,7 +4243,7 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
|
||||
|
||||
// Only perform the Promise microtask checkpoint on the outermost event
|
||||
// loop. Don't run it, for example, during sync XHR or importScripts.
|
||||
Promise::PerformMicroTaskCheckpoint();
|
||||
(void)Promise::PerformMicroTaskCheckpoint();
|
||||
|
||||
if (NS_HasPendingEvents(mThread)) {
|
||||
// Now *might* be a good time to GC. Let the JS engine make the decision.
|
||||
|
@ -1008,15 +1008,33 @@ nsXPConnect::JSToVariant(JSContext* ctx, HandleValue value, nsIVariant** _retval
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class DummyRunnable : public nsRunnable {
|
||||
public:
|
||||
NS_IMETHOD Run() { return NS_OK; }
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXPConnect::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
|
||||
uint32_t aRecursionDepth)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// If ProcessNextEvent was called during a Promise "then" callback, we
|
||||
// must process any pending microtasks before blocking in the event loop,
|
||||
// otherwise we may deadlock until an event enters the queue later.
|
||||
if (aMayWait) {
|
||||
Promise::PerformMicroTaskCheckpoint();
|
||||
if (Promise::PerformMicroTaskCheckpoint()) {
|
||||
// If any microtask was processed, we post a dummy event in order to
|
||||
// force the ProcessNextEvent call not to block. This is required
|
||||
// to support nested event loops implemented using a pattern like
|
||||
// "while (condition) thread.processNextEvent(true)", in case the
|
||||
// condition is triggered here by a Promise "then" callback.
|
||||
NS_DispatchToMainThread(new DummyRunnable());
|
||||
}
|
||||
}
|
||||
|
||||
// Record this event.
|
||||
@ -1024,7 +1042,6 @@ nsXPConnect::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
|
||||
|
||||
// Push a null JSContext so that we don't see any script during
|
||||
// event processing.
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
bool ok = PushJSContextNoScriptContext(nullptr);
|
||||
NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
|
||||
return NS_OK;
|
||||
|
@ -903,9 +903,6 @@ OnSharedPreferenceChangeListener
|
||||
if (GeckoAppShell.getGeckoInterface() != null) {
|
||||
intent.putExtra("user_agent", GeckoAppShell.getGeckoInterface().getDefaultUAString());
|
||||
}
|
||||
if (!AppConstants.MOZILLA_OFFICIAL) {
|
||||
intent.putExtra("is_debug", true);
|
||||
}
|
||||
broadcastAction(context, intent);
|
||||
}
|
||||
|
||||
|
@ -146,3 +146,4 @@ skip-if = android_version == "10"
|
||||
skip-if = android_version == "10"
|
||||
|
||||
[testStumblerSetting]
|
||||
skip-if = android_version == "10"
|
||||
|
@ -7,7 +7,7 @@ package org.mozilla.mozstumbler.service;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class AppGlobals {
|
||||
public static final String LOG_PREFIX = "Stumbler:";
|
||||
public static final String LOG_PREFIX = "Stumbler_";
|
||||
|
||||
/* All intent actions start with this string. Only locally broadcasted. */
|
||||
public static final String ACTION_NAMESPACE = "org.mozilla.mozstumbler.intent.action";
|
||||
@ -57,6 +57,14 @@ public class AppGlobals {
|
||||
}
|
||||
}
|
||||
|
||||
public static String makeLogTag(String name) {
|
||||
final int maxLen = 23 - LOG_PREFIX.length();
|
||||
if (name.length() > maxLen) {
|
||||
name = name.substring(name.length() - maxLen, name.length());
|
||||
}
|
||||
return LOG_PREFIX + name;
|
||||
}
|
||||
|
||||
public static final String ACTION_TEST_SETTING_ENABLED = "stumbler-test-setting-enabled";
|
||||
public static final String ACTION_TEST_SETTING_DISABLED = "stumbler-test-setting-disabled";
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
public final class Prefs {
|
||||
private static final String LOG_TAG = Prefs.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(Prefs.class.getSimpleName());
|
||||
private static final String NICKNAME_PREF = "nickname";
|
||||
private static final String USER_AGENT_PREF = "user-agent";
|
||||
private static final String VALUES_VERSION_PREF = "values_version";
|
||||
|
@ -29,7 +29,9 @@ import org.mozilla.mozstumbler.service.stumblerthread.StumblerService;
|
||||
* is a good time to try upload, as it is likely that the network is in use.
|
||||
*/
|
||||
public class PassiveServiceReceiver extends BroadcastReceiver {
|
||||
static final String LOG_TAG = AppGlobals.LOG_PREFIX + PassiveServiceReceiver.class.getSimpleName();
|
||||
// This allows global debugging logs to be enabled by doing
|
||||
// |adb shell setprop log.tag.PassiveStumbler DEBUG|
|
||||
static final String LOG_TAG = "PassiveStumbler";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@ -37,6 +39,11 @@ public class PassiveServiceReceiver extends BroadcastReceiver {
|
||||
return;
|
||||
}
|
||||
|
||||
// This value is cached, so if |setprop| is performed (as described on the LOG_TAG above),
|
||||
// then the start/stop intent must be resent by toggling the setting or stopping/starting Fennec.
|
||||
// This does not guard against dumping PII (PII in stumbler is location, wifi BSSID, cell tower details).
|
||||
AppGlobals.isDebug = Log.isLoggable(LOG_TAG, Log.DEBUG);
|
||||
|
||||
final String action = intent.getAction();
|
||||
final boolean isIntentFromHostApp = (action != null) && action.contains(".STUMBLER_PREF");
|
||||
if (!isIntentFromHostApp) {
|
||||
@ -47,9 +54,6 @@ public class PassiveServiceReceiver extends BroadcastReceiver {
|
||||
return;
|
||||
}
|
||||
|
||||
if (intent.hasExtra("is_debug")) {
|
||||
AppGlobals.isDebug = intent.getBooleanExtra("is_debug", false);
|
||||
}
|
||||
StumblerService.sFirefoxStumblingEnabled.set(intent.getBooleanExtra("enabled", false));
|
||||
|
||||
if (!StumblerService.sFirefoxStumblingEnabled.get()) {
|
||||
|
@ -30,7 +30,7 @@ import org.mozilla.mozstumbler.service.stumblerthread.scanners.GPSScanner;
|
||||
import org.mozilla.mozstumbler.service.stumblerthread.scanners.WifiScanner;
|
||||
|
||||
public final class Reporter extends BroadcastReceiver {
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + Reporter.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(Reporter.class.getSimpleName());
|
||||
public static final String ACTION_FLUSH_TO_BUNDLE = AppGlobals.ACTION_NAMESPACE + ".FLUSH";
|
||||
private boolean mIsStarted;
|
||||
|
||||
@ -195,7 +195,8 @@ public final class Reporter extends BroadcastReceiver {
|
||||
}
|
||||
|
||||
if (AppGlobals.isDebug) {
|
||||
Log.d(LOG_TAG, "Received bundle: " + mlsObj.toString());
|
||||
// PII: do not log the bundle without obfuscating it
|
||||
Log.d(LOG_TAG, "Received bundle");
|
||||
}
|
||||
|
||||
if (wifiCount + cellCount < 1) {
|
||||
|
@ -27,7 +27,7 @@ import org.mozilla.mozstumbler.service.utils.PersistentIntentService;
|
||||
//
|
||||
public class StumblerService extends PersistentIntentService
|
||||
implements DataStorageManager.StorageIsEmptyTracker {
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + StumblerService.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(StumblerService.class.getSimpleName());
|
||||
public static final String ACTION_BASE = AppGlobals.ACTION_NAMESPACE;
|
||||
public static final String ACTION_START_PASSIVE = ACTION_BASE + ".START_PASSIVE";
|
||||
public static final String ACTION_EXTRA_MOZ_API_KEY = ACTION_BASE + ".MOZKEY";
|
||||
@ -144,6 +144,8 @@ public class StumblerService extends PersistentIntentService
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
UploadAlarmReceiver.cancelAlarm(this, !mScanManager.isPassiveMode());
|
||||
|
||||
if (!mScanManager.isScanning()) {
|
||||
return;
|
||||
}
|
||||
@ -198,7 +200,11 @@ public class StumblerService extends PersistentIntentService
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DataStorageManager.getInstance().isDirEmpty()) {
|
||||
boolean hasFilesWaiting = !DataStorageManager.getInstance().isDirEmpty();
|
||||
if (AppGlobals.isDebug) {
|
||||
Log.d(LOG_TAG, "Files waiting:" + hasFilesWaiting);
|
||||
}
|
||||
if (hasFilesWaiting) {
|
||||
// non-empty on startup, schedule an upload
|
||||
// This is the only upload trigger in Firefox mode
|
||||
// Firefox triggers this ~4 seconds after startup (after Gecko is loaded), add a small delay to avoid
|
||||
|
@ -11,7 +11,7 @@ import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class BSSIDBlockList {
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + BSSIDBlockList.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(BSSIDBlockList.class.getSimpleName());
|
||||
private static final String NULL_BSSID = "000000000000";
|
||||
private static final String WILDCARD_BSSID = "ffffffffffff";
|
||||
private static final Pattern BSSID_PATTERN = Pattern.compile("([0-9a-f]{12})");
|
||||
|
@ -38,7 +38,7 @@ import java.util.TimerTask;
|
||||
* when the service is destroyed.
|
||||
*/
|
||||
public class DataStorageManager {
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + DataStorageManager.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(DataStorageManager.class.getSimpleName());
|
||||
|
||||
// The max number of reports stored in the mCurrentReports. Each report is a GPS location plus wifi and cell scan.
|
||||
// After this size is reached, data is persisted to disk, mCurrentReports is cleared.
|
||||
@ -201,19 +201,7 @@ public class DataStorageManager {
|
||||
}
|
||||
|
||||
private String getStorageDir(Context c) {
|
||||
File dir = null;
|
||||
if (AppGlobals.isDebug) {
|
||||
// in debug, put files in public location
|
||||
dir = c.getExternalFilesDir(null);
|
||||
if (dir != null) {
|
||||
dir = new File(dir.getAbsolutePath() + "/mozstumbler");
|
||||
}
|
||||
}
|
||||
|
||||
if (dir == null) {
|
||||
dir = c.getFilesDir();
|
||||
}
|
||||
|
||||
File dir = c.getFilesDir();
|
||||
if (!dir.exists()) {
|
||||
boolean ok = dir.mkdirs();
|
||||
if (!ok) {
|
||||
@ -414,9 +402,6 @@ public class DataStorageManager {
|
||||
}
|
||||
|
||||
final String result = sb.append(kSuffix).toString();
|
||||
if (AppGlobals.isDebug) {
|
||||
Log.d(LOG_TAG, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ public class GPSScanner implements LocationListener {
|
||||
public static final String NEW_STATUS_ARG_SATS = "sats";
|
||||
public static final String NEW_LOCATION_ARG_LOCATION = "location";
|
||||
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + GPSScanner.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(GPSScanner.class.getSimpleName());
|
||||
private static final int MIN_SAT_USED_IN_FIX = 3;
|
||||
private static final long ACTIVE_MODE_GPS_MIN_UPDATE_TIME_MS = 1000;
|
||||
private static final float ACTIVE_MODE_GPS_MIN_UPDATE_DISTANCE_M = 10;
|
||||
@ -191,15 +191,10 @@ public class GPSScanner implements LocationListener {
|
||||
sendToLogActivity(logMsg);
|
||||
|
||||
if (mBlockList.contains(location)) {
|
||||
Log.w(LOG_TAG, "Blocked location: " + location);
|
||||
reportLocationLost();
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppGlobals.isDebug) {
|
||||
Log.d(LOG_TAG, "New location: " + location);
|
||||
}
|
||||
|
||||
mLocation = location;
|
||||
|
||||
if (!mAutoGeofencing) {
|
||||
|
@ -10,7 +10,7 @@ import org.mozilla.mozstumbler.service.AppGlobals;
|
||||
import org.mozilla.mozstumbler.service.Prefs;
|
||||
|
||||
public final class LocationBlockList {
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + LocationBlockList.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(LocationBlockList.class.getSimpleName());
|
||||
private static final double MAX_ALTITUDE = 8848; // Mount Everest's altitude in meters
|
||||
private static final double MIN_ALTITUDE = -418; // Dead Sea's altitude in meters
|
||||
private static final float MAX_SPEED = 340.29f; // Mach 1 in meters/second
|
||||
|
@ -23,7 +23,7 @@ import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class ScanManager {
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + ScanManager.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(ScanManager.class.getSimpleName());
|
||||
private Timer mPassiveModeFlushTimer;
|
||||
private Context mContext;
|
||||
private boolean mIsScanning;
|
||||
|
@ -40,7 +40,7 @@ public class WifiScanner extends BroadcastReceiver {
|
||||
public static final int STATUS_ACTIVE = 1;
|
||||
public static final int STATUS_WIFI_DISABLED = -1;
|
||||
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + WifiScanner.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(WifiScanner.class.getSimpleName());
|
||||
private static final long WIFI_MIN_UPDATE_TIME = 5000; // milliseconds
|
||||
|
||||
private boolean mStarted;
|
||||
@ -194,11 +194,9 @@ public class WifiScanner extends BroadcastReceiver {
|
||||
|
||||
public static boolean shouldLog(ScanResult scanResult) {
|
||||
if (BSSIDBlockList.contains(scanResult)) {
|
||||
Log.w(LOG_TAG, "Blocked BSSID: " + scanResult);
|
||||
return false;
|
||||
}
|
||||
if (SSIDBlockList.contains(scanResult)) {
|
||||
Log.w(LOG_TAG, "Blocked SSID: " + scanResult);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -19,7 +19,7 @@ import org.json.JSONObject;
|
||||
import org.mozilla.mozstumbler.service.AppGlobals;
|
||||
|
||||
public class CellInfo implements Parcelable {
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + CellInfo.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(CellInfo.class.getSimpleName());
|
||||
|
||||
public static final String RADIO_GSM = "gsm";
|
||||
public static final String RADIO_CDMA = "cdma";
|
||||
|
@ -26,7 +26,7 @@ public class CellScanner {
|
||||
public static final String ACTION_CELLS_SCANNED_ARG_CELLS = "cells";
|
||||
public static final String ACTION_CELLS_SCANNED_ARG_TIME = AppGlobals.ACTION_ARG_TIME;
|
||||
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + CellScanner.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(CellScanner.class.getSimpleName());
|
||||
private static final long CELL_MIN_UPDATE_TIME = 1000; // milliseconds
|
||||
|
||||
private final Context mContext;
|
||||
|
@ -31,7 +31,7 @@ import java.util.List;
|
||||
/* Fennec does not yet support the api level for WCDMA import */
|
||||
public class CellScannerNoWCDMA implements CellScanner.CellScannerImpl {
|
||||
|
||||
protected static String LOG_TAG = AppGlobals.LOG_PREFIX + CellScannerNoWCDMA.class.getSimpleName();
|
||||
protected static String LOG_TAG = AppGlobals.makeLogTag(CellScannerNoWCDMA.class.getSimpleName());
|
||||
protected GetAllCellInfoScannerImpl mGetAllInfoCellScanner;
|
||||
protected TelephonyManager mTelephonyManager;
|
||||
protected boolean mIsStarted;
|
||||
|
@ -24,7 +24,7 @@ import org.mozilla.mozstumbler.service.utils.NetworkUtils;
|
||||
* preferences, do not call any code that isn't thread-safe. You will cause suffering.
|
||||
* An exception is made for AppGlobals.isDebug, a false reading is of no consequence. */
|
||||
public class AsyncUploader extends AsyncTask<Void, Void, SyncSummary> {
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + AsyncUploader.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(AsyncUploader.class.getSimpleName());
|
||||
private final UploadSettings mSettings;
|
||||
private final Object mListenerLock = new Object();
|
||||
private AsyncUploaderListener mListener;
|
||||
|
@ -30,7 +30,7 @@ import org.mozilla.mozstumbler.service.utils.NetworkUtils;
|
||||
// - triggered from the main thread
|
||||
// - actual work is done the upload thread (AsyncUploader)
|
||||
public class UploadAlarmReceiver extends BroadcastReceiver {
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + UploadAlarmReceiver.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(UploadAlarmReceiver.class.getSimpleName());
|
||||
private static final String EXTRA_IS_REPEATING = "is_repeating";
|
||||
private static boolean sIsAlreadyScheduled;
|
||||
|
||||
|
@ -20,7 +20,7 @@ import java.net.URL;
|
||||
|
||||
public abstract class AbstractCommunicator {
|
||||
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + AbstractCommunicator.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(AbstractCommunicator.class.getSimpleName());
|
||||
private static final String NICKNAME_HEADER = "X-Nickname";
|
||||
private static final String USER_AGENT_HEADER = "User-Agent";
|
||||
private HttpURLConnection mHttpURLConnection;
|
||||
|
@ -11,7 +11,7 @@ import android.util.Log;
|
||||
import org.mozilla.mozstumbler.service.AppGlobals;
|
||||
|
||||
public final class NetworkUtils {
|
||||
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + NetworkUtils.class.getSimpleName();
|
||||
private static final String LOG_TAG = AppGlobals.makeLogTag(NetworkUtils.class.getSimpleName());
|
||||
|
||||
ConnectivityManager mConnectivityManager;
|
||||
static NetworkUtils sInstance;
|
||||
|
@ -5856,6 +5856,11 @@
|
||||
"kind": "boolean",
|
||||
"description": "How many times has the devtool's Network Monitor been opened?"
|
||||
},
|
||||
"DEVTOOLS_STORAGE_OPENED_BOOLEAN": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
"description": "How many times has the Storage Inspector been opened?"
|
||||
},
|
||||
"DEVTOOLS_PAINTFLASHING_OPENED_BOOLEAN": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
@ -5976,6 +5981,11 @@
|
||||
"kind": "flag",
|
||||
"description": "How many users have opened the devtool's Network Monitor?"
|
||||
},
|
||||
"DEVTOOLS_STORAGE_OPENED_PER_USER_FLAG": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "flag",
|
||||
"description": "How many users have opened the devtool's Storage Inspector?"
|
||||
},
|
||||
"DEVTOOLS_PAINTFLASHING_OPENED_PER_USER_FLAG": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "flag",
|
||||
@ -6130,6 +6140,13 @@
|
||||
"n_buckets": 100,
|
||||
"description": "How long has the network monitor been active (seconds)"
|
||||
},
|
||||
"DEVTOOLS_STORAGE_TIME_ACTIVE_SECONDS": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": "10000000",
|
||||
"n_buckets": 100,
|
||||
"description": "How long has the storage inspector been active (seconds)"
|
||||
},
|
||||
"DEVTOOLS_PAINTFLASHING_TIME_ACTIVE_SECONDS": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
|
Loading…
Reference in New Issue
Block a user