This commit is contained in:
Ryan VanderMeulen 2014-11-11 16:43:46 -05:00
Родитель b621e08282 e98edf109a
Коммит 50905e0270
74 изменённых файлов: 1453 добавлений и 465 удалений

Просмотреть файл

@ -4,3 +4,4 @@
MOZ_APP_DISPLAYNAME=FirefoxDeveloperEdition MOZ_APP_DISPLAYNAME=FirefoxDeveloperEdition
MOZ_APP_REMOTINGNAME=firefox-dev MOZ_APP_REMOTINGNAME=firefox-dev
MOZ_DEV_EDITION=1

Просмотреть файл

@ -417,6 +417,8 @@ loop.contacts = (function(_, mozL10n) {
}, },
render: function() { render: function() {
let cx = React.addons.classSet;
let viewForItem = item => { let viewForItem = item => {
return ContactDetail({key: item._guid, contact: item, return ContactDetail({key: item._guid, contact: item,
handleContactAction: this.handleContactAction}) handleContactAction: this.handleContactAction})
@ -444,7 +446,6 @@ loop.contacts = (function(_, mozL10n) {
} }
} }
// TODO: bug 1076767 - add a spinner whilst importing contacts.
return ( return (
React.DOM.div(null, React.DOM.div(null,
React.DOM.div({className: "content-area"}, React.DOM.div({className: "content-area"},
@ -453,7 +454,11 @@ loop.contacts = (function(_, mozL10n) {
? mozL10n.get("importing_contacts_progress_button") ? mozL10n.get("importing_contacts_progress_button")
: mozL10n.get("import_contacts_button"), : mozL10n.get("import_contacts_button"),
disabled: this.state.importBusy, 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"), Button({caption: mozL10n.get("new_contact_button"),
onClick: this.handleAddContactButtonClick}) onClick: this.handleAddContactButtonClick})
), ),

Просмотреть файл

@ -417,6 +417,8 @@ loop.contacts = (function(_, mozL10n) {
}, },
render: function() { render: function() {
let cx = React.addons.classSet;
let viewForItem = item => { let viewForItem = item => {
return <ContactDetail key={item._guid} contact={item} return <ContactDetail key={item._guid} contact={item}
handleContactAction={this.handleContactAction} /> handleContactAction={this.handleContactAction} />
@ -444,7 +446,6 @@ loop.contacts = (function(_, mozL10n) {
} }
} }
// TODO: bug 1076767 - add a spinner whilst importing contacts.
return ( return (
<div> <div>
<div className="content-area"> <div className="content-area">
@ -453,7 +454,11 @@ loop.contacts = (function(_, mozL10n) {
? mozL10n.get("importing_contacts_progress_button") ? mozL10n.get("importing_contacts_progress_button")
: mozL10n.get("import_contacts_button")} : mozL10n.get("import_contacts_button")}
disabled={this.state.importBusy} 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")} <Button caption={mozL10n.get("new_contact_button")}
onClick={this.handleAddContactButtonClick} /> onClick={this.handleAddContactButtonClick} />
</ButtonGroup> </ButtonGroup>

Просмотреть файл

@ -18,7 +18,7 @@ loop.conversation = (function(mozL10n) {
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView; var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
var CallIdentifierView = loop.conversationViews.CallIdentifierView; var CallIdentifierView = loop.conversationViews.CallIdentifierView;
var DesktopRoomControllerView = loop.roomViews.DesktopRoomControllerView; var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
var IncomingCallView = React.createClass({displayName: 'IncomingCallView', var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin], mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
@ -584,10 +584,10 @@ loop.conversation = (function(mozL10n) {
)); ));
} }
case "room": { case "room": {
return (DesktopRoomControllerView({ return (DesktopRoomConversationView({
mozLoop: navigator.mozLoop,
dispatcher: this.props.dispatcher, dispatcher: this.props.dispatcher,
roomStore: this.props.roomStore} roomStore: this.props.roomStore,
dispatcher: this.props.dispatcher}
)); ));
} }
case "failed": { case "failed": {
@ -643,7 +643,8 @@ loop.conversation = (function(mozL10n) {
}); });
var activeRoomStore = new loop.store.ActiveRoomStore({ var activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,
mozLoop: navigator.mozLoop mozLoop: navigator.mozLoop,
sdkDriver: sdkDriver
}); });
var roomStore = new loop.store.RoomStore({ var roomStore = new loop.store.RoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,

Просмотреть файл

@ -18,7 +18,7 @@ loop.conversation = (function(mozL10n) {
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView; var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
var CallIdentifierView = loop.conversationViews.CallIdentifierView; var CallIdentifierView = loop.conversationViews.CallIdentifierView;
var DesktopRoomControllerView = loop.roomViews.DesktopRoomControllerView; var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
var IncomingCallView = React.createClass({ var IncomingCallView = React.createClass({
mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin], mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
@ -584,10 +584,10 @@ loop.conversation = (function(mozL10n) {
/>); />);
} }
case "room": { case "room": {
return (<DesktopRoomControllerView return (<DesktopRoomConversationView
mozLoop={navigator.mozLoop}
dispatcher={this.props.dispatcher} dispatcher={this.props.dispatcher}
roomStore={this.props.roomStore} roomStore={this.props.roomStore}
dispatcher={this.props.dispatcher}
/>); />);
} }
case "failed": { case "failed": {
@ -643,7 +643,8 @@ loop.conversation = (function(mozL10n) {
}); });
var activeRoomStore = new loop.store.ActiveRoomStore({ var activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,
mozLoop: navigator.mozLoop mozLoop: navigator.mozLoop,
sdkDriver: sdkDriver
}); });
var roomStore = new loop.store.RoomStore({ var roomStore = new loop.store.RoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,

Просмотреть файл

@ -399,18 +399,21 @@ loop.panel = (function(_, mozL10n) {
// readOnly attr will suppress a warning regarding this issue // readOnly attr will suppress a warning regarding this issue
// from the react lib. // from the react lib.
var cx = React.addons.classSet; 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 ( return (
React.DOM.div({className: "generate-url"}, React.DOM.div({className: "generate-url"},
React.DOM.header(null, __("share_link_header_text")), React.DOM.header(null, __("share_link_header_text")),
React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true", React.DOM.div({className: "generate-url-stack"},
onCopy: this.handleLinkExfiltration, React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true",
className: inputCSSClass}), 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"}, ButtonGroup({additionalClass: "url-actions"},
Button({additionalClass: "button-email", Button({additionalClass: "button-email",
disabled: !this.state.callUrl, disabled: !this.state.callUrl,

Просмотреть файл

@ -399,18 +399,21 @@ loop.panel = (function(_, mozL10n) {
// readOnly attr will suppress a warning regarding this issue // readOnly attr will suppress a warning regarding this issue
// from the react lib. // from the react lib.
var cx = React.addons.classSet; 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 ( return (
<div className="generate-url"> <div className="generate-url">
<header>{__("share_link_header_text")}</header> <header>{__("share_link_header_text")}</header>
<input type="url" value={this.state.callUrl} readOnly="true" <div className="generate-url-stack">
onCopy={this.handleLinkExfiltration} <input type="url" value={this.state.callUrl} readOnly="true"
className={inputCSSClass} /> 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"> <ButtonGroup additionalClass="url-actions">
<Button additionalClass="button-email" <Button additionalClass="button-email"
disabled={!this.state.callUrl} disabled={!this.state.callUrl}

Просмотреть файл

@ -11,6 +11,7 @@ var loop = loop || {};
loop.roomViews = (function(mozL10n) { loop.roomViews = (function(mozL10n) {
"use strict"; "use strict";
var sharedActions = loop.shared.actions;
var ROOM_STATES = loop.store.ROOM_STATES; var ROOM_STATES = loop.store.ROOM_STATES;
var sharedViews = loop.shared.views; var sharedViews = loop.shared.views;
@ -37,11 +38,20 @@ loop.roomViews = (function(mozL10n) {
}, },
_onActiveRoomStateChanged: function() { _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() { 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() { render: function() {
return ( return (
React.DOM.div({className: "room-conversation-wrapper"}, React.DOM.div({className: "room-invitation-overlay"},
React.DOM.div({className: "room-invitation-overlay"}, React.DOM.form({onSubmit: this.handleFormSubmit},
React.DOM.form({onSubmit: this.handleFormSubmit}, React.DOM.input({type: "text", ref: "roomName",
React.DOM.input({type: "text", ref: "roomName", placeholder: mozL10n.get("rooms_name_this_room_label")})
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")
)
)
), ),
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. * Desktop room conversation view.
*/ */
var DesktopRoomConversationView = React.createClass({displayName: 'DesktopRoomConversationView', 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], mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
propTypes: { propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired 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() { closeWindow: function() {
window.close(); window.close();
}, },
_renderRoomView: function(roomState) { /**
switch (roomState) { * Used to control publishing a stream - i.e. to mute a stream
case ROOM_STATES.FAILED: { *
return loop.conversation.GenericFailureView({ * @param {String} type The type of stream, e.g. "audio" or "video".
cancelCall: this.closeWindow} * @param {Boolean} enabled True to enable the stream, false otherwise.
); */
} publishStream: function(type, enabled) {
case ROOM_STATES.INIT: this.props.dispatcher.dispatch(
case ROOM_STATES.GATHER: new sharedActions.SetMute({
case ROOM_STATES.READY: type: type,
case ROOM_STATES.JOINED: { enabled: enabled
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}
);
}
}
}, },
render: function() { render: function() {
if (this.state.roomName) { if (this.state.roomName) {
this.setTitle(this.state.roomName); this.setTitle(this.state.roomName);
} }
return (
React.DOM.div({className: "room-conversation-wrapper"}, var localStreamClasses = React.addons.classSet({
this._renderRoomView(this.state.roomState) 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 { return {
ActiveRoomStoreMixin: ActiveRoomStoreMixin, ActiveRoomStoreMixin: ActiveRoomStoreMixin,
DesktopRoomControllerView: DesktopRoomControllerView,
DesktopRoomConversationView: DesktopRoomConversationView, DesktopRoomConversationView: DesktopRoomConversationView,
DesktopRoomInvitationView: DesktopRoomInvitationView DesktopRoomInvitationView: DesktopRoomInvitationView
}; };

Просмотреть файл

@ -11,6 +11,7 @@ var loop = loop || {};
loop.roomViews = (function(mozL10n) { loop.roomViews = (function(mozL10n) {
"use strict"; "use strict";
var sharedActions = loop.shared.actions;
var ROOM_STATES = loop.store.ROOM_STATES; var ROOM_STATES = loop.store.ROOM_STATES;
var sharedViews = loop.shared.views; var sharedViews = loop.shared.views;
@ -37,11 +38,20 @@ loop.roomViews = (function(mozL10n) {
}, },
_onActiveRoomStateChanged: function() { _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() { 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() { render: function() {
return ( return (
<div className="room-conversation-wrapper"> <div className="room-invitation-overlay">
<div className="room-invitation-overlay"> <form onSubmit={this.handleFormSubmit}>
<form onSubmit={this.handleFormSubmit}> <input type="text" ref="roomName"
<input type="text" ref="roomName" placeholder={mozL10n.get("rooms_name_this_room_label")} />
placeholder={mozL10n.get("rooms_name_this_room_label")} /> </form>
</form> <p>{mozL10n.get("invite_header_text")}</p>
<p>{mozL10n.get("invite_header_text")}</p> <div className="btn-group call-action-group">
<div className="btn-group call-action-group"> <button className="btn btn-info btn-email"
<button className="btn btn-info btn-email" onClick={this.handleEmailButtonClick}>
onClick={this.handleEmailButtonClick}> {mozL10n.get("share_button2")}
{mozL10n.get("share_button2")} </button>
</button> <button className="btn btn-info btn-copy"
<button className="btn btn-info btn-copy" onClick={this.handleCopyButtonClick}>
onClick={this.handleCopyButtonClick}> {mozL10n.get("copy_url_button2")}
{mozL10n.get("copy_url_button2")} </button>
</button>
</div>
</div> </div>
<DesktopRoomConversationView roomStore={this.props.roomStore} />
</div> </div>
); );
} }
@ -100,105 +107,150 @@ loop.roomViews = (function(mozL10n) {
* Desktop room conversation view. * Desktop room conversation view.
*/ */
var DesktopRoomConversationView = React.createClass({ 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], mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
propTypes: { propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired 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() { closeWindow: function() {
window.close(); window.close();
}, },
_renderRoomView: function(roomState) { /**
switch (roomState) { * Used to control publishing a stream - i.e. to mute a stream
case ROOM_STATES.FAILED: { *
return <loop.conversation.GenericFailureView * @param {String} type The type of stream, e.g. "audio" or "video".
cancelCall={this.closeWindow} * @param {Boolean} enabled True to enable the stream, false otherwise.
/>; */
} publishStream: function(type, enabled) {
case ROOM_STATES.INIT: this.props.dispatcher.dispatch(
case ROOM_STATES.GATHER: new sharedActions.SetMute({
case ROOM_STATES.READY: type: type,
case ROOM_STATES.JOINED: { enabled: enabled
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}
/>;
}
}
}, },
render: function() { render: function() {
if (this.state.roomName) { if (this.state.roomName) {
this.setTitle(this.state.roomName); this.setTitle(this.state.roomName);
} }
return (
<div className="room-conversation-wrapper">{ var localStreamClasses = React.addons.classSet({
this._renderRoomView(this.state.roomState) local: true,
}</div> "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 { return {
ActiveRoomStoreMixin: ActiveRoomStoreMixin, ActiveRoomStoreMixin: ActiveRoomStoreMixin,
DesktopRoomControllerView: DesktopRoomControllerView,
DesktopRoomConversationView: DesktopRoomConversationView, DesktopRoomConversationView: DesktopRoomConversationView,
DesktopRoomInvitationView: DesktopRoomInvitationView DesktopRoomInvitationView: DesktopRoomInvitationView
}; };

Просмотреть файл

@ -2,6 +2,16 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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 { .content-area input.contact-filter {
margin-top: 14px; margin-top: 14px;
border-radius: 10000px; border-radius: 10000px;

Просмотреть файл

@ -46,7 +46,7 @@ body {
flex: 1; flex: 1;
text-align: center; text-align: center;
color: #ccc; color: #ccc;
border-right: 1px solid #ccc; -moz-border-end: 1px solid #ccc;
padding: 0 10px; padding: 0 10px;
height: 16px; height: 16px;
cursor: pointer; cursor: pointer;
@ -56,7 +56,7 @@ body {
} }
.tab-view > li:last-child { .tab-view > li:last-child {
border-right-style: none; -moz-border-end-style: none;
} }
.tab-view > li[data-tab-name="call"], .tab-view > li[data-tab-name="call"],
@ -307,6 +307,10 @@ body {
font-size: 12px; font-size: 12px;
} }
.button > .button-caption {
vertical-align: middle;
}
.button:hover { .button:hover {
background-color: #ebebeb; background-color: #ebebeb;
} }
@ -372,10 +376,41 @@ body[dir=rtl] .dropdown-menu-item {
background-color: #eee; 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 */ /* Share tab */
.generate-url input { .generate-url-stack {
margin: 14px 0; margin: 14px 0;
position: relative;
}
.generate-url-input {
outline: 0; outline: 0;
border: 1px solid #ccc; /* Overriding background style for a text input (see border: 1px solid #ccc; /* Overriding background style for a text input (see
below) resets its borders to a weird beveled style; below) resets its borders to a weird beveled style;
@ -386,10 +421,18 @@ body[dir=rtl] .dropdown-menu-item {
font-size: 1em; font-size: 1em;
} }
.generate-url input.pending { .generate-url-spinner {
background-image: url(../img/loading-icon.gif); position: absolute;
background-repeat: no-repeat; pointer-events: none;
background-position: right; z-index: 1;
top: 4px;
left: auto;
right: 4px;
}
body[dir=rtl] .generate-url-spinner {
left: 4px;
right: auto;
} }
.generate-url .button { .generate-url .button {

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 2.5 KiB

Двоичные данные
browser/components/loop/content/shared/img/spinner.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 724 B

Двоичные данные
browser/components/loop/content/shared/img/spinner@2x.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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 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 * Used by the ongoing views to notify stores about the elements
* required for the sdk. * required for the sdk.

Просмотреть файл

@ -20,10 +20,12 @@ loop.store.ActiveRoomStore = (function() {
READY: "room-ready", READY: "room-ready",
// The room is known to be joined on the loop-server // The room is known to be joined on the loop-server
JOINED: "room-joined", 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 // There was an issue with the room
FAILED: "room-failed", FAILED: "room-failed"
// XXX to be implemented in bug 1074686/1074702
HAS_PARTICIPANTS: "room-has-participants"
}; };
/** /**
@ -51,15 +53,18 @@ loop.store.ActiveRoomStore = (function() {
} }
this._mozLoop = options.mozLoop; 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, [ this._dispatcher.register(this, [
"roomFailure",
"setupWindowData", "setupWindowData",
"fetchServerData", "fetchServerData"
"updateRoomInfo",
"joinRoom",
"joinedRoom",
"windowUnload",
"leaveRoom"
]); ]);
/** /**
@ -75,7 +80,9 @@ loop.store.ActiveRoomStore = (function() {
* otherwise it will be unset. * otherwise it will be unset.
*/ */
this._storeState = { 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 * Execute setupWindowData event action from the dispatcher. This gets
* the room data from the mozLoop api, and dispatches an UpdateRoomInfo event. * the room data from the mozLoop api, and dispatches an UpdateRoomInfo event.
@ -127,6 +154,8 @@ loop.store.ActiveRoomStore = (function() {
return; return;
} }
this._registerActions();
this.setStoreState({ this.setStoreState({
roomState: ROOM_STATES.GATHER roomState: ROOM_STATES.GATHER
}); });
@ -169,6 +198,8 @@ loop.store.ActiveRoomStore = (function() {
return; return;
} }
this._registerActions();
this.setStoreState({ this.setStoreState({
roomToken: actionData.token, roomToken: actionData.token,
roomState: ROOM_STATES.READY roomState: ROOM_STATES.READY
@ -228,6 +259,57 @@ loop.store.ActiveRoomStore = (function() {
}); });
this._setRefreshTimeout(actionData.expires); 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 * Handles leaving a room. Clears any membership timeouts, then
* signals to the server the leave of the room. * 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() { _leaveRoom: function(nextState) {
if (this._storeState.roomState !== ROOM_STATES.JOINED) { this._sdkDriver.disconnectSession();
return;
}
if (this._timeout) { if (this._timeout) {
clearTimeout(this._timeout); clearTimeout(this._timeout);
delete this._timeout; delete this._timeout;
} }
this._mozLoop.rooms.leave(this._storeState.roomToken, if (this._storeState.roomState === ROOM_STATES.JOINED ||
this._storeState.sessionToken); 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({ 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.dispatcher = options.dispatcher;
this.sdkDriver = options.sdkDriver; 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, [ this.dispatcher.register(this, [
"connectionFailure", "setupWindowData"
"connectionProgress",
"setupWindowData",
"connectCall",
"hangupCall",
"peerHungupCall",
"cancelCall",
"retryCall",
"mediaConnected",
"setMute",
"fetchEmailLink"
]); ]);
}, },
@ -196,6 +190,19 @@ loop.store.ConversationStore = (function() {
return; return;
} }
this.dispatcher.register(this, [
"connectionFailure",
"connectionProgress",
"connectCall",
"hangupCall",
"remotePeerDisconnected",
"cancelCall",
"retryCall",
"mediaConnected",
"setMute",
"fetchEmailLink"
]);
this.set({ this.set({
contact: actionData.contact, contact: actionData.contact,
outgoing: windowType === "outgoing", 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._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) { connectSession: function(sessionData) {
this.session = this.sdk.initSession(sessionData.sessionId); 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("streamCreated", this._onRemoteStreamCreated.bind(this));
this.session.on("connectionDestroyed", this.session.on("connectionDestroyed",
this._onConnectionDestroyed.bind(this)); this._onConnectionDestroyed.bind(this));
@ -130,6 +131,7 @@ loop.OTSdkDriver = (function() {
return; return;
} }
this.dispatcher.dispatch(new sharedActions.ConnectedToSdkServers());
this._sessionConnected = true; this._sessionConnected = true;
this._maybePublishLocalStream(); this._maybePublishLocalStream();
}, },
@ -141,18 +143,9 @@ loop.OTSdkDriver = (function() {
* https://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html * https://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html
*/ */
_onConnectionDestroyed: function(event) { _onConnectionDestroyed: function(event) {
var action; this.dispatcher.dispatch(new sharedActions.RemotePeerDisconnected({
if (event.reason === "clientDisconnected") { peerHungup: 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);
}, },
/** /**
@ -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. * 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, React.DOM.button({onClick: this.props.onClick,
disabled: this.props.disabled, disabled: this.props.disabled,
className: cx(classObject)}, 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} <button onClick={this.props.onClick}
disabled={this.props.disabled} disabled={this.props.disabled}
className={cx(classObject)}> className={cx(classObject)}>
{this.props.caption} <span className="button-caption">{this.props.caption}</span>
{this.props.children}
</button> </button>
) )
} }

Просмотреть файл

@ -32,7 +32,8 @@ browser.jar:
content/browser/loop/shared/img/sad.png (content/shared/img/sad.png) 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_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/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.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/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) 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/validate.js"></script>
<script type="text/javascript" src="shared/js/dispatcher.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/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="shared/js/activeRoomStore.js"></script>
<script type="text/javascript" src="js/standaloneAppStore.js"></script> <script type="text/javascript" src="js/standaloneAppStore.js"></script>
<script type="text/javascript" src="js/standaloneClient.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({ var client = new loop.StandaloneClient({
baseServerUrl: loop.config.serverUrl baseServerUrl: loop.config.serverUrl
}); });
var sdkDriver = new loop.OTSdkDriver({
dispatcher: dispatcher,
sdk: OT
});
var standaloneAppStore = new loop.store.StandaloneAppStore({ var standaloneAppStore = new loop.store.StandaloneAppStore({
conversation: conversation, conversation: conversation,
@ -996,7 +1000,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
}); });
var activeRoomStore = new loop.store.ActiveRoomStore({ var activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,
mozLoop: standaloneMozLoop mozLoop: standaloneMozLoop,
sdkDriver: sdkDriver
}); });
window.addEventListener("unload", function() { window.addEventListener("unload", function() {

Просмотреть файл

@ -987,6 +987,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
var client = new loop.StandaloneClient({ var client = new loop.StandaloneClient({
baseServerUrl: loop.config.serverUrl baseServerUrl: loop.config.serverUrl
}); });
var sdkDriver = new loop.OTSdkDriver({
dispatcher: dispatcher,
sdk: OT
});
var standaloneAppStore = new loop.store.StandaloneAppStore({ var standaloneAppStore = new loop.store.StandaloneAppStore({
conversation: conversation, conversation: conversation,
@ -996,7 +1000,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
}); });
var activeRoomStore = new loop.store.ActiveRoomStore({ var activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,
mozLoop: standaloneMozLoop mozLoop: standaloneMozLoop,
sdkDriver: sdkDriver
}); });
window.addEventListener("unload", function() { window.addEventListener("unload", function() {

Просмотреть файл

@ -143,7 +143,8 @@ describe("loop.conversation", function() {
roomStore: roomStore, roomStore: roomStore,
sdk: {}, sdk: {},
conversationStore: conversationStore, conversationStore: conversationStore,
conversationAppStore: conversationAppStore conversationAppStore: conversationAppStore,
dispatcher: dispatcher
})); }));
} }
@ -214,7 +215,7 @@ describe("loop.conversation", function() {
ccView = mountTestComponent(); ccView = mountTestComponent();
TestUtils.findRenderedComponentWithType(ccView, TestUtils.findRenderedComponentWithType(ccView,
loop.roomViews.DesktopRoomControllerView); loop.roomViews.DesktopRoomConversationView);
}); });
it("should display the GenericFailureView for failures", function() { it("should display the GenericFailureView for failures", function() {

Просмотреть файл

@ -32,7 +32,8 @@ describe("loop.roomViews", function () {
activeRoomStore = new loop.store.ActiveRoomStore({ activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,
mozLoop: {} mozLoop: {},
sdkDriver: {}
}); });
roomStore = new loop.store.RoomStore({ roomStore = new loop.store.RoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,
@ -62,6 +63,8 @@ describe("loop.roomViews", function () {
expect(testView.state).eql({ expect(testView.state).eql({
roomState: ROOM_STATES.INIT, roomState: ROOM_STATES.INIT,
audioMuted: false,
videoMuted: false,
foo: "bar" foo: "bar"
}); });
}); });
@ -77,21 +80,90 @@ describe("loop.roomViews", function () {
activeRoomStore.setStoreState({roomState: ROOM_STATES.READY}); 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; var view;
beforeEach(function() {
sandbox.stub(dispatcher, "dispatch");
});
function mountTestComponent() { function mountTestComponent() {
return TestUtils.renderIntoDocument( return TestUtils.renderIntoDocument(
new loop.roomViews.DesktopRoomControllerView({ new loop.roomViews.DesktopRoomConversationView({
mozLoop: {}, dispatcher: dispatcher,
roomStore: roomStore 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() { describe("#render", function() {
it("should set document.title to store.serverData.roomName", function() { it("should set document.title to store.serverData.roomName", function() {
mountTestComponent(); mountTestComponent();

Просмотреть файл

@ -7,7 +7,7 @@ describe("loop.store.ActiveRoomStore", function () {
"use strict"; "use strict";
var ROOM_STATES = loop.store.ROOM_STATES; var ROOM_STATES = loop.store.ROOM_STATES;
var sandbox, dispatcher, store, fakeMozLoop; var sandbox, dispatcher, store, fakeMozLoop, fakeSdkDriver;
beforeEach(function() { beforeEach(function() {
sandbox = sinon.sandbox.create(); sandbox = sinon.sandbox.create();
@ -25,8 +25,16 @@ describe("loop.store.ActiveRoomStore", function () {
} }
}; };
store = new loop.store.ActiveRoomStore( fakeSdkDriver = {
{mozLoop: fakeMozLoop, dispatcher: dispatcher}); connectSession: sandbox.stub(),
disconnectSession: sandbox.stub()
};
store = new loop.store.ActiveRoomStore({
dispatcher: dispatcher,
mozLoop: fakeMozLoop,
sdkDriver: fakeSdkDriver
});
}); });
afterEach(function() { afterEach(function() {
@ -45,6 +53,12 @@ describe("loop.store.ActiveRoomStore", function () {
new loop.store.ActiveRoomStore({dispatcher: dispatcher}); new loop.store.ActiveRoomStore({dispatcher: dispatcher});
}).to.Throw(/mozLoop/); }).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() { describe("#roomFailure", function() {
@ -281,6 +295,16 @@ describe("loop.store.ActiveRoomStore", function () {
expect(state.sessionId).eql(fakeJoinedData.sessionId); 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", it("should call mozLoop.rooms.refreshMembership before the expiresTime",
function() { function() {
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData)); 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() { describe("#windowUnload", function() {
beforeEach(function() { beforeEach(function() {
store.setStoreState({ 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() { it("should clear any existing timeout", function() {
sandbox.stub(window, "clearTimeout"); sandbox.stub(window, "clearTimeout");
store._timeout = {}; 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() { it("should clear any existing timeout", function() {
sandbox.stub(window, "clearTimeout"); sandbox.stub(window, "clearTimeout");
store._timeout = {}; store._timeout = {};

Просмотреть файл

@ -132,14 +132,14 @@ describe("loop.store.ConversationStore", function () {
}); });
it("should disconnect the session", function() { it("should disconnect the session", function() {
dispatcher.dispatch( store.connectionFailure(
new sharedActions.ConnectionFailure({reason: "fake"})); new sharedActions.ConnectionFailure({reason: "fake"}));
sinon.assert.calledOnce(sdkDriver.disconnectSession); sinon.assert.calledOnce(sdkDriver.disconnectSession);
}); });
it("should ensure the websocket is closed", function() { it("should ensure the websocket is closed", function() {
dispatcher.dispatch( store.connectionFailure(
new sharedActions.ConnectionFailure({reason: "fake"})); new sharedActions.ConnectionFailure({reason: "fake"}));
sinon.assert.calledOnce(wsCloseSpy); sinon.assert.calledOnce(wsCloseSpy);
@ -148,7 +148,7 @@ describe("loop.store.ConversationStore", function () {
it("should set the state to 'terminated'", function() { it("should set the state to 'terminated'", function() {
store.set({callState: CALL_STATES.ALERTING}); store.set({callState: CALL_STATES.ALERTING});
dispatcher.dispatch( store.connectionFailure(
new sharedActions.ConnectionFailure({reason: "fake"})); new sharedActions.ConnectionFailure({reason: "fake"}));
expect(store.get("callState")).eql(CALL_STATES.TERMINATED); expect(store.get("callState")).eql(CALL_STATES.TERMINATED);
@ -156,7 +156,7 @@ describe("loop.store.ConversationStore", function () {
}); });
it("should release mozLoop callsData", function() { it("should release mozLoop callsData", function() {
dispatcher.dispatch( store.connectionFailure(
new sharedActions.ConnectionFailure({reason: "fake"})); new sharedActions.ConnectionFailure({reason: "fake"}));
sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress); 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() { it("should change the state from 'gather' to 'connecting'", function() {
store.set({callState: CALL_STATES.GATHER}); store.set({callState: CALL_STATES.GATHER});
dispatcher.dispatch( store.connectionProgress(
new sharedActions.ConnectionProgress({wsState: WS_STATES.INIT})); new sharedActions.ConnectionProgress({wsState: WS_STATES.INIT}));
expect(store.get("callState")).eql(CALL_STATES.CONNECTING); 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() { it("should change the state from 'gather' to 'alerting'", function() {
store.set({callState: CALL_STATES.GATHER}); store.set({callState: CALL_STATES.GATHER});
dispatcher.dispatch( store.connectionProgress(
new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING})); new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING}));
expect(store.get("callState")).eql(CALL_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() { it("should change the state from 'init' to 'alerting'", function() {
store.set({callState: CALL_STATES.INIT}); store.set({callState: CALL_STATES.INIT});
dispatcher.dispatch( store.connectionProgress(
new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING})); new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING}));
expect(store.get("callState")).eql(CALL_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() { it("should change the state to 'ongoing'", function() {
dispatcher.dispatch( store.connectionProgress(
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING})); new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
expect(store.get("callState")).eql(CALL_STATES.ONGOING); expect(store.get("callState")).eql(CALL_STATES.ONGOING);
@ -212,7 +212,7 @@ describe("loop.store.ConversationStore", function () {
it("should connect the session", function() { it("should connect the session", function() {
store.set(fakeSessionData); store.set(fakeSessionData);
dispatcher.dispatch( store.connectionProgress(
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING})); new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
sinon.assert.calledOnce(sdkDriver.connectSession); sinon.assert.calledOnce(sdkDriver.connectSession);
@ -386,7 +386,7 @@ describe("loop.store.ConversationStore", function () {
describe("#connectCall", function() { describe("#connectCall", function() {
it("should save the call session data", function() { it("should save the call session data", function() {
dispatcher.dispatch( store.connectCall(
new sharedActions.ConnectCall({sessionData: fakeSessionData})); new sharedActions.ConnectCall({sessionData: fakeSessionData}));
expect(store.get("apiKey")).eql("fakeKey"); expect(store.get("apiKey")).eql("fakeKey");
@ -403,7 +403,7 @@ describe("loop.store.ConversationStore", function () {
on: sinon.spy() on: sinon.spy()
}); });
dispatcher.dispatch( store.connectCall(
new sharedActions.ConnectCall({sessionData: fakeSessionData})); new sharedActions.ConnectCall({sessionData: fakeSessionData}));
sinon.assert.calledOnce(loop.CallConnectionWebSocket); sinon.assert.calledOnce(loop.CallConnectionWebSocket);
@ -415,7 +415,7 @@ describe("loop.store.ConversationStore", function () {
}); });
it("should connect the websocket to the server", function() { it("should connect the websocket to the server", function() {
dispatcher.dispatch( store.connectCall(
new sharedActions.ConnectCall({sessionData: fakeSessionData})); new sharedActions.ConnectCall({sessionData: fakeSessionData}));
sinon.assert.calledOnce(store._websocket.promiseConnect); sinon.assert.calledOnce(store._websocket.promiseConnect);
@ -423,7 +423,7 @@ describe("loop.store.ConversationStore", function () {
describe("WebSocket connection result", function() { describe("WebSocket connection result", function() {
beforeEach(function() { beforeEach(function() {
dispatcher.dispatch( store.connectCall(
new sharedActions.ConnectCall({sessionData: fakeSessionData})); new sharedActions.ConnectCall({sessionData: fakeSessionData}));
sandbox.stub(dispatcher, "dispatch"); sandbox.stub(dispatcher, "dispatch");
@ -480,31 +480,31 @@ describe("loop.store.ConversationStore", function () {
}); });
it("should disconnect the session", function() { it("should disconnect the session", function() {
dispatcher.dispatch(new sharedActions.HangupCall()); store.hangupCall(new sharedActions.HangupCall());
sinon.assert.calledOnce(sdkDriver.disconnectSession); sinon.assert.calledOnce(sdkDriver.disconnectSession);
}); });
it("should send a media-fail message to the websocket if it is open", function() { 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); sinon.assert.calledOnce(wsMediaFailSpy);
}); });
it("should ensure the websocket is closed", function() { it("should ensure the websocket is closed", function() {
dispatcher.dispatch(new sharedActions.HangupCall()); store.hangupCall(new sharedActions.HangupCall());
sinon.assert.calledOnce(wsCloseSpy); sinon.assert.calledOnce(wsCloseSpy);
}); });
it("should set the callState to finished", function() { 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); expect(store.get("callState")).eql(CALL_STATES.FINISHED);
}); });
it("should release mozLoop callsData", function() { 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.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
sinon.assert.calledWithExactly( sinon.assert.calledWithExactly(
@ -512,7 +512,7 @@ describe("loop.store.ConversationStore", function () {
}); });
}); });
describe("#peerHungupCall", function() { describe("#remotePeerDisconnected", function() {
var wsMediaFailSpy, wsCloseSpy; var wsMediaFailSpy, wsCloseSpy;
beforeEach(function() { beforeEach(function() {
wsMediaFailSpy = sinon.spy(); wsMediaFailSpy = sinon.spy();
@ -527,30 +527,56 @@ describe("loop.store.ConversationStore", function () {
}); });
it("should disconnect the session", function() { it("should disconnect the session", function() {
dispatcher.dispatch(new sharedActions.PeerHungupCall()); store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
peerHungup: true
}));
sinon.assert.calledOnce(sdkDriver.disconnectSession); sinon.assert.calledOnce(sdkDriver.disconnectSession);
}); });
it("should ensure the websocket is closed", function() { it("should ensure the websocket is closed", function() {
dispatcher.dispatch(new sharedActions.PeerHungupCall()); store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
peerHungup: true
}));
sinon.assert.calledOnce(wsCloseSpy); 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() { 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.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
sinon.assert.calledWithExactly( sinon.assert.calledWithExactly(
navigator.mozLoop.calls.clearCallInProgress, "42"); 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() { describe("#cancelCall", function() {
@ -562,25 +588,25 @@ describe("loop.store.ConversationStore", function () {
}); });
it("should disconnect the session", function() { it("should disconnect the session", function() {
dispatcher.dispatch(new sharedActions.CancelCall()); store.cancelCall(new sharedActions.CancelCall());
sinon.assert.calledOnce(sdkDriver.disconnectSession); sinon.assert.calledOnce(sdkDriver.disconnectSession);
}); });
it("should send a cancel message to the websocket if it is open", function() { 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); sinon.assert.calledOnce(wsCancelSpy);
}); });
it("should ensure the websocket is closed", function() { it("should ensure the websocket is closed", function() {
dispatcher.dispatch(new sharedActions.CancelCall()); store.cancelCall(new sharedActions.CancelCall());
sinon.assert.calledOnce(wsCloseSpy); sinon.assert.calledOnce(wsCloseSpy);
}); });
it("should set the state to close if the call is connecting", function() { 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); 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() { it("should set the state to close if the call has terminated already", function() {
store.set({callState: CALL_STATES.TERMINATED}); store.set({callState: CALL_STATES.TERMINATED});
dispatcher.dispatch(new sharedActions.CancelCall()); store.cancelCall(new sharedActions.CancelCall());
expect(store.get("callState")).eql(CALL_STATES.CLOSE); expect(store.get("callState")).eql(CALL_STATES.CLOSE);
}); });
it("should release mozLoop callsData", function() { 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.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
sinon.assert.calledWithExactly( sinon.assert.calledWithExactly(
@ -606,7 +632,7 @@ describe("loop.store.ConversationStore", function () {
it("should set the state to gather", function() { it("should set the state to gather", function() {
store.set({callState: CALL_STATES.TERMINATED}); store.set({callState: CALL_STATES.TERMINATED});
dispatcher.dispatch(new sharedActions.RetryCall()); store.retryCall(new sharedActions.RetryCall());
expect(store.get("callState")).eql(CALL_STATES.GATHER); expect(store.get("callState")).eql(CALL_STATES.GATHER);
}); });
@ -619,7 +645,7 @@ describe("loop.store.ConversationStore", function () {
contact: contact contact: contact
}); });
dispatcher.dispatch(new sharedActions.RetryCall()); store.retryCall(new sharedActions.RetryCall());
sinon.assert.calledOnce(client.setupOutgoingCall); sinon.assert.calledOnce(client.setupOutgoingCall);
sinon.assert.calledWith(client.setupOutgoingCall, sinon.assert.calledWith(client.setupOutgoingCall,
@ -631,7 +657,7 @@ describe("loop.store.ConversationStore", function () {
it("should send mediaUp via the websocket", function() { it("should send mediaUp via the websocket", function() {
store._websocket = fakeWebsocket; store._websocket = fakeWebsocket;
dispatcher.dispatch(new sharedActions.MediaConnected()); store.mediaConnected(new sharedActions.MediaConnected());
sinon.assert.calledOnce(wsMediaUpSpy); sinon.assert.calledOnce(wsMediaUpSpy);
}); });
@ -663,7 +689,7 @@ describe("loop.store.ConversationStore", function () {
describe("#fetchEmailLink", function() { describe("#fetchEmailLink", function() {
it("should request a new call url to the server", 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.calledOnce(client.requestCallUrl);
sinon.assert.calledWith(client.requestCallUrl, ""); sinon.assert.calledWith(client.requestCallUrl, "");
@ -674,7 +700,7 @@ describe("loop.store.ConversationStore", function () {
client.requestCallUrl = function(callId, cb) { client.requestCallUrl = function(callId, cb) {
cb(null, {callUrl: "http://fake.invalid/"}); cb(null, {callUrl: "http://fake.invalid/"});
}; };
dispatcher.dispatch(new sharedActions.FetchEmailLink()); store.fetchEmailLink(new sharedActions.FetchEmailLink());
expect(store.get("emailLink")).eql("http://fake.invalid/"); expect(store.get("emailLink")).eql("http://fake.invalid/");
}); });
@ -685,7 +711,7 @@ describe("loop.store.ConversationStore", function () {
client.requestCallUrl = function(callId, cb) { client.requestCallUrl = function(callId, cb) {
cb("error"); cb("error");
}; };
dispatcher.dispatch(new sharedActions.FetchEmailLink()); store.fetchEmailLink(new sharedActions.FetchEmailLink());
sinon.assert.calledOnce(trigger); sinon.assert.calledOnce(trigger);
sinon.assert.calledWithExactly(trigger, "error:emailLink"); sinon.assert.calledWithExactly(trigger, "error:emailLink");
@ -695,7 +721,7 @@ describe("loop.store.ConversationStore", function () {
describe("Events", function() { describe("Events", function() {
describe("Websocket progress", function() { describe("Websocket progress", function() {
beforeEach(function() { beforeEach(function() {
dispatcher.dispatch( store.connectCall(
new sharedActions.ConnectCall({sessionData: fakeSessionData})); new sharedActions.ConnectCall({sessionData: fakeSessionData}));
sandbox.stub(dispatcher, "dispatch"); sandbox.stub(dispatcher, "dispatch");

Просмотреть файл

@ -231,26 +231,30 @@ describe("loop.OTSdkDriver", function () {
}); });
describe("connectionDestroyed", function() { describe("connectionDestroyed", function() {
it("should dispatch a peerHungupCall action if the client disconnected", function() { it("should dispatch a remotePeerDisconnected action if the client" +
session.trigger("connectionDestroyed", { "disconnected", function() {
reason: "clientDisconnected" 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); it("should dispatch a remotePeerDisconnected action if the connection" +
sinon.assert.calledWithMatch(dispatcher.dispatch, "failed", function() {
sinon.match.hasOwn("name", "peerHungupCall")); session.trigger("connectionDestroyed", {
}); reason: "networkDisconnected"
});
it("should dispatch a connectionFailure action if the connection failed", function() { sinon.assert.calledOnce(dispatcher.dispatch);
session.trigger("connectionDestroyed", { sinon.assert.calledWithMatch(dispatcher.dispatch,
reason: "networkDisconnected" sinon.match.hasOwn("name", "remotePeerDisconnected"));
}); sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("peerHungup", false));
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"));
}); });
}); });
@ -296,5 +300,33 @@ describe("loop.OTSdkDriver", function () {
sinon.match.hasOwn("name", "mediaConnected")); 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() { beforeEach(function() {
activeRoomStore = new loop.store.ActiveRoomStore({ activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,
mozLoop: fakeMozLoop mozLoop: fakeMozLoop,
sdkDriver: {}
}); });
store = new loop.store.RoomStore({ store = new loop.store.RoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,

Просмотреть файл

@ -41,6 +41,7 @@
<script src="../../content/shared/js/validate.js"></script> <script src="../../content/shared/js/validate.js"></script>
<script src="../../content/shared/js/dispatcher.js"></script> <script src="../../content/shared/js/dispatcher.js"></script>
<script src="../../content/shared/js/activeRoomStore.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/multiplexGum.js"></script>
<script src="../../standalone/content/js/standaloneAppStore.js"></script> <script src="../../standalone/content/js/standaloneAppStore.js"></script>
<script src="../../standalone/content/js/standaloneClient.js"></script> <script src="../../standalone/content/js/standaloneClient.js"></script>

Просмотреть файл

@ -612,7 +612,8 @@ describe("loop.webapp", function() {
dispatcher = new loop.Dispatcher(); dispatcher = new loop.Dispatcher();
activeRoomStore = new loop.store.ActiveRoomStore({ activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,
mozLoop: {} mozLoop: {},
sdkDriver: {}
}); });
standaloneAppStore = new loop.store.StandaloneAppStore({ standaloneAppStore = new loop.store.StandaloneAppStore({
dispatcher: dispatcher, dispatcher: dispatcher,

Просмотреть файл

@ -22,7 +22,6 @@
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView; var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
var CallFailedView = loop.conversationViews.CallFailedView; var CallFailedView = loop.conversationViews.CallFailedView;
var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView; var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
var DesktopRoomInvitationView = loop.roomViews.DesktopRoomInvitationView;
// 2. Standalone webapp // 2. Standalone webapp
var HomeView = loop.webapp.HomeView; var HomeView = loop.webapp.HomeView;
@ -39,6 +38,9 @@
var ConversationView = loop.shared.views.ConversationView; var ConversationView = loop.shared.views.ConversationView;
var FeedbackView = loop.shared.views.FeedbackView; var FeedbackView = loop.shared.views.FeedbackView;
// Room constants
var ROOM_STATES = loop.store.ROOM_STATES;
// Local helpers // Local helpers
function returnTrue() { function returnTrue() {
return true; return true;
@ -61,7 +63,8 @@
var dispatcher = new loop.Dispatcher(); var dispatcher = new loop.Dispatcher();
var activeRoomStore = new loop.store.ActiveRoomStore({ var activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,
mozLoop: navigator.mozLoop mozLoop: navigator.mozLoop,
sdkDriver: {}
}); });
var roomStore = new loop.store.RoomStore({ var roomStore = new loop.store.RoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,
@ -533,20 +536,24 @@
) )
), ),
Section({name: "DesktopRoomInvitationView"}, Section({name: "DesktopRoomConversationView"},
Example({summary: "Desktop room invitation", dashed: "true", Example({summary: "Desktop room conversation (invitation)", dashed: "true",
style: {width: "260px", height: "265px"}}, style: {width: "260px", height: "265px"}},
React.DOM.div({className: "fx-embedded"}, 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", Example({summary: "Desktop room conversation", dashed: "true",
style: {width: "260px", height: "265px"}}, style: {width: "260px", height: "265px"}},
React.DOM.div({className: "fx-embedded"}, 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 DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
var CallFailedView = loop.conversationViews.CallFailedView; var CallFailedView = loop.conversationViews.CallFailedView;
var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView; var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
var DesktopRoomInvitationView = loop.roomViews.DesktopRoomInvitationView;
// 2. Standalone webapp // 2. Standalone webapp
var HomeView = loop.webapp.HomeView; var HomeView = loop.webapp.HomeView;
@ -39,6 +38,9 @@
var ConversationView = loop.shared.views.ConversationView; var ConversationView = loop.shared.views.ConversationView;
var FeedbackView = loop.shared.views.FeedbackView; var FeedbackView = loop.shared.views.FeedbackView;
// Room constants
var ROOM_STATES = loop.store.ROOM_STATES;
// Local helpers // Local helpers
function returnTrue() { function returnTrue() {
return true; return true;
@ -61,7 +63,8 @@
var dispatcher = new loop.Dispatcher(); var dispatcher = new loop.Dispatcher();
var activeRoomStore = new loop.store.ActiveRoomStore({ var activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,
mozLoop: navigator.mozLoop mozLoop: navigator.mozLoop,
sdkDriver: {}
}); });
var roomStore = new loop.store.RoomStore({ var roomStore = new loop.store.RoomStore({
dispatcher: dispatcher, dispatcher: dispatcher,
@ -533,20 +536,24 @@
</Example> </Example>
</Section> </Section>
<Section name="DesktopRoomInvitationView"> <Section name="DesktopRoomConversationView">
<Example summary="Desktop room invitation" dashed="true" <Example summary="Desktop room conversation (invitation)" dashed="true"
style={{width: "260px", height: "265px"}}> style={{width: "260px", height: "265px"}}>
<div className="fx-embedded"> <div className="fx-embedded">
<DesktopRoomInvitationView roomStore={roomStore} /> <DesktopRoomConversationView
roomStore={roomStore}
dispatcher={dispatcher}
roomState={ROOM_STATES.INIT} />
</div> </div>
</Example> </Example>
</Section>
<Section name="DesktopRoomConversationView">
<Example summary="Desktop room conversation" dashed="true" <Example summary="Desktop room conversation" dashed="true"
style={{width: "260px", height: "265px"}}> style={{width: "260px", height: "265px"}}>
<div className="fx-embedded"> <div className="fx-embedded">
<DesktopRoomConversationView roomStore={roomStore} /> <DesktopRoomConversationView
roomStore={roomStore}
dispatcher={dispatcher}
roomState={ROOM_STATES.HAS_PARTICIPANTS} />
</div> </div>
</Example> </Example>
</Section> </Section>

Просмотреть файл

@ -635,21 +635,8 @@ let SessionStoreInternal = {
let uri = activePageData ? activePageData.url || null : null; let uri = activePageData ? activePageData.url || null : null;
browser.userTypedValue = uri; browser.userTypedValue = uri;
// If the page has a title, set it. // Update tab label and icon again after the tab history was updated.
if (activePageData) { this.updateTabLabelAndIcon(tab, tabData);
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);
}
let event = win.document.createEvent("Events"); let event = win.document.createEvent("Events");
event.initEvent("SSTabRestoring", true, false); 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 * Restores the session state stored in LastSession. This will attempt
* to merge data into the current session. If a window was opened at startup * 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; 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. // Restore all tabs.
for (let t = 0; t < aTabs.length; t++) { 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", browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory",
{tabData: tabData, epoch: epoch}); {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. // Restore tab attributes.
if ("attributes" in tabData) { if ("attributes" in tabData) {
TabAttributes.set(tab, tabData.attributes); TabAttributes.set(tab, tabData.attributes);

Просмотреть файл

@ -761,6 +761,10 @@ Toolbox.prototype = {
let tiltEnabled = !this.target.isMultiProcess && let tiltEnabled = !this.target.isMultiProcess &&
Services.prefs.getBoolPref("devtools.command-button-tilt.enabled"); Services.prefs.getBoolPref("devtools.command-button-tilt.enabled");
let tiltButton = this.doc.getElementById("command-button-tilt"); 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) { if (tiltEnabled) {
tiltButton.removeAttribute("hidden"); tiltButton.removeAttribute("hidden");

Просмотреть файл

@ -76,6 +76,14 @@ exports.showDoorhanger = Task.async(function *({ window, type, anchor }) {
return; 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 document = window.document;
let panel = document.createElementNS(XULNS, "panel"); let panel = document.createElementNS(XULNS, "panel");
@ -108,9 +116,6 @@ exports.showDoorhanger = Task.async(function *({ window, type, anchor }) {
close(); close();
}); });
} }
// Call success function to set preferences, etc.
success();
}); });
function setDoorhangerStyle (panel, frame) { function setDoorhangerStyle (panel, frame) {
@ -147,3 +152,9 @@ function onFrameLoad (frame) {
function getGBrowser () { function getGBrowser () {
return getMostRecentBrowserWindow().gBrowser; 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", userHistogram: "DEVTOOLS_NETMONITOR_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_NETMONITOR_TIME_ACTIVE_SECONDS" 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: { tilt: {
histogram: "DEVTOOLS_TILT_OPENED_BOOLEAN", histogram: "DEVTOOLS_TILT_OPENED_BOOLEAN",
userHistogram: "DEVTOOLS_TILT_OPENED_PER_USER_FLAG", 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_netmonitor.js]
[browser_telemetry_toolboxtabs_options.js] [browser_telemetry_toolboxtabs_options.js]
[browser_telemetry_toolboxtabs_shadereditor.js] [browser_telemetry_toolboxtabs_shadereditor.js]
[browser_telemetry_toolboxtabs_storage.js]
[browser_telemetry_toolboxtabs_styleeditor.js] [browser_telemetry_toolboxtabs_styleeditor.js]
[browser_telemetry_toolboxtabs_webaudioeditor.js] [browser_telemetry_toolboxtabs_webaudioeditor.js]
[browser_telemetry_toolboxtabs_webconsole.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() { destroy: function() {
if (!this._destroyed) { if (!this._destroyed) {

Просмотреть файл

@ -21,6 +21,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "ViewHelpers",
XPCOMUtils.defineLazyModuleGetter(this, "VariablesView", XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
"resource:///modules/devtools/VariablesView.jsm"); "resource:///modules/devtools/VariablesView.jsm");
let Telemetry = require("devtools/shared/telemetry");
/** /**
* Localization convenience methods. * Localization convenience methods.
*/ */
@ -85,6 +87,9 @@ this.StorageUI = function StorageUI(front, target, panelWin) {
this.handleKeypress = this.handleKeypress.bind(this); this.handleKeypress = this.handleKeypress.bind(this);
this._panelDoc.addEventListener("keypress", this.handleKeypress); this._panelDoc.addEventListener("keypress", this.handleKeypress);
this._telemetry = new Telemetry();
this._telemetry.toolOpened("storage");
} }
exports.StorageUI = StorageUI; exports.StorageUI = StorageUI;
@ -97,6 +102,7 @@ StorageUI.prototype = {
destroy: function() { destroy: function() {
this.front.off("stores-update", this.onUpdate); this.front.off("stores-update", this.onUpdate);
this._panelDoc.removeEventListener("keypress", this.handleKeypress); this._panelDoc.removeEventListener("keypress", this.handleKeypress);
this._telemetry.toolClosed("storage");
}, },
/** /**

Просмотреть файл

@ -285,7 +285,14 @@ let UI = {
this.cancelBusyTimeout(); this.cancelBusyTimeout();
this.unbusy(); this.unbusy();
}, (e) => { }, (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(); this.cancelBusyTimeout();
let operationCanceled = e && e.canceled; let operationCanceled = e && e.canceled;
if (!operationCanceled) { if (!operationCanceled) {
@ -817,7 +824,8 @@ let UI = {
playCmd.setAttribute("disabled", "true"); playCmd.setAttribute("disabled", "true");
stopCmd.setAttribute("disabled", "true"); stopCmd.setAttribute("disabled", "true");
} else { } else {
if (AppManager.selectedProject.errorsCount == 0) { if (AppManager.selectedProject.errorsCount == 0 &&
AppManager.runtimeCanHandleApps()) {
playCmd.removeAttribute("disabled"); playCmd.removeAttribute("disabled");
} else { } else {
playCmd.setAttribute("disabled", "true"); playCmd.setAttribute("disabled", "true");

Просмотреть файл

@ -106,18 +106,28 @@ let AppManager = exports.AppManager = {
this._listTabsResponse = null; this._listTabsResponse = null;
} else { } else {
this.connection.client.listTabs((response) => { this.connection.client.listTabs((response) => {
let front = new AppActorFront(this.connection.client, if (response.webappsActor) {
response); let front = new AppActorFront(this.connection.client,
front.on("install-progress", this.onInstallProgress); response);
front.watchApps(() => this.checkIfProjectIsRunning()) front.on("install-progress", this.onInstallProgress);
.then(() => front.fetchIcons()) front.watchApps(() => this.checkIfProjectIsRunning())
.then(() => { .then(() => {
this._appsFront = front; // This can't be done earlier as many operations
this.checkIfProjectIsRunning(); // in the apps actor require watchApps to be called
this.update("runtime-apps-found"); // first.
}); this._appsFront = front;
this._listTabsResponse = response; this._listTabsResponse = response;
this.update("list-tabs-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() { installAndRunProject: function() {
let project = this.selectedProject; let project = this.selectedProject;
@ -429,6 +443,11 @@ let AppManager = exports.AppManager = {
return promise.reject("Can't install"); 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* () { return Task.spawn(function* () {
let self = AppManager; let self = AppManager;

Просмотреть файл

@ -29,7 +29,7 @@
let fakeRuntime = { let fakeRuntime = {
type: "USB", type: "USB",
connect: function(connection) { 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.host = null; // force connectPipe
connection.connect(); connection.connect();
return promise.resolve(); return promise.resolve();

Просмотреть файл

@ -51,7 +51,7 @@
win.AppManager.runtimeList.usb.push({ win.AppManager.runtimeList.usb.push({
connect: function(connection) { 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.host = null; // force connectPipe
connection.connect(); connection.connect();
return promise.resolve(); return promise.resolve();
@ -84,6 +84,8 @@
is(Object.keys(DebuggerServer._connections).length, 1, "Connected"); is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
yield waitForUpdate(win, "list-tabs-response");
ok(isPlayActive(), "play button is enabled 1"); ok(isPlayActive(), "play button is enabled 1");
ok(!isStopActive(), "stop button is disabled 1"); ok(!isStopActive(), "stop button is disabled 1");
let oldProject = win.AppManager.selectedProject; let oldProject = win.AppManager.selectedProject;

Просмотреть файл

@ -9,3 +9,5 @@ skip-if = debug # bug 1058695
[browser_pdfjs_savedialog.js] [browser_pdfjs_savedialog.js]
[browser_pdfjs_views.js] [browser_pdfjs_views.js]
skip-if = debug # bug 1058695 skip-if = debug # bug 1058695
[browser_pdfjs_zoom.js]
skip-if = debug # bug 1058695

Просмотреть файл

@ -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) AC_DEFINE_UNQUOTED(ATK_REV_VERSION, $ATK_REV_VERSION)
fi fi
if test -z "$RELEASE_BUILD" -a -z "$NIGHTLY_BUILD"; then if test -n "$MOZ_DEV_EDITION"; then
AC_DEFINE(MOZ_DEV_EDITION) AC_DEFINE(MOZ_DEV_EDITION)
fi fi

Просмотреть файл

@ -360,21 +360,27 @@ Promise::MaybeReject(const nsRefPtr<MediaStreamError>& aArg) {
MaybeSomething(aArg, &Promise::MaybeReject); MaybeSomething(aArg, &Promise::MaybeReject);
} }
void bool
Promise::PerformMicroTaskCheckpoint() Promise::PerformMicroTaskCheckpoint()
{ {
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get(); CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
nsTArray<nsRefPtr<nsIRunnable>>& microtaskQueue = nsTArray<nsRefPtr<nsIRunnable>>& microtaskQueue =
runtime->GetPromiseMicroTaskQueue(); runtime->GetPromiseMicroTaskQueue();
while (!microtaskQueue.IsEmpty()) { if (microtaskQueue.IsEmpty()) {
return false;
}
do {
nsRefPtr<nsIRunnable> runnable = microtaskQueue.ElementAt(0); nsRefPtr<nsIRunnable> runnable = microtaskQueue.ElementAt(0);
MOZ_ASSERT(runnable); MOZ_ASSERT(runnable);
// This function can re-enter, so we remove the element before calling. // This function can re-enter, so we remove the element before calling.
microtaskQueue.RemoveElementAt(0); microtaskQueue.RemoveElementAt(0);
runnable->Run(); runnable->Run();
} } while (!microtaskQueue.IsEmpty());
return true;
} }
/* static */ bool /* static */ bool

Просмотреть файл

@ -121,7 +121,8 @@ public:
// the T values we support. // the T values we support.
// Called by DOM to let us execute our callbacks. May be called recursively. // 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 // WebIDL

Просмотреть файл

@ -4243,7 +4243,7 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
// Only perform the Promise microtask checkpoint on the outermost event // Only perform the Promise microtask checkpoint on the outermost event
// loop. Don't run it, for example, during sync XHR or importScripts. // loop. Don't run it, for example, during sync XHR or importScripts.
Promise::PerformMicroTaskCheckpoint(); (void)Promise::PerformMicroTaskCheckpoint();
if (NS_HasPendingEvents(mThread)) { if (NS_HasPendingEvents(mThread)) {
// Now *might* be a good time to GC. Let the JS engine make the decision. // 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; return NS_OK;
} }
namespace {
class DummyRunnable : public nsRunnable {
public:
NS_IMETHOD Run() { return NS_OK; }
};
} // anonymous namespace
NS_IMETHODIMP NS_IMETHODIMP
nsXPConnect::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait, nsXPConnect::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
uint32_t aRecursionDepth) uint32_t aRecursionDepth)
{ {
MOZ_ASSERT(NS_IsMainThread());
// If ProcessNextEvent was called during a Promise "then" callback, we // If ProcessNextEvent was called during a Promise "then" callback, we
// must process any pending microtasks before blocking in the event loop, // must process any pending microtasks before blocking in the event loop,
// otherwise we may deadlock until an event enters the queue later. // otherwise we may deadlock until an event enters the queue later.
if (aMayWait) { 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. // 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 // Push a null JSContext so that we don't see any script during
// event processing. // event processing.
MOZ_ASSERT(NS_IsMainThread());
bool ok = PushJSContextNoScriptContext(nullptr); bool ok = PushJSContextNoScriptContext(nullptr);
NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
return NS_OK; return NS_OK;

Просмотреть файл

@ -903,9 +903,6 @@ OnSharedPreferenceChangeListener
if (GeckoAppShell.getGeckoInterface() != null) { if (GeckoAppShell.getGeckoInterface() != null) {
intent.putExtra("user_agent", GeckoAppShell.getGeckoInterface().getDefaultUAString()); intent.putExtra("user_agent", GeckoAppShell.getGeckoInterface().getDefaultUAString());
} }
if (!AppConstants.MOZILLA_OFFICIAL) {
intent.putExtra("is_debug", true);
}
broadcastAction(context, intent); broadcastAction(context, intent);
} }

Просмотреть файл

@ -146,3 +146,4 @@ skip-if = android_version == "10"
skip-if = android_version == "10" skip-if = android_version == "10"
[testStumblerSetting] [testStumblerSetting]
skip-if = android_version == "10"

Просмотреть файл

@ -7,7 +7,7 @@ package org.mozilla.mozstumbler.service;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
public class AppGlobals { 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. */ /* All intent actions start with this string. Only locally broadcasted. */
public static final String ACTION_NAMESPACE = "org.mozilla.mozstumbler.intent.action"; 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_ENABLED = "stumbler-test-setting-enabled";
public static final String ACTION_TEST_SETTING_DISABLED = "stumbler-test-setting-disabled"; public static final String ACTION_TEST_SETTING_DISABLED = "stumbler-test-setting-disabled";
} }

Просмотреть файл

@ -14,7 +14,7 @@ import android.text.TextUtils;
import android.util.Log; import android.util.Log;
public final class Prefs { 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 NICKNAME_PREF = "nickname";
private static final String USER_AGENT_PREF = "user-agent"; private static final String USER_AGENT_PREF = "user-agent";
private static final String VALUES_VERSION_PREF = "values_version"; 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. * is a good time to try upload, as it is likely that the network is in use.
*/ */
public class PassiveServiceReceiver extends BroadcastReceiver { 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 @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -37,6 +39,11 @@ public class PassiveServiceReceiver extends BroadcastReceiver {
return; 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 String action = intent.getAction();
final boolean isIntentFromHostApp = (action != null) && action.contains(".STUMBLER_PREF"); final boolean isIntentFromHostApp = (action != null) && action.contains(".STUMBLER_PREF");
if (!isIntentFromHostApp) { if (!isIntentFromHostApp) {
@ -47,9 +54,6 @@ public class PassiveServiceReceiver extends BroadcastReceiver {
return; return;
} }
if (intent.hasExtra("is_debug")) {
AppGlobals.isDebug = intent.getBooleanExtra("is_debug", false);
}
StumblerService.sFirefoxStumblingEnabled.set(intent.getBooleanExtra("enabled", false)); StumblerService.sFirefoxStumblingEnabled.set(intent.getBooleanExtra("enabled", false));
if (!StumblerService.sFirefoxStumblingEnabled.get()) { if (!StumblerService.sFirefoxStumblingEnabled.get()) {

Просмотреть файл

@ -30,7 +30,7 @@ import org.mozilla.mozstumbler.service.stumblerthread.scanners.GPSScanner;
import org.mozilla.mozstumbler.service.stumblerthread.scanners.WifiScanner; import org.mozilla.mozstumbler.service.stumblerthread.scanners.WifiScanner;
public final class Reporter extends BroadcastReceiver { 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"; public static final String ACTION_FLUSH_TO_BUNDLE = AppGlobals.ACTION_NAMESPACE + ".FLUSH";
private boolean mIsStarted; private boolean mIsStarted;
@ -195,7 +195,8 @@ public final class Reporter extends BroadcastReceiver {
} }
if (AppGlobals.isDebug) { 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) { if (wifiCount + cellCount < 1) {

Просмотреть файл

@ -27,7 +27,7 @@ import org.mozilla.mozstumbler.service.utils.PersistentIntentService;
// //
public class StumblerService extends PersistentIntentService public class StumblerService extends PersistentIntentService
implements DataStorageManager.StorageIsEmptyTracker { 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_BASE = AppGlobals.ACTION_NAMESPACE;
public static final String ACTION_START_PASSIVE = ACTION_BASE + ".START_PASSIVE"; public static final String ACTION_START_PASSIVE = ACTION_BASE + ".START_PASSIVE";
public static final String ACTION_EXTRA_MOZ_API_KEY = ACTION_BASE + ".MOZKEY"; public static final String ACTION_EXTRA_MOZ_API_KEY = ACTION_BASE + ".MOZKEY";
@ -144,6 +144,8 @@ public class StumblerService extends PersistentIntentService
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
UploadAlarmReceiver.cancelAlarm(this, !mScanManager.isPassiveMode());
if (!mScanManager.isScanning()) { if (!mScanManager.isScanning()) {
return; return;
} }
@ -198,7 +200,11 @@ public class StumblerService extends PersistentIntentService
return; 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 // non-empty on startup, schedule an upload
// This is the only upload trigger in Firefox mode // 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 // 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; import java.util.regex.Pattern;
public final class BSSIDBlockList { 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 NULL_BSSID = "000000000000";
private static final String WILDCARD_BSSID = "ffffffffffff"; private static final String WILDCARD_BSSID = "ffffffffffff";
private static final Pattern BSSID_PATTERN = Pattern.compile("([0-9a-f]{12})"); 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. * when the service is destroyed.
*/ */
public class DataStorageManager { 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. // 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. // 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) { private String getStorageDir(Context c) {
File dir = null; File dir = c.getFilesDir();
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();
}
if (!dir.exists()) { if (!dir.exists()) {
boolean ok = dir.mkdirs(); boolean ok = dir.mkdirs();
if (!ok) { if (!ok) {
@ -414,9 +402,6 @@ public class DataStorageManager {
} }
final String result = sb.append(kSuffix).toString(); final String result = sb.append(kSuffix).toString();
if (AppGlobals.isDebug) {
Log.d(LOG_TAG, result);
}
return 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_STATUS_ARG_SATS = "sats";
public static final String NEW_LOCATION_ARG_LOCATION = "location"; 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 int MIN_SAT_USED_IN_FIX = 3;
private static final long ACTIVE_MODE_GPS_MIN_UPDATE_TIME_MS = 1000; private static final long ACTIVE_MODE_GPS_MIN_UPDATE_TIME_MS = 1000;
private static final float ACTIVE_MODE_GPS_MIN_UPDATE_DISTANCE_M = 10; private static final float ACTIVE_MODE_GPS_MIN_UPDATE_DISTANCE_M = 10;
@ -191,15 +191,10 @@ public class GPSScanner implements LocationListener {
sendToLogActivity(logMsg); sendToLogActivity(logMsg);
if (mBlockList.contains(location)) { if (mBlockList.contains(location)) {
Log.w(LOG_TAG, "Blocked location: " + location);
reportLocationLost(); reportLocationLost();
return; return;
} }
if (AppGlobals.isDebug) {
Log.d(LOG_TAG, "New location: " + location);
}
mLocation = location; mLocation = location;
if (!mAutoGeofencing) { if (!mAutoGeofencing) {

Просмотреть файл

@ -10,7 +10,7 @@ import org.mozilla.mozstumbler.service.AppGlobals;
import org.mozilla.mozstumbler.service.Prefs; import org.mozilla.mozstumbler.service.Prefs;
public final class LocationBlockList { 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 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 double MIN_ALTITUDE = -418; // Dead Sea's altitude in meters
private static final float MAX_SPEED = 340.29f; // Mach 1 in meters/second 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; import java.util.TimerTask;
public class ScanManager { 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 Timer mPassiveModeFlushTimer;
private Context mContext; private Context mContext;
private boolean mIsScanning; private boolean mIsScanning;

Просмотреть файл

@ -40,7 +40,7 @@ public class WifiScanner extends BroadcastReceiver {
public static final int STATUS_ACTIVE = 1; public static final int STATUS_ACTIVE = 1;
public static final int STATUS_WIFI_DISABLED = -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 static final long WIFI_MIN_UPDATE_TIME = 5000; // milliseconds
private boolean mStarted; private boolean mStarted;
@ -194,11 +194,9 @@ public class WifiScanner extends BroadcastReceiver {
public static boolean shouldLog(ScanResult scanResult) { public static boolean shouldLog(ScanResult scanResult) {
if (BSSIDBlockList.contains(scanResult)) { if (BSSIDBlockList.contains(scanResult)) {
Log.w(LOG_TAG, "Blocked BSSID: " + scanResult);
return false; return false;
} }
if (SSIDBlockList.contains(scanResult)) { if (SSIDBlockList.contains(scanResult)) {
Log.w(LOG_TAG, "Blocked SSID: " + scanResult);
return false; return false;
} }
return true; return true;

Просмотреть файл

@ -19,7 +19,7 @@ import org.json.JSONObject;
import org.mozilla.mozstumbler.service.AppGlobals; import org.mozilla.mozstumbler.service.AppGlobals;
public class CellInfo implements Parcelable { 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_GSM = "gsm";
public static final String RADIO_CDMA = "cdma"; 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_CELLS = "cells";
public static final String ACTION_CELLS_SCANNED_ARG_TIME = AppGlobals.ACTION_ARG_TIME; 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 static final long CELL_MIN_UPDATE_TIME = 1000; // milliseconds
private final Context mContext; private final Context mContext;

Просмотреть файл

@ -31,7 +31,7 @@ import java.util.List;
/* Fennec does not yet support the api level for WCDMA import */ /* Fennec does not yet support the api level for WCDMA import */
public class CellScannerNoWCDMA implements CellScanner.CellScannerImpl { 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 GetAllCellInfoScannerImpl mGetAllInfoCellScanner;
protected TelephonyManager mTelephonyManager; protected TelephonyManager mTelephonyManager;
protected boolean mIsStarted; 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. * 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. */ * An exception is made for AppGlobals.isDebug, a false reading is of no consequence. */
public class AsyncUploader extends AsyncTask<Void, Void, SyncSummary> { 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 UploadSettings mSettings;
private final Object mListenerLock = new Object(); private final Object mListenerLock = new Object();
private AsyncUploaderListener mListener; private AsyncUploaderListener mListener;

Просмотреть файл

@ -30,7 +30,7 @@ import org.mozilla.mozstumbler.service.utils.NetworkUtils;
// - triggered from the main thread // - triggered from the main thread
// - actual work is done the upload thread (AsyncUploader) // - actual work is done the upload thread (AsyncUploader)
public class UploadAlarmReceiver extends BroadcastReceiver { 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 final String EXTRA_IS_REPEATING = "is_repeating";
private static boolean sIsAlreadyScheduled; private static boolean sIsAlreadyScheduled;

Просмотреть файл

@ -20,7 +20,7 @@ import java.net.URL;
public abstract class AbstractCommunicator { 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 NICKNAME_HEADER = "X-Nickname";
private static final String USER_AGENT_HEADER = "User-Agent"; private static final String USER_AGENT_HEADER = "User-Agent";
private HttpURLConnection mHttpURLConnection; private HttpURLConnection mHttpURLConnection;

Просмотреть файл

@ -11,7 +11,7 @@ import android.util.Log;
import org.mozilla.mozstumbler.service.AppGlobals; import org.mozilla.mozstumbler.service.AppGlobals;
public final class NetworkUtils { 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; ConnectivityManager mConnectivityManager;
static NetworkUtils sInstance; static NetworkUtils sInstance;

Просмотреть файл

@ -5856,6 +5856,11 @@
"kind": "boolean", "kind": "boolean",
"description": "How many times has the devtool's Network Monitor been opened?" "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": { "DEVTOOLS_PAINTFLASHING_OPENED_BOOLEAN": {
"expires_in_version": "never", "expires_in_version": "never",
"kind": "boolean", "kind": "boolean",
@ -5976,6 +5981,11 @@
"kind": "flag", "kind": "flag",
"description": "How many users have opened the devtool's Network Monitor?" "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": { "DEVTOOLS_PAINTFLASHING_OPENED_PER_USER_FLAG": {
"expires_in_version": "never", "expires_in_version": "never",
"kind": "flag", "kind": "flag",
@ -6130,6 +6140,13 @@
"n_buckets": 100, "n_buckets": 100,
"description": "How long has the network monitor been active (seconds)" "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": { "DEVTOOLS_PAINTFLASHING_TIME_ACTIVE_SECONDS": {
"expires_in_version": "never", "expires_in_version": "never",
"kind": "exponential", "kind": "exponential",