Merge fx-team to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-11-11 16:43:46 -05:00
commit 50905e0270
74 changed files with 1453 additions and 465 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -399,18 +399,21 @@ loop.panel = (function(_, mozL10n) {
// readOnly attr will suppress a warning regarding this issue
// from the react lib.
var cx = React.addons.classSet;
var inputCSSClass = cx({
"pending": this.state.pending,
// Used in functional testing, signals that
// call url was received from loop server
"callUrl": !this.state.pending
});
return (
React.DOM.div({className: "generate-url"},
React.DOM.header(null, __("share_link_header_text")),
React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true",
onCopy: this.handleLinkExfiltration,
className: inputCSSClass}),
React.DOM.div({className: "generate-url-stack"},
React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true",
onCopy: this.handleLinkExfiltration,
className: cx({"generate-url-input": true,
pending: this.state.pending,
// Used in functional testing, signals that
// call url was received from loop server
callUrl: !this.state.pending})}),
React.DOM.div({className: cx({"generate-url-spinner": true,
spinner: true,
busy: this.state.pending})})
),
ButtonGroup({additionalClass: "url-actions"},
Button({additionalClass: "button-email",
disabled: !this.state.callUrl,

View File

@ -399,18 +399,21 @@ loop.panel = (function(_, mozL10n) {
// readOnly attr will suppress a warning regarding this issue
// from the react lib.
var cx = React.addons.classSet;
var inputCSSClass = cx({
"pending": this.state.pending,
// Used in functional testing, signals that
// call url was received from loop server
"callUrl": !this.state.pending
});
return (
<div className="generate-url">
<header>{__("share_link_header_text")}</header>
<input type="url" value={this.state.callUrl} readOnly="true"
onCopy={this.handleLinkExfiltration}
className={inputCSSClass} />
<div className="generate-url-stack">
<input type="url" value={this.state.callUrl} readOnly="true"
onCopy={this.handleLinkExfiltration}
className={cx({"generate-url-input": true,
pending: this.state.pending,
// Used in functional testing, signals that
// call url was received from loop server
callUrl: !this.state.pending})} />
<div className={cx({"generate-url-spinner": true,
spinner: true,
busy: this.state.pending})} />
</div>
<ButtonGroup additionalClass="url-actions">
<Button additionalClass="button-email"
disabled={!this.state.callUrl}

View File

@ -11,6 +11,7 @@ var loop = loop || {};
loop.roomViews = (function(mozL10n) {
"use strict";
var sharedActions = loop.shared.actions;
var ROOM_STATES = loop.store.ROOM_STATES;
var sharedViews = loop.shared.views;
@ -37,11 +38,20 @@ loop.roomViews = (function(mozL10n) {
},
_onActiveRoomStateChanged: function() {
this.setState(this.props.roomStore.getStoreState("activeRoom"));
// Only update the state if we're mounted, to avoid the problem where
// stopListening doesn't nuke the active listeners during a event
// processing.
if (this.isMounted()) {
this.setState(this.props.roomStore.getStoreState("activeRoom"));
}
},
getInitialState: function() {
return this.props.roomStore.getStoreState("activeRoom");
var storeState = this.props.roomStore.getStoreState("activeRoom");
return _.extend(storeState, {
// Used by the UI showcase.
roomState: this.props.roomState || storeState.roomState
});
}
};
@ -72,25 +82,22 @@ loop.roomViews = (function(mozL10n) {
render: function() {
return (
React.DOM.div({className: "room-conversation-wrapper"},
React.DOM.div({className: "room-invitation-overlay"},
React.DOM.form({onSubmit: this.handleFormSubmit},
React.DOM.input({type: "text", ref: "roomName",
placeholder: mozL10n.get("rooms_name_this_room_label")})
),
React.DOM.p(null, mozL10n.get("invite_header_text")),
React.DOM.div({className: "btn-group call-action-group"},
React.DOM.button({className: "btn btn-info btn-email",
onClick: this.handleEmailButtonClick},
mozL10n.get("share_button2")
),
React.DOM.button({className: "btn btn-info btn-copy",
onClick: this.handleCopyButtonClick},
mozL10n.get("copy_url_button2")
)
)
React.DOM.div({className: "room-invitation-overlay"},
React.DOM.form({onSubmit: this.handleFormSubmit},
React.DOM.input({type: "text", ref: "roomName",
placeholder: mozL10n.get("rooms_name_this_room_label")})
),
DesktopRoomConversationView({roomStore: this.props.roomStore})
React.DOM.p(null, mozL10n.get("invite_header_text")),
React.DOM.div({className: "btn-group call-action-group"},
React.DOM.button({className: "btn btn-info btn-email",
onClick: this.handleEmailButtonClick},
mozL10n.get("share_button2")
),
React.DOM.button({className: "btn btn-info btn-copy",
onClick: this.handleCopyButtonClick},
mozL10n.get("copy_url_button2")
)
)
)
);
}
@ -100,105 +107,150 @@ loop.roomViews = (function(mozL10n) {
* Desktop room conversation view.
*/
var DesktopRoomConversationView = React.createClass({displayName: 'DesktopRoomConversationView',
mixins: [ActiveRoomStoreMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
video: React.PropTypes.object,
audio: React.PropTypes.object,
displayInvitation: React.PropTypes.bool
},
getDefaultProps: function() {
return {
video: {enabled: true, visible: true},
audio: {enabled: true, visible: true}
};
},
render: function() {
var localStreamClasses = React.addons.classSet({
local: true,
"local-stream": true,
"local-stream-audio": !this.props.video.enabled
});
return (
React.DOM.div({className: "room-conversation-wrapper"},
React.DOM.div({className: "video-layout-wrapper"},
React.DOM.div({className: "conversation room-conversation"},
React.DOM.div({className: "media nested"},
React.DOM.div({className: "video_wrapper remote_wrapper"},
React.DOM.div({className: "video_inner remote"})
),
React.DOM.div({className: localStreamClasses})
),
sharedViews.ConversationToolbar({
video: this.props.video,
audio: this.props.audio,
publishStream: noop,
hangup: noop})
)
)
)
);
}
});
/**
* Desktop room controller view.
*/
var DesktopRoomControllerView = React.createClass({displayName: 'DesktopRoomControllerView',
mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
_renderInvitationOverlay: function() {
if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
return DesktopRoomInvitationView({
roomStore: this.props.roomStore,
dispatcher: this.props.dispatcher}
);
}
return null;
},
componentDidMount: function() {
/**
* OT inserts inline styles into the markup. Using a listener for
* resize events helps us trigger a full width/height on the element
* so that they update to the correct dimensions.
* XXX: this should be factored as a mixin.
*/
window.addEventListener('orientationchange', this.updateVideoContainer);
window.addEventListener('resize', this.updateVideoContainer);
// The SDK needs to know about the configuration and the elements to use
// for display. So the best way seems to pass the information here - ideally
// the sdk wouldn't need to know this, but we can't change that.
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
publisherConfig: this._getPublisherConfig(),
getLocalElementFunc: this._getElement.bind(this, ".local"),
getRemoteElementFunc: this._getElement.bind(this, ".remote")
}));
},
_getPublisherConfig: function() {
// height set to 100%" to fix video layout on Google Chrome
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
return {
insertMode: "append",
width: "100%",
height: "100%",
publishVideo: !this.state.videoMuted,
style: {
audioLevelDisplayMode: "off",
bugDisplayMode: "off",
buttonDisplayMode: "off",
nameDisplayMode: "off",
videoDisabledDisplayMode: "off"
}
};
},
/**
* Used to update the video container whenever the orientation or size of the
* display area changes.
*/
updateVideoContainer: function() {
var localStreamParent = this._getElement('.local .OT_publisher');
var remoteStreamParent = this._getElement('.remote .OT_subscriber');
if (localStreamParent) {
localStreamParent.style.width = "100%";
}
if (remoteStreamParent) {
remoteStreamParent.style.height = "100%";
}
},
/**
* Returns either the required DOMNode
*
* @param {String} className The name of the class to get the element for.
*/
_getElement: function(className) {
return this.getDOMNode().querySelector(className);
},
/**
* Closes the window if the cancel button is pressed in the generic failure view.
*/
closeWindow: function() {
window.close();
},
_renderRoomView: function(roomState) {
switch (roomState) {
case ROOM_STATES.FAILED: {
return loop.conversation.GenericFailureView({
cancelCall: this.closeWindow}
);
}
case ROOM_STATES.INIT:
case ROOM_STATES.GATHER:
case ROOM_STATES.READY:
case ROOM_STATES.JOINED: {
return DesktopRoomInvitationView({
dispatcher: this.props.dispatcher,
roomStore: this.props.roomStore}
);
}
// XXX needs bug 1074686/1074702
case ROOM_STATES.HAS_PARTICIPANTS: {
return DesktopRoomConversationView({
dispatcher: this.props.dispatcher,
roomStore: this.props.roomStore}
);
}
}
/**
* Used to control publishing a stream - i.e. to mute a stream
*
* @param {String} type The type of stream, e.g. "audio" or "video".
* @param {Boolean} enabled True to enable the stream, false otherwise.
*/
publishStream: function(type, enabled) {
this.props.dispatcher.dispatch(
new sharedActions.SetMute({
type: type,
enabled: enabled
}));
},
render: function() {
if (this.state.roomName) {
this.setTitle(this.state.roomName);
}
return (
React.DOM.div({className: "room-conversation-wrapper"},
this._renderRoomView(this.state.roomState)
)
);
var localStreamClasses = React.addons.classSet({
local: true,
"local-stream": true,
"local-stream-audio": !this.state.videoMuted
});
switch(this.state.roomState) {
case ROOM_STATES.FAILED: {
return loop.conversation.GenericFailureView({
cancelCall: this.closeWindow}
);
}
default: {
return (
React.DOM.div({className: "room-conversation-wrapper"},
this._renderInvitationOverlay(),
React.DOM.div({className: "video-layout-wrapper"},
React.DOM.div({className: "conversation room-conversation"},
React.DOM.div({className: "media nested"},
React.DOM.div({className: "video_wrapper remote_wrapper"},
React.DOM.div({className: "video_inner remote"})
),
React.DOM.div({className: localStreamClasses})
),
sharedViews.ConversationToolbar({
video: {enabled: !this.state.videoMuted, visible: true},
audio: {enabled: !this.state.audioMuted, visible: true},
publishStream: this.publishStream,
hangup: noop})
)
)
)
);
}
}
}
});
return {
ActiveRoomStoreMixin: ActiveRoomStoreMixin,
DesktopRoomControllerView: DesktopRoomControllerView,
DesktopRoomConversationView: DesktopRoomConversationView,
DesktopRoomInvitationView: DesktopRoomInvitationView
};

View File

@ -11,6 +11,7 @@ var loop = loop || {};
loop.roomViews = (function(mozL10n) {
"use strict";
var sharedActions = loop.shared.actions;
var ROOM_STATES = loop.store.ROOM_STATES;
var sharedViews = loop.shared.views;
@ -37,11 +38,20 @@ loop.roomViews = (function(mozL10n) {
},
_onActiveRoomStateChanged: function() {
this.setState(this.props.roomStore.getStoreState("activeRoom"));
// Only update the state if we're mounted, to avoid the problem where
// stopListening doesn't nuke the active listeners during a event
// processing.
if (this.isMounted()) {
this.setState(this.props.roomStore.getStoreState("activeRoom"));
}
},
getInitialState: function() {
return this.props.roomStore.getStoreState("activeRoom");
var storeState = this.props.roomStore.getStoreState("activeRoom");
return _.extend(storeState, {
// Used by the UI showcase.
roomState: this.props.roomState || storeState.roomState
});
}
};
@ -72,25 +82,22 @@ loop.roomViews = (function(mozL10n) {
render: function() {
return (
<div className="room-conversation-wrapper">
<div className="room-invitation-overlay">
<form onSubmit={this.handleFormSubmit}>
<input type="text" ref="roomName"
placeholder={mozL10n.get("rooms_name_this_room_label")} />
</form>
<p>{mozL10n.get("invite_header_text")}</p>
<div className="btn-group call-action-group">
<button className="btn btn-info btn-email"
onClick={this.handleEmailButtonClick}>
{mozL10n.get("share_button2")}
</button>
<button className="btn btn-info btn-copy"
onClick={this.handleCopyButtonClick}>
{mozL10n.get("copy_url_button2")}
</button>
</div>
<div className="room-invitation-overlay">
<form onSubmit={this.handleFormSubmit}>
<input type="text" ref="roomName"
placeholder={mozL10n.get("rooms_name_this_room_label")} />
</form>
<p>{mozL10n.get("invite_header_text")}</p>
<div className="btn-group call-action-group">
<button className="btn btn-info btn-email"
onClick={this.handleEmailButtonClick}>
{mozL10n.get("share_button2")}
</button>
<button className="btn btn-info btn-copy"
onClick={this.handleCopyButtonClick}>
{mozL10n.get("copy_url_button2")}
</button>
</div>
<DesktopRoomConversationView roomStore={this.props.roomStore} />
</div>
);
}
@ -100,105 +107,150 @@ loop.roomViews = (function(mozL10n) {
* Desktop room conversation view.
*/
var DesktopRoomConversationView = React.createClass({
mixins: [ActiveRoomStoreMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
video: React.PropTypes.object,
audio: React.PropTypes.object,
displayInvitation: React.PropTypes.bool
},
getDefaultProps: function() {
return {
video: {enabled: true, visible: true},
audio: {enabled: true, visible: true}
};
},
render: function() {
var localStreamClasses = React.addons.classSet({
local: true,
"local-stream": true,
"local-stream-audio": !this.props.video.enabled
});
return (
<div className="room-conversation-wrapper">
<div className="video-layout-wrapper">
<div className="conversation room-conversation">
<div className="media nested">
<div className="video_wrapper remote_wrapper">
<div className="video_inner remote"></div>
</div>
<div className={localStreamClasses}></div>
</div>
<sharedViews.ConversationToolbar
video={this.props.video}
audio={this.props.audio}
publishStream={noop}
hangup={noop} />
</div>
</div>
</div>
);
}
});
/**
* Desktop room controller view.
*/
var DesktopRoomControllerView = React.createClass({
mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
_renderInvitationOverlay: function() {
if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
return <DesktopRoomInvitationView
roomStore={this.props.roomStore}
dispatcher={this.props.dispatcher}
/>;
}
return null;
},
componentDidMount: function() {
/**
* OT inserts inline styles into the markup. Using a listener for
* resize events helps us trigger a full width/height on the element
* so that they update to the correct dimensions.
* XXX: this should be factored as a mixin.
*/
window.addEventListener('orientationchange', this.updateVideoContainer);
window.addEventListener('resize', this.updateVideoContainer);
// The SDK needs to know about the configuration and the elements to use
// for display. So the best way seems to pass the information here - ideally
// the sdk wouldn't need to know this, but we can't change that.
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
publisherConfig: this._getPublisherConfig(),
getLocalElementFunc: this._getElement.bind(this, ".local"),
getRemoteElementFunc: this._getElement.bind(this, ".remote")
}));
},
_getPublisherConfig: function() {
// height set to 100%" to fix video layout on Google Chrome
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
return {
insertMode: "append",
width: "100%",
height: "100%",
publishVideo: !this.state.videoMuted,
style: {
audioLevelDisplayMode: "off",
bugDisplayMode: "off",
buttonDisplayMode: "off",
nameDisplayMode: "off",
videoDisabledDisplayMode: "off"
}
};
},
/**
* Used to update the video container whenever the orientation or size of the
* display area changes.
*/
updateVideoContainer: function() {
var localStreamParent = this._getElement('.local .OT_publisher');
var remoteStreamParent = this._getElement('.remote .OT_subscriber');
if (localStreamParent) {
localStreamParent.style.width = "100%";
}
if (remoteStreamParent) {
remoteStreamParent.style.height = "100%";
}
},
/**
* Returns either the required DOMNode
*
* @param {String} className The name of the class to get the element for.
*/
_getElement: function(className) {
return this.getDOMNode().querySelector(className);
},
/**
* Closes the window if the cancel button is pressed in the generic failure view.
*/
closeWindow: function() {
window.close();
},
_renderRoomView: function(roomState) {
switch (roomState) {
case ROOM_STATES.FAILED: {
return <loop.conversation.GenericFailureView
cancelCall={this.closeWindow}
/>;
}
case ROOM_STATES.INIT:
case ROOM_STATES.GATHER:
case ROOM_STATES.READY:
case ROOM_STATES.JOINED: {
return <DesktopRoomInvitationView
dispatcher={this.props.dispatcher}
roomStore={this.props.roomStore}
/>;
}
// XXX needs bug 1074686/1074702
case ROOM_STATES.HAS_PARTICIPANTS: {
return <DesktopRoomConversationView
dispatcher={this.props.dispatcher}
roomStore={this.props.roomStore}
/>;
}
}
/**
* Used to control publishing a stream - i.e. to mute a stream
*
* @param {String} type The type of stream, e.g. "audio" or "video".
* @param {Boolean} enabled True to enable the stream, false otherwise.
*/
publishStream: function(type, enabled) {
this.props.dispatcher.dispatch(
new sharedActions.SetMute({
type: type,
enabled: enabled
}));
},
render: function() {
if (this.state.roomName) {
this.setTitle(this.state.roomName);
}
return (
<div className="room-conversation-wrapper">{
this._renderRoomView(this.state.roomState)
}</div>
);
var localStreamClasses = React.addons.classSet({
local: true,
"local-stream": true,
"local-stream-audio": !this.state.videoMuted
});
switch(this.state.roomState) {
case ROOM_STATES.FAILED: {
return <loop.conversation.GenericFailureView
cancelCall={this.closeWindow}
/>;
}
default: {
return (
<div className="room-conversation-wrapper">
{this._renderInvitationOverlay()}
<div className="video-layout-wrapper">
<div className="conversation room-conversation">
<div className="media nested">
<div className="video_wrapper remote_wrapper">
<div className="video_inner remote"></div>
</div>
<div className={localStreamClasses}></div>
</div>
<sharedViews.ConversationToolbar
video={{enabled: !this.state.videoMuted, visible: true}}
audio={{enabled: !this.state.audioMuted, visible: true}}
publishStream={this.publishStream}
hangup={noop} />
</div>
</div>
</div>
);
}
}
}
});
return {
ActiveRoomStoreMixin: ActiveRoomStoreMixin,
DesktopRoomControllerView: DesktopRoomControllerView,
DesktopRoomConversationView: DesktopRoomConversationView,
DesktopRoomInvitationView: DesktopRoomInvitationView
};

View File

@ -2,6 +2,16 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.contact-import-spinner {
display: none;
}
.contact-import-spinner.busy {
display: inline-block;
vertical-align: middle;
-moz-margin-start: 10px;
}
.content-area input.contact-filter {
margin-top: 14px;
border-radius: 10000px;

View File

@ -46,7 +46,7 @@ body {
flex: 1;
text-align: center;
color: #ccc;
border-right: 1px solid #ccc;
-moz-border-end: 1px solid #ccc;
padding: 0 10px;
height: 16px;
cursor: pointer;
@ -56,7 +56,7 @@ body {
}
.tab-view > li:last-child {
border-right-style: none;
-moz-border-end-style: none;
}
.tab-view > li[data-tab-name="call"],
@ -307,6 +307,10 @@ body {
font-size: 12px;
}
.button > .button-caption {
vertical-align: middle;
}
.button:hover {
background-color: #ebebeb;
}
@ -372,10 +376,41 @@ body[dir=rtl] .dropdown-menu-item {
background-color: #eee;
}
/* Spinner */
@keyframes spinnerRotate {
to { transform: rotate(360deg); }
}
.spinner {
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-size: 16px 16px;
}
.spinner.busy {
background-image: url(../img/spinner.png);
animation-name: spinnerRotate;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@media (min-resolution: 2dppx) {
.spinner.busy {
background-image: url(../img/spinner@2x.png);
}
}
/* Share tab */
.generate-url input {
.generate-url-stack {
margin: 14px 0;
position: relative;
}
.generate-url-input {
outline: 0;
border: 1px solid #ccc; /* Overriding background style for a text input (see
below) resets its borders to a weird beveled style;
@ -386,10 +421,18 @@ body[dir=rtl] .dropdown-menu-item {
font-size: 1em;
}
.generate-url input.pending {
background-image: url(../img/loading-icon.gif);
background-repeat: no-repeat;
background-position: right;
.generate-url-spinner {
position: absolute;
pointer-events: none;
z-index: 1;
top: 4px;
left: auto;
right: 4px;
}
body[dir=rtl] .generate-url-spinner {
left: 4px;
right: auto;
}
.generate-url .button {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -109,9 +109,12 @@ loop.shared.actions = (function() {
}),
/**
* Used to indicate the peer hung up the call.
* Used to indicate the remote peer was disconnected for some reason.
*
* peerHungup is true if the peer intentionally disconnected, false otherwise.
*/
PeerHungupCall: Action.define("peerHungupCall", {
RemotePeerDisconnected: Action.define("remotePeerDisconnected", {
peerHungup: Boolean
}),
/**
@ -132,6 +135,18 @@ loop.shared.actions = (function() {
reason: String
}),
/**
* Used to notify that the sdk session is now connected to the servers.
*/
ConnectedToSdkServers: Action.define("connectedToSdkServers", {
}),
/**
* Used to notify that a remote peer has connected to the room.
*/
RemotePeerConnected: Action.define("remotePeerConnected", {
}),
/**
* Used by the ongoing views to notify stores about the elements
* required for the sdk.

View File

@ -20,10 +20,12 @@ loop.store.ActiveRoomStore = (function() {
READY: "room-ready",
// The room is known to be joined on the loop-server
JOINED: "room-joined",
// The room is connected to the sdk server.
SESSION_CONNECTED: "room-session-connected",
// There are participants in the room.
HAS_PARTICIPANTS: "room-has-participants",
// There was an issue with the room
FAILED: "room-failed",
// XXX to be implemented in bug 1074686/1074702
HAS_PARTICIPANTS: "room-has-participants"
FAILED: "room-failed"
};
/**
@ -51,15 +53,18 @@ loop.store.ActiveRoomStore = (function() {
}
this._mozLoop = options.mozLoop;
if (!options.sdkDriver) {
throw new Error("Missing option sdkDriver");
}
this._sdkDriver = options.sdkDriver;
// XXX Further actions are registered in setupWindowData and
// fetchServerData when we know what window type this is. At some stage,
// we might want to consider store mixins or some alternative which
// means the stores would only be created when we want them.
this._dispatcher.register(this, [
"roomFailure",
"setupWindowData",
"fetchServerData",
"updateRoomInfo",
"joinRoom",
"joinedRoom",
"windowUnload",
"leaveRoom"
"fetchServerData"
]);
/**
@ -75,7 +80,9 @@ loop.store.ActiveRoomStore = (function() {
* otherwise it will be unset.
*/
this._storeState = {
roomState: ROOM_STATES.INIT
roomState: ROOM_STATES.INIT,
audioMuted: false,
videoMuted: false
};
}
@ -113,6 +120,26 @@ loop.store.ActiveRoomStore = (function() {
});
},
/**
* Registers the actions with the dispatcher that this store is interested
* in.
*/
_registerActions: function() {
this._dispatcher.register(this, [
"roomFailure",
"updateRoomInfo",
"joinRoom",
"joinedRoom",
"connectedToSdkServers",
"connectionFailure",
"setMute",
"remotePeerDisconnected",
"remotePeerConnected",
"windowUnload",
"leaveRoom"
]);
},
/**
* Execute setupWindowData event action from the dispatcher. This gets
* the room data from the mozLoop api, and dispatches an UpdateRoomInfo event.
@ -127,6 +154,8 @@ loop.store.ActiveRoomStore = (function() {
return;
}
this._registerActions();
this.setStoreState({
roomState: ROOM_STATES.GATHER
});
@ -169,6 +198,8 @@ loop.store.ActiveRoomStore = (function() {
return;
}
this._registerActions();
this.setStoreState({
roomToken: actionData.token,
roomState: ROOM_STATES.READY
@ -228,6 +259,57 @@ loop.store.ActiveRoomStore = (function() {
});
this._setRefreshTimeout(actionData.expires);
this._sdkDriver.connectSession(actionData);
},
/**
* Handles recording when the sdk has connected to the servers.
*/
connectedToSdkServers: function() {
this.setStoreState({
roomState: ROOM_STATES.SESSION_CONNECTED
});
},
/**
* Handles disconnection of this local client from the sdk servers.
*/
connectionFailure: function() {
// Treat all reasons as something failed. In theory, clientDisconnected
// could be a success case, but there's no way we should be intentionally
// sending that and still have the window open.
this._leaveRoom(ROOM_STATES.FAILED);
},
/**
* Records the mute state for the stream.
*
* @param {sharedActions.setMute} actionData The mute state for the stream type.
*/
setMute: function(actionData) {
var muteState = {};
muteState[actionData.type + "Muted"] = !actionData.enabled;
this.setStoreState(muteState);
},
/**
* Handles recording when a remote peer has connected to the servers.
*/
remotePeerConnected: function() {
this.setStoreState({
roomState: ROOM_STATES.HAS_PARTICIPANTS
});
},
/**
* Handles a remote peer disconnecting from the session.
*/
remotePeerDisconnected: function() {
// As we only support two users at the moment, we just set this
// back to joined.
this.setStoreState({
roomState: ROOM_STATES.SESSION_CONNECTED
});
},
/**
@ -275,22 +357,27 @@ loop.store.ActiveRoomStore = (function() {
/**
* Handles leaving a room. Clears any membership timeouts, then
* signals to the server the leave of the room.
*
* @param {ROOM_STATES} nextState Optional; the next state to switch to.
* Switches to READY if undefined.
*/
_leaveRoom: function() {
if (this._storeState.roomState !== ROOM_STATES.JOINED) {
return;
}
_leaveRoom: function(nextState) {
this._sdkDriver.disconnectSession();
if (this._timeout) {
clearTimeout(this._timeout);
delete this._timeout;
}
this._mozLoop.rooms.leave(this._storeState.roomToken,
this._storeState.sessionToken);
if (this._storeState.roomState === ROOM_STATES.JOINED ||
this._storeState.roomState === ROOM_STATES.SESSION_CONNECTED ||
this._storeState.roomState === ROOM_STATES.HAS_PARTICIPANTS) {
this._mozLoop.rooms.leave(this._storeState.roomToken,
this._storeState.sessionToken);
}
this.setStoreState({
roomState: ROOM_STATES.READY
roomState: nextState ? nextState : ROOM_STATES.READY
});
}

View File

@ -118,18 +118,12 @@ loop.store.ConversationStore = (function() {
this.dispatcher = options.dispatcher;
this.sdkDriver = options.sdkDriver;
// XXX Further actions are registered in setupWindowData when
// we know what window type this is. At some stage, we might want to
// consider store mixins or some alternative which means the stores
// would only be created when we want them.
this.dispatcher.register(this, [
"connectionFailure",
"connectionProgress",
"setupWindowData",
"connectCall",
"hangupCall",
"peerHungupCall",
"cancelCall",
"retryCall",
"mediaConnected",
"setMute",
"fetchEmailLink"
"setupWindowData"
]);
},
@ -196,6 +190,19 @@ loop.store.ConversationStore = (function() {
return;
}
this.dispatcher.register(this, [
"connectionFailure",
"connectionProgress",
"connectCall",
"hangupCall",
"remotePeerDisconnected",
"cancelCall",
"retryCall",
"mediaConnected",
"setMute",
"fetchEmailLink"
]);
this.set({
contact: actionData.contact,
outgoing: windowType === "outgoing",
@ -236,11 +243,23 @@ loop.store.ConversationStore = (function() {
},
/**
* The peer hungup the call.
* The remote peer disconnected from the session.
*
* @param {sharedActions.RemotePeerDisconnected} actionData
*/
peerHungupCall: function() {
remotePeerDisconnected: function(actionData) {
this._endSession();
this.set({callState: CALL_STATES.FINISHED});
// If the peer hungup, we end normally, otherwise
// we treat this as a call failure.
if (actionData.peerHungup) {
this.set({callState: CALL_STATES.FINISHED});
} else {
this.set({
callState: CALL_STATES.TERMINATED,
callStateReason: "peerNetworkDisconnected"
});
}
},
/**

View File

@ -79,6 +79,7 @@ loop.OTSdkDriver = (function() {
connectSession: function(sessionData) {
this.session = this.sdk.initSession(sessionData.sessionId);
this.session.on("connectionCreated", this._onConnectionCreated.bind(this));
this.session.on("streamCreated", this._onRemoteStreamCreated.bind(this));
this.session.on("connectionDestroyed",
this._onConnectionDestroyed.bind(this));
@ -130,6 +131,7 @@ loop.OTSdkDriver = (function() {
return;
}
this.dispatcher.dispatch(new sharedActions.ConnectedToSdkServers());
this._sessionConnected = true;
this._maybePublishLocalStream();
},
@ -141,18 +143,9 @@ loop.OTSdkDriver = (function() {
* https://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html
*/
_onConnectionDestroyed: function(event) {
var action;
if (event.reason === "clientDisconnected") {
action = new sharedActions.PeerHungupCall();
} else {
// Strictly speaking this isn't a failure on our part, but since our
// flow requires a full reconnection, then we just treat this as
// if a failure of our end had occurred.
action = new sharedActions.ConnectionFailure({
reason: "peerNetworkDisconnected"
});
}
this.dispatcher.dispatch(action);
this.dispatcher.dispatch(new sharedActions.RemotePeerDisconnected({
peerHungup: event.reason === "clientDisconnected"
}));
},
/**
@ -171,6 +164,14 @@ loop.OTSdkDriver = (function() {
}
},
_onConnectionCreated: function(event) {
if (this.session.connection.id === event.connection.id) {
return;
}
this.dispatcher.dispatch(new sharedActions.RemotePeerConnected());
},
/**
* Handles the event when the remote stream is created.
*

View File

@ -728,7 +728,8 @@ loop.shared.views = (function(_, OT, l10n) {
React.DOM.button({onClick: this.props.onClick,
disabled: this.props.disabled,
className: cx(classObject)},
this.props.caption
React.DOM.span({className: "button-caption"}, this.props.caption),
this.props.children
)
)
}

View File

@ -728,7 +728,8 @@ loop.shared.views = (function(_, OT, l10n) {
<button onClick={this.props.onClick}
disabled={this.props.disabled}
className={cx(classObject)}>
{this.props.caption}
<span className="button-caption">{this.props.caption}</span>
{this.props.children}
</button>
)
}

View File

@ -32,7 +32,8 @@ browser.jar:
content/browser/loop/shared/img/sad.png (content/shared/img/sad.png)
content/browser/loop/shared/img/icon_32.png (content/shared/img/icon_32.png)
content/browser/loop/shared/img/icon_64.png (content/shared/img/icon_64.png)
content/browser/loop/shared/img/loading-icon.gif (content/shared/img/loading-icon.gif)
content/browser/loop/shared/img/spinner.png (content/shared/img/spinner.png)
content/browser/loop/shared/img/spinner@2x.png (content/shared/img/spinner@2x.png)
content/browser/loop/shared/img/audio-inverse-14x14.png (content/shared/img/audio-inverse-14x14.png)
content/browser/loop/shared/img/audio-inverse-14x14@2x.png (content/shared/img/audio-inverse-14x14@2x.png)
content/browser/loop/shared/img/facemute-14x14.png (content/shared/img/facemute-14x14.png)

View File

@ -96,6 +96,7 @@
<script type="text/javascript" src="shared/js/validate.js"></script>
<script type="text/javascript" src="shared/js/dispatcher.js"></script>
<script type="text/javascript" src="shared/js/websocket.js"></script>
<script type="text/javascript" src="shared/js/otSdkDriver.js"></script>
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
<script type="text/javascript" src="js/standaloneAppStore.js"></script>
<script type="text/javascript" src="js/standaloneClient.js"></script>

View File

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

View File

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

View File

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

View File

@ -32,7 +32,8 @@ describe("loop.roomViews", function () {
activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher,
mozLoop: {}
mozLoop: {},
sdkDriver: {}
});
roomStore = new loop.store.RoomStore({
dispatcher: dispatcher,
@ -62,6 +63,8 @@ describe("loop.roomViews", function () {
expect(testView.state).eql({
roomState: ROOM_STATES.INIT,
audioMuted: false,
videoMuted: false,
foo: "bar"
});
});
@ -77,21 +80,90 @@ describe("loop.roomViews", function () {
activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
expect(testView.state).eql({roomState: ROOM_STATES.READY});
expect(testView.state.roomState).eql(ROOM_STATES.READY);
});
});
describe("DesktopRoomControllerView", function() {
describe("DesktopRoomConversationView", function() {
var view;
beforeEach(function() {
sandbox.stub(dispatcher, "dispatch");
});
function mountTestComponent() {
return TestUtils.renderIntoDocument(
new loop.roomViews.DesktopRoomControllerView({
mozLoop: {},
new loop.roomViews.DesktopRoomConversationView({
dispatcher: dispatcher,
roomStore: roomStore
}));
}
it("should dispatch a setupStreamElements action when the view is created",
function() {
view = mountTestComponent();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "setupStreamElements"));
});
it("should dispatch a setMute action when the audio mute button is pressed",
function() {
view = mountTestComponent();
view.setState({audioMuted: true});
var muteBtn = view.getDOMNode().querySelector('.btn-mute-audio');
React.addons.TestUtils.Simulate.click(muteBtn);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "setMute"));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("enabled", true));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("type", "audio"));
});
it("should dispatch a setMute action when the video mute button is pressed",
function() {
view = mountTestComponent();
view.setState({videoMuted: false});
var muteBtn = view.getDOMNode().querySelector('.btn-mute-video');
React.addons.TestUtils.Simulate.click(muteBtn);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "setMute"));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("enabled", false));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("type", "video"));
});
it("should set the mute button as mute off", function() {
view = mountTestComponent();
view.setState({videoMuted: false});
var muteBtn = view.getDOMNode().querySelector('.btn-mute-video');
expect(muteBtn.classList.contains("muted")).eql(false);
});
it("should set the mute button as mute on", function() {
view = mountTestComponent();
view.setState({audioMuted: true});
var muteBtn = view.getDOMNode().querySelector('.btn-mute-audio');
expect(muteBtn.classList.contains("muted")).eql(true);
});
describe("#render", function() {
it("should set document.title to store.serverData.roomName", function() {
mountTestComponent();

View File

@ -7,7 +7,7 @@ describe("loop.store.ActiveRoomStore", function () {
"use strict";
var ROOM_STATES = loop.store.ROOM_STATES;
var sandbox, dispatcher, store, fakeMozLoop;
var sandbox, dispatcher, store, fakeMozLoop, fakeSdkDriver;
beforeEach(function() {
sandbox = sinon.sandbox.create();
@ -25,8 +25,16 @@ describe("loop.store.ActiveRoomStore", function () {
}
};
store = new loop.store.ActiveRoomStore(
{mozLoop: fakeMozLoop, dispatcher: dispatcher});
fakeSdkDriver = {
connectSession: sandbox.stub(),
disconnectSession: sandbox.stub()
};
store = new loop.store.ActiveRoomStore({
dispatcher: dispatcher,
mozLoop: fakeMozLoop,
sdkDriver: fakeSdkDriver
});
});
afterEach(function() {
@ -45,6 +53,12 @@ describe("loop.store.ActiveRoomStore", function () {
new loop.store.ActiveRoomStore({dispatcher: dispatcher});
}).to.Throw(/mozLoop/);
});
it("should throw an error if sdkDriver is missing", function() {
expect(function() {
new loop.store.ActiveRoomStore({dispatcher: dispatcher, mozLoop: {}});
}).to.Throw(/sdkDriver/);
});
});
describe("#roomFailure", function() {
@ -281,6 +295,16 @@ describe("loop.store.ActiveRoomStore", function () {
expect(state.sessionId).eql(fakeJoinedData.sessionId);
});
it("should start the session connection with the sdk", function() {
var actionData = new sharedActions.JoinedRoom(fakeJoinedData);
store.joinedRoom(actionData);
sinon.assert.calledOnce(fakeSdkDriver.connectSession);
sinon.assert.calledWithExactly(fakeSdkDriver.connectSession,
actionData);
});
it("should call mozLoop.rooms.refreshMembership before the expiresTime",
function() {
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
@ -330,6 +354,93 @@ describe("loop.store.ActiveRoomStore", function () {
});
});
describe("#connectedToSdkServers", function() {
it("should set the state to `SESSION_CONNECTED`", function() {
store.connectedToSdkServers(new sharedActions.ConnectedToSdkServers());
expect(store.getStoreState().roomState).eql(ROOM_STATES.SESSION_CONNECTED);
});
});
describe("#connectionFailure", function() {
beforeEach(function() {
store.setStoreState({
roomState: ROOM_STATES.JOINED,
roomToken: "fakeToken",
sessionToken: "1627384950"
});
});
it("should disconnect from the servers via the sdk", function() {
store.connectionFailure();
sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
});
it("should clear any existing timeout", function() {
sandbox.stub(window, "clearTimeout");
store._timeout = {};
store.connectionFailure();
sinon.assert.calledOnce(clearTimeout);
});
it("should call mozLoop.rooms.leave", function() {
store.connectionFailure();
sinon.assert.calledOnce(fakeMozLoop.rooms.leave);
sinon.assert.calledWithExactly(fakeMozLoop.rooms.leave,
"fakeToken", "1627384950");
});
it("should set the state to `FAILED`", function() {
store.connectionFailure();
expect(store.getStoreState().roomState).eql(ROOM_STATES.FAILED);
});
});
describe("#setMute", function() {
it("should save the mute state for the audio stream", function() {
store.setStoreState({audioMuted: false});
store.setMute(new sharedActions.SetMute({
type: "audio",
enabled: true
}));
expect(store.getStoreState().audioMuted).eql(false);
});
it("should save the mute state for the video stream", function() {
store.setStoreState({videoMuted: true});
store.setMute(new sharedActions.SetMute({
type: "video",
enabled: false
}));
expect(store.getStoreState().videoMuted).eql(true);
});
});
describe("#remotePeerConnected", function() {
it("should set the state to `HAS_PARTICIPANTS`", function() {
store.remotePeerConnected();
expect(store.getStoreState().roomState).eql(ROOM_STATES.HAS_PARTICIPANTS);
});
});
describe("#remotePeerDisconnected", function() {
it("should set the state to `SESSION_CONNECTED`", function() {
store.remotePeerDisconnected();
expect(store.getStoreState().roomState).eql(ROOM_STATES.SESSION_CONNECTED);
});
});
describe("#windowUnload", function() {
beforeEach(function() {
store.setStoreState({
@ -339,6 +450,12 @@ describe("loop.store.ActiveRoomStore", function () {
});
});
it("should disconnect from the servers via the sdk", function() {
store.windowUnload();
sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
});
it("should clear any existing timeout", function() {
sandbox.stub(window, "clearTimeout");
store._timeout = {};
@ -372,6 +489,12 @@ describe("loop.store.ActiveRoomStore", function () {
});
});
it("should disconnect from the servers via the sdk", function() {
store.leaveRoom();
sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
});
it("should clear any existing timeout", function() {
sandbox.stub(window, "clearTimeout");
store._timeout = {};

View File

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

View File

@ -231,26 +231,30 @@ describe("loop.OTSdkDriver", function () {
});
describe("connectionDestroyed", function() {
it("should dispatch a peerHungupCall action if the client disconnected", function() {
session.trigger("connectionDestroyed", {
reason: "clientDisconnected"
it("should dispatch a remotePeerDisconnected action if the client" +
"disconnected", function() {
session.trigger("connectionDestroyed", {
reason: "clientDisconnected"
});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "remotePeerDisconnected"));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("peerHungup", true));
});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "peerHungupCall"));
});
it("should dispatch a remotePeerDisconnected action if the connection" +
"failed", function() {
session.trigger("connectionDestroyed", {
reason: "networkDisconnected"
});
it("should dispatch a connectionFailure action if the connection failed", function() {
session.trigger("connectionDestroyed", {
reason: "networkDisconnected"
});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "connectionFailure"));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("reason", "peerNetworkDisconnected"));
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "remotePeerDisconnected"));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("peerHungup", false));
});
});
@ -296,5 +300,33 @@ describe("loop.OTSdkDriver", function () {
sinon.match.hasOwn("name", "mediaConnected"));
});
});
describe("connectionCreated", function() {
beforeEach(function() {
session.connection = {
id: "localUser"
};
});
it("should dispatch a RemotePeerConnected action if this is for a remote user",
function() {
session.trigger("connectionCreated", {
connection: {id: "remoteUser"}
});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.RemotePeerConnected());
});
it("should not dispatch an action if this is for a local user",
function() {
session.trigger("connectionCreated", {
connection: {id: "localUser"}
});
sinon.assert.notCalled(dispatcher.dispatch);
});
});
});
});

View File

@ -370,7 +370,8 @@ describe("loop.store.RoomStore", function () {
beforeEach(function() {
activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher,
mozLoop: fakeMozLoop
mozLoop: fakeMozLoop,
sdkDriver: {}
});
store = new loop.store.RoomStore({
dispatcher: dispatcher,

View File

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

View File

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

View File

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

View File

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

View File

@ -635,21 +635,8 @@ let SessionStoreInternal = {
let uri = activePageData ? activePageData.url || null : null;
browser.userTypedValue = uri;
// If the page has a title, set it.
if (activePageData) {
if (activePageData.title) {
tab.label = activePageData.title;
tab.crop = "end";
} else if (activePageData.url != "about:blank") {
tab.label = activePageData.url;
tab.crop = "center";
}
}
// Restore the tab icon.
if ("image" in tabData) {
win.gBrowser.setIcon(tab, tabData.image);
}
// Update tab label and icon again after the tab history was updated.
this.updateTabLabelAndIcon(tab, tabData);
let event = win.document.createEvent("Events");
event.initEvent("SSTabRestoring", true, false);
@ -1860,6 +1847,26 @@ let SessionStoreInternal = {
}
},
updateTabLabelAndIcon(tab, tabData) {
let activePageData = tabData.entries[tabData.index - 1] || null;
// If the page has a title, set it.
if (activePageData) {
if (activePageData.title) {
tab.label = activePageData.title;
tab.crop = "end";
} else if (activePageData.url != "about:blank") {
tab.label = activePageData.url;
tab.crop = "center";
}
}
// Restore the tab icon.
if ("image" in tabData) {
tab.ownerDocument.defaultView.gBrowser.setIcon(tab, tabData.image);
}
},
/**
* Restores the session state stored in LastSession. This will attempt
* to merge data into the current session. If a window was opened at startup
@ -2532,9 +2539,17 @@ let SessionStoreInternal = {
this._windows[aWindow.__SSi].selected = aSelectTab;
}
// If we restore the selected tab, make sure it goes first.
let selectedIndex = aTabs.indexOf(tabbrowser.selectedTab);
if (selectedIndex > -1) {
this.restoreTab(tabbrowser.selectedTab, aTabData[selectedIndex]);
}
// Restore all tabs.
for (let t = 0; t < aTabs.length; t++) {
this.restoreTab(aTabs[t], aTabData[t]);
if (t != selectedIndex) {
this.restoreTab(aTabs[t], aTabData[t]);
}
}
},
@ -2640,6 +2655,10 @@ let SessionStoreInternal = {
browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory",
{tabData: tabData, epoch: epoch});
// Update tab label and icon to show something
// while we wait for the messages to be processed.
this.updateTabLabelAndIcon(tab, tabData);
// Restore tab attributes.
if ("attributes" in tabData) {
TabAttributes.set(tab, tabData.attributes);

View File

@ -761,6 +761,10 @@ Toolbox.prototype = {
let tiltEnabled = !this.target.isMultiProcess &&
Services.prefs.getBoolPref("devtools.command-button-tilt.enabled");
let tiltButton = this.doc.getElementById("command-button-tilt");
// Remote toolboxes don't add the button to the DOM at all
if (!tiltButton) {
return;
}
if (tiltEnabled) {
tiltButton.removeAttribute("hidden");

View File

@ -76,6 +76,14 @@ exports.showDoorhanger = Task.async(function *({ window, type, anchor }) {
return;
}
// Call success function to set preferences/cleanup immediately,
// so if triggered multiple times, only happens once (Windows/Linux)
success();
// Wait 200ms to prevent flickering where the popup is displayed
// before the underlying window (Windows 7, 64bit)
yield wait(200);
let document = window.document;
let panel = document.createElementNS(XULNS, "panel");
@ -108,9 +116,6 @@ exports.showDoorhanger = Task.async(function *({ window, type, anchor }) {
close();
});
}
// Call success function to set preferences, etc.
success();
});
function setDoorhangerStyle (panel, frame) {
@ -147,3 +152,9 @@ function onFrameLoad (frame) {
function getGBrowser () {
return getMostRecentBrowserWindow().gBrowser;
}
function wait (n) {
let { resolve, promise } = Promise.defer();
setTimeout(resolve, n);
return promise;
}

View File

@ -145,6 +145,11 @@ Telemetry.prototype = {
userHistogram: "DEVTOOLS_NETMONITOR_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_NETMONITOR_TIME_ACTIVE_SECONDS"
},
storage: {
histogram: "DEVTOOLS_STORAGE_OPENED_BOOLEAN",
userHistogram: "DEVTOOLS_STORAGE_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_STORAGE_TIME_ACTIVE_SECONDS"
},
tilt: {
histogram: "DEVTOOLS_TILT_OPENED_BOOLEAN",
userHistogram: "DEVTOOLS_TILT_OPENED_PER_USER_FLAG",

View File

@ -60,6 +60,7 @@ skip-if = e10s # Bug 1086492 - Disable tilt for e10s
[browser_telemetry_toolboxtabs_netmonitor.js]
[browser_telemetry_toolboxtabs_options.js]
[browser_telemetry_toolboxtabs_shadereditor.js]
[browser_telemetry_toolboxtabs_storage.js]
[browser_telemetry_toolboxtabs_styleeditor.js]
[browser_telemetry_toolboxtabs_webaudioeditor.js]
[browser_telemetry_toolboxtabs_webconsole.js]

View File

@ -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;
}

View File

@ -59,7 +59,7 @@ StoragePanel.prototype = {
},
/**
* Destroy the style editor.
* Destroy the storage inspector.
*/
destroy: function() {
if (!this._destroyed) {

View File

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

View File

@ -285,7 +285,14 @@ let UI = {
this.cancelBusyTimeout();
this.unbusy();
}, (e) => {
let message = operationDescription + (e ? (": " + e) : "");
let message;
if (e.error && e.message) {
// Some errors come from fronts that are not based on protocol.js.
// Errors are not translated to strings.
message = operationDescription + " (" + e.error + "): " + e.message;
} else {
message = operationDescription + (e ? (": " + e) : "");
}
this.cancelBusyTimeout();
let operationCanceled = e && e.canceled;
if (!operationCanceled) {
@ -817,7 +824,8 @@ let UI = {
playCmd.setAttribute("disabled", "true");
stopCmd.setAttribute("disabled", "true");
} else {
if (AppManager.selectedProject.errorsCount == 0) {
if (AppManager.selectedProject.errorsCount == 0 &&
AppManager.runtimeCanHandleApps()) {
playCmd.removeAttribute("disabled");
} else {
playCmd.setAttribute("disabled", "true");

View File

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

View File

@ -29,7 +29,7 @@
let fakeRuntime = {
type: "USB",
connect: function(connection) {
ok(connection, win.AppManager.connection, "connection is valid");
is(connection, win.AppManager.connection, "connection is valid");
connection.host = null; // force connectPipe
connection.connect();
return promise.resolve();

View File

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

View File

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

View File

@ -0,0 +1,173 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const RELATIVE_DIR = "browser/extensions/pdfjs/test/";
const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
const TESTS = [
{
action: {
selector: "button#zoomIn",
event: "click"
},
expectedZoom: 1, // 1 - zoom in
message: "Zoomed in using the '+' (zoom in) button"
},
{
action: {
selector: "button#zoomOut",
event: "click"
},
expectedZoom: -1, // -1 - zoom out
message: "Zoomed out using the '-' (zoom out) button"
},
{
action: {
keyboard: true,
event: "+"
},
expectedZoom: 1, // 1 - zoom in
message: "Zoomed in using the CTRL++ keys"
},
{
action: {
keyboard: true,
event: "-"
},
expectedZoom: -1, // -1 - zoom out
message: "Zoomed out using the CTRL+- keys"
},
{
action: {
selector: "select#scaleSelect",
index: 5,
event: "change"
},
expectedZoom: -1, // -1 - zoom out
message: "Zoomed using the zoom picker"
}
];
let initialWidth; // the initial width of the PDF document
var previousWidth; // the width of the PDF document at previous step/test
function test() {
var tab;
let handlerService = Cc["@mozilla.org/uriloader/handler-service;1"]
.getService(Ci.nsIHandlerService);
let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
// Make sure pdf.js is the default handler.
is(handlerInfo.alwaysAskBeforeHandling, false,
'pdf handler defaults to always-ask is false');
is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.handleInternally,
'pdf handler defaults to internal');
info('Pref action: ' + handlerInfo.preferredAction);
waitForExplicitFinish();
registerCleanupFunction(function() {
gBrowser.removeTab(tab);
});
tab = gBrowser.selectedTab = gBrowser.addTab(TESTROOT + "file_pdfjs_test.pdf");
var newTabBrowser = gBrowser.getBrowserForTab(tab);
newTabBrowser.addEventListener("load", function eventHandler() {
newTabBrowser.removeEventListener("load", eventHandler, true);
var document = newTabBrowser.contentDocument,
window = newTabBrowser.contentWindow;
// Runs tests after all 'load' event handlers have fired off
window.addEventListener("documentload", function() {
initialWidth = parseInt(document.querySelector("div#pageContainer1").style.width);
previousWidth = initialWidth;
runTests(document, window, finish);
}, false, true);
}, true);
}
function runTests(document, window, callback) {
// check that PDF is opened with internal viewer
ok(document.querySelector('div#viewer'), "document content has viewer UI");
ok('PDFJS' in window.wrappedJSObject, "window content has PDFJS object");
// Start the zooming tests after the document is loaded
waitForDocumentLoad(document).then(function () {
zoomPDF(document, window, TESTS.shift(), finish);
});
}
function waitForDocumentLoad(document) {
var deferred = Promise.defer();
var interval = setInterval(function () {
if (document.querySelector("div#pageContainer1") != null){
clearInterval(interval);
deferred.resolve();
}
}, 500);
return deferred.promise;
}
function zoomPDF(document, window, test, endCallback) {
var renderedPage;
document.addEventListener("pagerendered", function onPageRendered(e) {
if(e.detail.pageNumber !== 1) {
return;
}
document.removeEventListener("pagerendered", onPageRendered, true);
var pageZoomScale = document.querySelector('select#scaleSelect');
// The zoom value displayed in the zoom select
var zoomValue = pageZoomScale.options[pageZoomScale.selectedIndex].innerHTML;
let pageContainer = document.querySelector('div#pageContainer1');
let actualWidth = parseInt(pageContainer.style.width);
// the actual zoom of the PDF document
let computedZoomValue = parseInt(((actualWidth/initialWidth).toFixed(2))*100) + "%";
is(computedZoomValue, zoomValue, "Content has correct zoom");
// Check that document zooms in the expected way (in/out)
let zoom = (actualWidth - previousWidth) * test.expectedZoom;
ok(zoom > 0, test.message);
// Go to next test (if there is any) or finish
var nextTest = TESTS.shift();
if (nextTest) {
previousWidth = actualWidth;
zoomPDF(document, window, nextTest, endCallback);
}
else
endCallback();
}, true);
// We zoom using an UI element
if (test.action.selector) {
// Get the element and trigger the action for changing the zoom
var el = document.querySelector(test.action.selector);
ok(el, "Element '" + test.action.selector + "' has been found");
if (test.action.index){
el.selectedIndex = test.action.index;
}
// Dispatch the event for changing the zoom
el.dispatchEvent(new Event(test.action.event));
}
// We zoom using keyboard
else {
// Simulate key press
EventUtils.synthesizeKey(test.action.event, { ctrlKey: true });
}
}

View File

@ -8989,7 +8989,7 @@ if test "$ACCESSIBILITY" -a "$MOZ_ENABLE_GTK" ; then
AC_DEFINE_UNQUOTED(ATK_REV_VERSION, $ATK_REV_VERSION)
fi
if test -z "$RELEASE_BUILD" -a -z "$NIGHTLY_BUILD"; then
if test -n "$MOZ_DEV_EDITION"; then
AC_DEFINE(MOZ_DEV_EDITION)
fi

View File

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

View File

@ -121,7 +121,8 @@ public:
// the T values we support.
// Called by DOM to let us execute our callbacks. May be called recursively.
static void PerformMicroTaskCheckpoint();
// Returns true if at least one microtask was processed.
static bool PerformMicroTaskCheckpoint();
// WebIDL

View File

@ -4243,7 +4243,7 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
// Only perform the Promise microtask checkpoint on the outermost event
// loop. Don't run it, for example, during sync XHR or importScripts.
Promise::PerformMicroTaskCheckpoint();
(void)Promise::PerformMicroTaskCheckpoint();
if (NS_HasPendingEvents(mThread)) {
// Now *might* be a good time to GC. Let the JS engine make the decision.

View File

@ -1008,15 +1008,33 @@ nsXPConnect::JSToVariant(JSContext* ctx, HandleValue value, nsIVariant** _retval
return NS_OK;
}
namespace {
class DummyRunnable : public nsRunnable {
public:
NS_IMETHOD Run() { return NS_OK; }
};
} // anonymous namespace
NS_IMETHODIMP
nsXPConnect::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
uint32_t aRecursionDepth)
{
MOZ_ASSERT(NS_IsMainThread());
// If ProcessNextEvent was called during a Promise "then" callback, we
// must process any pending microtasks before blocking in the event loop,
// otherwise we may deadlock until an event enters the queue later.
if (aMayWait) {
Promise::PerformMicroTaskCheckpoint();
if (Promise::PerformMicroTaskCheckpoint()) {
// If any microtask was processed, we post a dummy event in order to
// force the ProcessNextEvent call not to block. This is required
// to support nested event loops implemented using a pattern like
// "while (condition) thread.processNextEvent(true)", in case the
// condition is triggered here by a Promise "then" callback.
NS_DispatchToMainThread(new DummyRunnable());
}
}
// Record this event.
@ -1024,7 +1042,6 @@ nsXPConnect::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
// Push a null JSContext so that we don't see any script during
// event processing.
MOZ_ASSERT(NS_IsMainThread());
bool ok = PushJSContextNoScriptContext(nullptr);
NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
return NS_OK;

View File

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

View File

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

View File

@ -7,7 +7,7 @@ package org.mozilla.mozstumbler.service;
import java.util.concurrent.ConcurrentLinkedQueue;
public class AppGlobals {
public static final String LOG_PREFIX = "Stumbler:";
public static final String LOG_PREFIX = "Stumbler_";
/* All intent actions start with this string. Only locally broadcasted. */
public static final String ACTION_NAMESPACE = "org.mozilla.mozstumbler.intent.action";
@ -57,6 +57,14 @@ public class AppGlobals {
}
}
public static String makeLogTag(String name) {
final int maxLen = 23 - LOG_PREFIX.length();
if (name.length() > maxLen) {
name = name.substring(name.length() - maxLen, name.length());
}
return LOG_PREFIX + name;
}
public static final String ACTION_TEST_SETTING_ENABLED = "stumbler-test-setting-enabled";
public static final String ACTION_TEST_SETTING_DISABLED = "stumbler-test-setting-disabled";
}

View File

@ -14,7 +14,7 @@ import android.text.TextUtils;
import android.util.Log;
public final class Prefs {
private static final String LOG_TAG = Prefs.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(Prefs.class.getSimpleName());
private static final String NICKNAME_PREF = "nickname";
private static final String USER_AGENT_PREF = "user-agent";
private static final String VALUES_VERSION_PREF = "values_version";

View File

@ -29,7 +29,9 @@ import org.mozilla.mozstumbler.service.stumblerthread.StumblerService;
* is a good time to try upload, as it is likely that the network is in use.
*/
public class PassiveServiceReceiver extends BroadcastReceiver {
static final String LOG_TAG = AppGlobals.LOG_PREFIX + PassiveServiceReceiver.class.getSimpleName();
// This allows global debugging logs to be enabled by doing
// |adb shell setprop log.tag.PassiveStumbler DEBUG|
static final String LOG_TAG = "PassiveStumbler";
@Override
public void onReceive(Context context, Intent intent) {
@ -37,6 +39,11 @@ public class PassiveServiceReceiver extends BroadcastReceiver {
return;
}
// This value is cached, so if |setprop| is performed (as described on the LOG_TAG above),
// then the start/stop intent must be resent by toggling the setting or stopping/starting Fennec.
// This does not guard against dumping PII (PII in stumbler is location, wifi BSSID, cell tower details).
AppGlobals.isDebug = Log.isLoggable(LOG_TAG, Log.DEBUG);
final String action = intent.getAction();
final boolean isIntentFromHostApp = (action != null) && action.contains(".STUMBLER_PREF");
if (!isIntentFromHostApp) {
@ -47,9 +54,6 @@ public class PassiveServiceReceiver extends BroadcastReceiver {
return;
}
if (intent.hasExtra("is_debug")) {
AppGlobals.isDebug = intent.getBooleanExtra("is_debug", false);
}
StumblerService.sFirefoxStumblingEnabled.set(intent.getBooleanExtra("enabled", false));
if (!StumblerService.sFirefoxStumblingEnabled.get()) {

View File

@ -30,7 +30,7 @@ import org.mozilla.mozstumbler.service.stumblerthread.scanners.GPSScanner;
import org.mozilla.mozstumbler.service.stumblerthread.scanners.WifiScanner;
public final class Reporter extends BroadcastReceiver {
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + Reporter.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(Reporter.class.getSimpleName());
public static final String ACTION_FLUSH_TO_BUNDLE = AppGlobals.ACTION_NAMESPACE + ".FLUSH";
private boolean mIsStarted;
@ -195,7 +195,8 @@ public final class Reporter extends BroadcastReceiver {
}
if (AppGlobals.isDebug) {
Log.d(LOG_TAG, "Received bundle: " + mlsObj.toString());
// PII: do not log the bundle without obfuscating it
Log.d(LOG_TAG, "Received bundle");
}
if (wifiCount + cellCount < 1) {

View File

@ -27,7 +27,7 @@ import org.mozilla.mozstumbler.service.utils.PersistentIntentService;
//
public class StumblerService extends PersistentIntentService
implements DataStorageManager.StorageIsEmptyTracker {
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + StumblerService.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(StumblerService.class.getSimpleName());
public static final String ACTION_BASE = AppGlobals.ACTION_NAMESPACE;
public static final String ACTION_START_PASSIVE = ACTION_BASE + ".START_PASSIVE";
public static final String ACTION_EXTRA_MOZ_API_KEY = ACTION_BASE + ".MOZKEY";
@ -144,6 +144,8 @@ public class StumblerService extends PersistentIntentService
public void onDestroy() {
super.onDestroy();
UploadAlarmReceiver.cancelAlarm(this, !mScanManager.isPassiveMode());
if (!mScanManager.isScanning()) {
return;
}
@ -198,7 +200,11 @@ public class StumblerService extends PersistentIntentService
return;
}
if (!DataStorageManager.getInstance().isDirEmpty()) {
boolean hasFilesWaiting = !DataStorageManager.getInstance().isDirEmpty();
if (AppGlobals.isDebug) {
Log.d(LOG_TAG, "Files waiting:" + hasFilesWaiting);
}
if (hasFilesWaiting) {
// non-empty on startup, schedule an upload
// This is the only upload trigger in Firefox mode
// Firefox triggers this ~4 seconds after startup (after Gecko is loaded), add a small delay to avoid

View File

@ -11,7 +11,7 @@ import java.util.Locale;
import java.util.regex.Pattern;
public final class BSSIDBlockList {
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + BSSIDBlockList.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(BSSIDBlockList.class.getSimpleName());
private static final String NULL_BSSID = "000000000000";
private static final String WILDCARD_BSSID = "ffffffffffff";
private static final Pattern BSSID_PATTERN = Pattern.compile("([0-9a-f]{12})");

View File

@ -38,7 +38,7 @@ import java.util.TimerTask;
* when the service is destroyed.
*/
public class DataStorageManager {
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + DataStorageManager.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(DataStorageManager.class.getSimpleName());
// The max number of reports stored in the mCurrentReports. Each report is a GPS location plus wifi and cell scan.
// After this size is reached, data is persisted to disk, mCurrentReports is cleared.
@ -201,19 +201,7 @@ public class DataStorageManager {
}
private String getStorageDir(Context c) {
File dir = null;
if (AppGlobals.isDebug) {
// in debug, put files in public location
dir = c.getExternalFilesDir(null);
if (dir != null) {
dir = new File(dir.getAbsolutePath() + "/mozstumbler");
}
}
if (dir == null) {
dir = c.getFilesDir();
}
File dir = c.getFilesDir();
if (!dir.exists()) {
boolean ok = dir.mkdirs();
if (!ok) {
@ -414,9 +402,6 @@ public class DataStorageManager {
}
final String result = sb.append(kSuffix).toString();
if (AppGlobals.isDebug) {
Log.d(LOG_TAG, result);
}
return result;
}

View File

@ -34,7 +34,7 @@ public class GPSScanner implements LocationListener {
public static final String NEW_STATUS_ARG_SATS = "sats";
public static final String NEW_LOCATION_ARG_LOCATION = "location";
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + GPSScanner.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(GPSScanner.class.getSimpleName());
private static final int MIN_SAT_USED_IN_FIX = 3;
private static final long ACTIVE_MODE_GPS_MIN_UPDATE_TIME_MS = 1000;
private static final float ACTIVE_MODE_GPS_MIN_UPDATE_DISTANCE_M = 10;
@ -191,15 +191,10 @@ public class GPSScanner implements LocationListener {
sendToLogActivity(logMsg);
if (mBlockList.contains(location)) {
Log.w(LOG_TAG, "Blocked location: " + location);
reportLocationLost();
return;
}
if (AppGlobals.isDebug) {
Log.d(LOG_TAG, "New location: " + location);
}
mLocation = location;
if (!mAutoGeofencing) {

View File

@ -10,7 +10,7 @@ import org.mozilla.mozstumbler.service.AppGlobals;
import org.mozilla.mozstumbler.service.Prefs;
public final class LocationBlockList {
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + LocationBlockList.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(LocationBlockList.class.getSimpleName());
private static final double MAX_ALTITUDE = 8848; // Mount Everest's altitude in meters
private static final double MIN_ALTITUDE = -418; // Dead Sea's altitude in meters
private static final float MAX_SPEED = 340.29f; // Mach 1 in meters/second

View File

@ -23,7 +23,7 @@ import java.util.Timer;
import java.util.TimerTask;
public class ScanManager {
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + ScanManager.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(ScanManager.class.getSimpleName());
private Timer mPassiveModeFlushTimer;
private Context mContext;
private boolean mIsScanning;

View File

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

View File

@ -19,7 +19,7 @@ import org.json.JSONObject;
import org.mozilla.mozstumbler.service.AppGlobals;
public class CellInfo implements Parcelable {
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + CellInfo.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(CellInfo.class.getSimpleName());
public static final String RADIO_GSM = "gsm";
public static final String RADIO_CDMA = "cdma";

View File

@ -26,7 +26,7 @@ public class CellScanner {
public static final String ACTION_CELLS_SCANNED_ARG_CELLS = "cells";
public static final String ACTION_CELLS_SCANNED_ARG_TIME = AppGlobals.ACTION_ARG_TIME;
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + CellScanner.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(CellScanner.class.getSimpleName());
private static final long CELL_MIN_UPDATE_TIME = 1000; // milliseconds
private final Context mContext;

View File

@ -31,7 +31,7 @@ import java.util.List;
/* Fennec does not yet support the api level for WCDMA import */
public class CellScannerNoWCDMA implements CellScanner.CellScannerImpl {
protected static String LOG_TAG = AppGlobals.LOG_PREFIX + CellScannerNoWCDMA.class.getSimpleName();
protected static String LOG_TAG = AppGlobals.makeLogTag(CellScannerNoWCDMA.class.getSimpleName());
protected GetAllCellInfoScannerImpl mGetAllInfoCellScanner;
protected TelephonyManager mTelephonyManager;
protected boolean mIsStarted;

View File

@ -24,7 +24,7 @@ import org.mozilla.mozstumbler.service.utils.NetworkUtils;
* preferences, do not call any code that isn't thread-safe. You will cause suffering.
* An exception is made for AppGlobals.isDebug, a false reading is of no consequence. */
public class AsyncUploader extends AsyncTask<Void, Void, SyncSummary> {
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + AsyncUploader.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(AsyncUploader.class.getSimpleName());
private final UploadSettings mSettings;
private final Object mListenerLock = new Object();
private AsyncUploaderListener mListener;

View File

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

View File

@ -20,7 +20,7 @@ import java.net.URL;
public abstract class AbstractCommunicator {
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + AbstractCommunicator.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(AbstractCommunicator.class.getSimpleName());
private static final String NICKNAME_HEADER = "X-Nickname";
private static final String USER_AGENT_HEADER = "User-Agent";
private HttpURLConnection mHttpURLConnection;

View File

@ -11,7 +11,7 @@ import android.util.Log;
import org.mozilla.mozstumbler.service.AppGlobals;
public final class NetworkUtils {
private static final String LOG_TAG = AppGlobals.LOG_PREFIX + NetworkUtils.class.getSimpleName();
private static final String LOG_TAG = AppGlobals.makeLogTag(NetworkUtils.class.getSimpleName());
ConnectivityManager mConnectivityManager;
static NetworkUtils sInstance;

View File

@ -5856,6 +5856,11 @@
"kind": "boolean",
"description": "How many times has the devtool's Network Monitor been opened?"
},
"DEVTOOLS_STORAGE_OPENED_BOOLEAN": {
"expires_in_version": "never",
"kind": "boolean",
"description": "How many times has the Storage Inspector been opened?"
},
"DEVTOOLS_PAINTFLASHING_OPENED_BOOLEAN": {
"expires_in_version": "never",
"kind": "boolean",
@ -5976,6 +5981,11 @@
"kind": "flag",
"description": "How many users have opened the devtool's Network Monitor?"
},
"DEVTOOLS_STORAGE_OPENED_PER_USER_FLAG": {
"expires_in_version": "never",
"kind": "flag",
"description": "How many users have opened the devtool's Storage Inspector?"
},
"DEVTOOLS_PAINTFLASHING_OPENED_PER_USER_FLAG": {
"expires_in_version": "never",
"kind": "flag",
@ -6130,6 +6140,13 @@
"n_buckets": 100,
"description": "How long has the network monitor been active (seconds)"
},
"DEVTOOLS_STORAGE_TIME_ACTIVE_SECONDS": {
"expires_in_version": "never",
"kind": "exponential",
"high": "10000000",
"n_buckets": 100,
"description": "How long has the storage inspector been active (seconds)"
},
"DEVTOOLS_PAINTFLASHING_TIME_ACTIVE_SECONDS": {
"expires_in_version": "never",
"kind": "exponential",