зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c a=merge despite the CLOSED TREE
This commit is contained in:
Коммит
60713994dc
|
@ -19,11 +19,7 @@ function testVal(aExpected) {
|
|||
is(result, aExpected);
|
||||
}
|
||||
|
||||
add_task(function* () {
|
||||
return new Promise(resolve => Services.search.init(resolve));
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
function test() {
|
||||
const prefname = "browser.urlbar.formatting.enabled";
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
|
@ -113,4 +109,4 @@ add_task(function* () {
|
|||
Services.prefs.setBoolPref(prefname, false);
|
||||
|
||||
testVal("https://mozilla.org");
|
||||
});
|
||||
}
|
||||
|
|
|
@ -185,25 +185,11 @@
|
|||
</method>
|
||||
|
||||
<field name="_formattingEnabled">true</field>
|
||||
<field name="_searchServiceInitialized">false</field>
|
||||
<method name="formatValue">
|
||||
<body><![CDATA[
|
||||
if (!this._formattingEnabled || this.focused)
|
||||
return;
|
||||
|
||||
// Initialize the search service asynchronously if that hasn't
|
||||
// happened yet. We will need it to highlight search terms later.
|
||||
if (!this._searchServiceInitialized) {
|
||||
Services.search.init(() => {
|
||||
if (this.formatValue) {
|
||||
this._searchServiceInitialized = true;
|
||||
this.formatValue();
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let controller = this.editor.selectionController;
|
||||
let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
|
||||
selection.removeAllRanges();
|
||||
|
@ -238,29 +224,20 @@
|
|||
subDomain = domain.slice(0, -baseDomain.length);
|
||||
}
|
||||
|
||||
function addSelectionRange(start, end) {
|
||||
let rangeLength = preDomain.length + subDomain.length;
|
||||
if (rangeLength) {
|
||||
let range = document.createRange();
|
||||
range.setStart(textNode, start);
|
||||
range.setEnd(textNode, end);
|
||||
range.setStart(textNode, 0);
|
||||
range.setEnd(textNode, rangeLength);
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
let rangeLength = preDomain.length + subDomain.length;
|
||||
if (rangeLength) {
|
||||
addSelectionRange(0, rangeLength);
|
||||
}
|
||||
|
||||
let result = Services.search.parseSubmissionURL(value);
|
||||
let startRest = preDomain.length + domain.length;
|
||||
|
||||
// Format search terms in the URL, if any.
|
||||
if (result.termsOffset > -1 && result.termsLength) {
|
||||
addSelectionRange(startRest, result.termsOffset);
|
||||
startRest = result.termsOffset + result.termsLength;
|
||||
}
|
||||
|
||||
if (startRest < value.length) {
|
||||
addSelectionRange(startRest, value.length);
|
||||
let range = document.createRange();
|
||||
range.setStart(textNode, startRest);
|
||||
range.setEnd(textNode, value.length);
|
||||
selection.addRange(range);
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
@ -650,7 +627,6 @@
|
|||
this.timeout = this._prefs.getIntPref(aData);
|
||||
break;
|
||||
case "formatting.enabled":
|
||||
this._clearFormatting();
|
||||
this._formattingEnabled = this._prefs.getBoolPref(aData);
|
||||
break;
|
||||
case "trimURLs":
|
||||
|
|
|
@ -68,15 +68,6 @@ loop.conversation = (function(mozL10n) {
|
|||
return false;
|
||||
},
|
||||
|
||||
_toggleDeclineMenu: function() {
|
||||
var currentState = this.state.showDeclineMenu;
|
||||
this.setState({showDeclineMenu: !currentState});
|
||||
},
|
||||
|
||||
_hideDeclineMenu: function() {
|
||||
this.setState({showDeclineMenu: false});
|
||||
},
|
||||
|
||||
/*
|
||||
* Generate props for <AcceptCallButton> component based on
|
||||
* incoming call type. An incoming video call will render a video
|
||||
|
|
|
@ -12,10 +12,12 @@ loop.conversation = (function(mozL10n) {
|
|||
"use strict";
|
||||
|
||||
var sharedViews = loop.shared.views;
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
var sharedModels = loop.shared.models;
|
||||
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
|
||||
|
||||
var IncomingCallView = React.createClass({
|
||||
mixins: [sharedMixins.DropdownMenuMixin],
|
||||
|
||||
propTypes: {
|
||||
model: React.PropTypes.object.isRequired,
|
||||
|
@ -24,25 +26,11 @@ loop.conversation = (function(mozL10n) {
|
|||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
showDeclineMenu: false,
|
||||
showMenu: false,
|
||||
video: true
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {showDeclineMenu: this.props.showDeclineMenu};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
window.addEventListener("click", this.clickHandler);
|
||||
window.addEventListener("blur", this._hideDeclineMenu);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener("click", this.clickHandler);
|
||||
window.removeEventListener("blur", this._hideDeclineMenu);
|
||||
},
|
||||
|
||||
clickHandler: function(e) {
|
||||
var target = e.target;
|
||||
if (!target.classList.contains('btn-chevron')) {
|
||||
|
@ -68,15 +56,6 @@ loop.conversation = (function(mozL10n) {
|
|||
return false;
|
||||
},
|
||||
|
||||
_toggleDeclineMenu: function() {
|
||||
var currentState = this.state.showDeclineMenu;
|
||||
this.setState({showDeclineMenu: !currentState});
|
||||
},
|
||||
|
||||
_hideDeclineMenu: function() {
|
||||
this.setState({showDeclineMenu: false});
|
||||
},
|
||||
|
||||
/*
|
||||
* Generate props for <AcceptCallButton> component based on
|
||||
* incoming call type. An incoming video call will render a video
|
||||
|
@ -113,7 +92,7 @@ loop.conversation = (function(mozL10n) {
|
|||
var dropdownMenuClassesDecline = React.addons.classSet({
|
||||
"native-dropdown-menu": true,
|
||||
"conversation-window-dropdown": true,
|
||||
"visually-hidden": !this.state.showDeclineMenu
|
||||
"visually-hidden": !this.state.showMenu
|
||||
});
|
||||
return (
|
||||
<div className="call-window">
|
||||
|
@ -126,13 +105,11 @@ loop.conversation = (function(mozL10n) {
|
|||
<div className="btn-group-chevron">
|
||||
<div className="btn-group">
|
||||
|
||||
<button className="btn btn-error btn-decline"
|
||||
<button className="btn btn-decline"
|
||||
onClick={this._handleDecline}>
|
||||
{mozL10n.get("incoming_call_cancel_button")}
|
||||
</button>
|
||||
<div className="btn-chevron"
|
||||
onClick={this._toggleDeclineMenu}>
|
||||
</div>
|
||||
<div className="btn-chevron" onClick={this.toggleDropdownMenu} />
|
||||
</div>
|
||||
|
||||
<ul className={dropdownMenuClassesDecline}>
|
||||
|
|
|
@ -137,7 +137,9 @@ p {
|
|||
|
||||
.btn-cancel,
|
||||
.btn-error,
|
||||
.btn-decline,
|
||||
.btn-hangup,
|
||||
.btn-decline + .btn-chevron,
|
||||
.btn-error + .btn-chevron {
|
||||
background-color: #d74345;
|
||||
border: 1px solid #d74345;
|
||||
|
@ -145,7 +147,9 @@ p {
|
|||
|
||||
.btn-cancel:hover,
|
||||
.btn-error:hover,
|
||||
.btn-decline:hover,
|
||||
.btn-hangup:hover,
|
||||
.btn-decline + .btn-chevron:hover,
|
||||
.btn-error + .btn-chevron:hover {
|
||||
background-color: #c53436;
|
||||
border: 1px solid #c53436;
|
||||
|
@ -153,7 +157,9 @@ p {
|
|||
|
||||
.btn-cancel:active,
|
||||
.btn-error:active,
|
||||
.btn-decline:active,
|
||||
.btn-hangup:active,
|
||||
.btn-decline + .btn-chevron:active,
|
||||
.btn-error + .btn-chevron:active {
|
||||
background-color: #ae2325;
|
||||
border: 1px solid #ae2325;
|
||||
|
@ -182,6 +188,7 @@ p {
|
|||
}
|
||||
|
||||
.btn-group-chevron .btn {
|
||||
border-radius: 2px;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
flex: 2;
|
||||
|
@ -369,7 +376,7 @@ p {
|
|||
padding: 20px 0;
|
||||
border: 1px solid #e7e7e7;
|
||||
box-shadow: 0 2px 0 rgba(0, 0, 0, .03);
|
||||
margin-bottom: 25px;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.info-panel h1 {
|
||||
|
|
|
@ -31,6 +31,10 @@ loop.shared.mixins = (function() {
|
|||
* @type {Object}
|
||||
*/
|
||||
var DropdownMenuMixin = {
|
||||
get documentBody() {
|
||||
return rootObject.document.body;
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {showMenu: false};
|
||||
},
|
||||
|
@ -40,11 +44,13 @@ loop.shared.mixins = (function() {
|
|||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
rootObject.document.body.addEventListener("click", this._onBodyClick);
|
||||
this.documentBody.addEventListener("click", this._onBodyClick);
|
||||
this.documentBody.addEventListener("blur", this.hideDropdownMenu);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
rootObject.document.body.removeEventListener("click", this._onBodyClick);
|
||||
this.documentBody.removeEventListener("click", this._onBodyClick);
|
||||
this.documentBody.removeEventListener("blur", this.hideDropdownMenu);
|
||||
},
|
||||
|
||||
showDropdownMenu: function() {
|
||||
|
@ -53,7 +59,11 @@ loop.shared.mixins = (function() {
|
|||
|
||||
hideDropdownMenu: function() {
|
||||
this.setState({showMenu: false});
|
||||
}
|
||||
},
|
||||
|
||||
toggleDropdownMenu: function() {
|
||||
this.setState({showMenu: !this.state.showMenu});
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,8 +29,8 @@ loop.shared.models = (function(l10n) {
|
|||
// requires.
|
||||
callType: undefined, // The type of incoming call selected by
|
||||
// other peer ("audio" or "audio-video")
|
||||
selectedCallType: undefined, // The selected type for the call that was
|
||||
// initiated ("audio" or "audio-video")
|
||||
selectedCallType: "audio-video", // The selected type for the call that was
|
||||
// initiated ("audio" or "audio-video")
|
||||
callToken: undefined, // Incoming call token.
|
||||
// Used for blocking a call url
|
||||
subscribedStream: false, // Used to indicate that a stream has been
|
||||
|
@ -86,8 +86,13 @@ loop.shared.models = (function(l10n) {
|
|||
/**
|
||||
* Used to indicate that an outgoing call should start any necessary
|
||||
* set-up.
|
||||
*
|
||||
* @param {String} selectedCallType Call type ("audio" or "audio-video")
|
||||
*/
|
||||
setupOutgoingCall: function() {
|
||||
setupOutgoingCall: function(selectedCallType) {
|
||||
if (selectedCallType) {
|
||||
this.set("selectedCallType", selectedCallType);
|
||||
}
|
||||
this.trigger("call:outgoing:setup");
|
||||
},
|
||||
|
||||
|
|
|
@ -115,8 +115,9 @@ body,
|
|||
line-height: 2.2rem;
|
||||
}
|
||||
|
||||
.standalone-btn-label {
|
||||
p.standalone-btn-label {
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.light-color-font {
|
||||
|
|
|
@ -14,9 +14,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
loop.config = loop.config || {};
|
||||
loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
|
||||
|
||||
var sharedModels = loop.shared.models,
|
||||
sharedViews = loop.shared.views,
|
||||
sharedUtils = loop.shared.utils;
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
var sharedModels = loop.shared.models;
|
||||
var sharedViews = loop.shared.views;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
|
||||
/**
|
||||
* Homepage view.
|
||||
|
@ -116,7 +117,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
render: function() {
|
||||
return (
|
||||
React.DOM.h1({className: "standalone-header-title"},
|
||||
React.DOM.strong(null, mozL10n.get("brandShortname")), " ", mozL10n.get("clientShortname")
|
||||
React.DOM.strong(null, mozL10n.get("brandShortname")),
|
||||
mozL10n.get("clientShortname")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -305,53 +307,105 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
React.DOM.div({className: "flex-padding-1"})
|
||||
)
|
||||
),
|
||||
|
||||
ConversationFooter(null)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Conversation launcher view. A ConversationModel is associated and attached
|
||||
* as a `model` property.
|
||||
*
|
||||
* Required properties:
|
||||
* - {loop.shared.models.ConversationModel} model Conversation model.
|
||||
* - {loop.shared.models.NotificationCollection} notifications
|
||||
*/
|
||||
var StartConversationView = React.createClass({displayName: 'StartConversationView',
|
||||
var InitiateCallButton = React.createClass({displayName: 'InitiateCallButton',
|
||||
mixins: [sharedMixins.DropdownMenuMixin],
|
||||
|
||||
propTypes: {
|
||||
model: React.PropTypes.oneOfType([
|
||||
React.PropTypes.instanceOf(sharedModels.ConversationModel),
|
||||
React.PropTypes.instanceOf(FxOSConversationModel)
|
||||
]).isRequired,
|
||||
// XXX Check more tightly here when we start injecting window.loop.*
|
||||
notifications: React.PropTypes.object.isRequired,
|
||||
client: React.PropTypes.object.isRequired
|
||||
caption: React.PropTypes.string.isRequired,
|
||||
startCall: React.PropTypes.func.isRequired,
|
||||
disabled: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {showCallOptionsMenu: false};
|
||||
return {disabled: false};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var dropdownMenuClasses = React.addons.classSet({
|
||||
"native-dropdown-large-parent": true,
|
||||
"standalone-dropdown-menu": true,
|
||||
"visually-hidden": !this.state.showMenu
|
||||
});
|
||||
var chevronClasses = React.addons.classSet({
|
||||
"btn-chevron": true,
|
||||
"disabled": this.props.disabled
|
||||
});
|
||||
return (
|
||||
React.DOM.div({className: "standalone-btn-chevron-menu-group"},
|
||||
React.DOM.div({className: "btn-group-chevron"},
|
||||
React.DOM.div({className: "btn-group"},
|
||||
React.DOM.button({className: "btn btn-large btn-accept",
|
||||
onClick: this.props.startCall("audio-video"),
|
||||
disabled: this.props.disabled,
|
||||
title: mozL10n.get("initiate_audio_video_call_tooltip2")},
|
||||
React.DOM.span({className: "standalone-call-btn-text"},
|
||||
this.props.caption
|
||||
),
|
||||
React.DOM.span({className: "standalone-call-btn-video-icon"})
|
||||
),
|
||||
React.DOM.div({className: chevronClasses,
|
||||
onClick: this.toggleDropdownMenu}
|
||||
)
|
||||
),
|
||||
React.DOM.ul({className: dropdownMenuClasses},
|
||||
React.DOM.li(null,
|
||||
React.DOM.button({className: "start-audio-only-call",
|
||||
onClick: this.props.startCall("audio"),
|
||||
disabled: this.props.disabled},
|
||||
mozL10n.get("initiate_audio_call_button2")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Initiate conversation view.
|
||||
*/
|
||||
var InitiateConversationView = React.createClass({displayName: 'InitiateConversationView',
|
||||
mixins: [Backbone.Events],
|
||||
|
||||
propTypes: {
|
||||
conversation: React.PropTypes.oneOfType([
|
||||
React.PropTypes.instanceOf(sharedModels.ConversationModel),
|
||||
React.PropTypes.instanceOf(FxOSConversationModel)
|
||||
]).isRequired,
|
||||
// XXX Check more tightly here when we start injecting window.loop.*
|
||||
notifications: React.PropTypes.object.isRequired,
|
||||
client: React.PropTypes.object.isRequired,
|
||||
title: React.PropTypes.string.isRequired,
|
||||
callButtonLabel: React.PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
urlCreationDateString: '',
|
||||
disableCallButton: false,
|
||||
showCallOptionsMenu: this.props.showCallOptionsMenu
|
||||
disableCallButton: false
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
// Listen for events & hide dropdown menu if user clicks away
|
||||
window.addEventListener("click", this.clickHandler);
|
||||
this.props.model.listenTo(this.props.model, "session:error",
|
||||
this._onSessionError);
|
||||
this.props.model.listenTo(this.props.model, "fxos:app-needed",
|
||||
this._onFxOSAppNeeded);
|
||||
this.props.client.requestCallUrlInfo(this.props.model.get("loopToken"),
|
||||
this._setConversationTimestamp);
|
||||
this.listenTo(this.props.conversation,
|
||||
"session:error", this._onSessionError);
|
||||
this.listenTo(this.props.conversation,
|
||||
"fxos:app-needed", this._onFxOSAppNeeded);
|
||||
this.props.client.requestCallUrlInfo(
|
||||
this.props.conversation.get("loopToken"),
|
||||
this._setConversationTimestamp);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.stopListening(this.props.conversation);
|
||||
localStorage.setItem("has-seen-tos", "true");
|
||||
},
|
||||
|
||||
_onSessionError: function(error, l10nProps) {
|
||||
|
@ -362,11 +416,9 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
|
||||
_onFxOSAppNeeded: function() {
|
||||
this.setState({
|
||||
marketplaceSrc: loop.config.marketplaceUrl
|
||||
});
|
||||
this.setState({
|
||||
onMarketplaceMessage: this.props.model.onMarketplaceMessage.bind(
|
||||
this.props.model
|
||||
marketplaceSrc: loop.config.marketplaceUrl,
|
||||
onMarketplaceMessage: this.props.conversation.onMarketplaceMessage.bind(
|
||||
this.props.conversation
|
||||
)
|
||||
});
|
||||
},
|
||||
|
@ -379,11 +431,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
*
|
||||
* @param {string} User call type choice "audio" or "audio-video"
|
||||
*/
|
||||
_initiateOutgoingCall: function(callType) {
|
||||
startCall: function(callType) {
|
||||
return function() {
|
||||
this.props.model.set("selectedCallType", callType);
|
||||
this.props.conversation.setupOutgoingCall(callType);
|
||||
this.setState({disableCallButton: true});
|
||||
this.props.model.setupOutgoingCall();
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
|
@ -398,47 +449,21 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener("click", this.clickHandler);
|
||||
localStorage.setItem("has-seen-tos", "true");
|
||||
},
|
||||
|
||||
clickHandler: function(e) {
|
||||
if (!e.target.classList.contains('btn-chevron') &&
|
||||
this.state.showCallOptionsMenu) {
|
||||
this._toggleCallOptionsMenu();
|
||||
}
|
||||
},
|
||||
|
||||
_toggleCallOptionsMenu: function() {
|
||||
var state = this.state.showCallOptionsMenu;
|
||||
this.setState({showCallOptionsMenu: !state});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var tos_link_name = mozL10n.get("terms_of_use_link_text");
|
||||
var privacy_notice_name = mozL10n.get("privacy_notice_link_text");
|
||||
var tosLinkName = mozL10n.get("terms_of_use_link_text");
|
||||
var privacyNoticeName = mozL10n.get("privacy_notice_link_text");
|
||||
|
||||
var tosHTML = mozL10n.get("legal_text_and_links", {
|
||||
"terms_of_use_url": "<a target=_blank href='/legal/terms/'>" +
|
||||
tos_link_name + "</a>",
|
||||
tosLinkName + "</a>",
|
||||
"privacy_notice_url": "<a target=_blank href='" +
|
||||
"https://www.mozilla.org/privacy/'>" + privacy_notice_name + "</a>"
|
||||
"https://www.mozilla.org/privacy/'>" + privacyNoticeName + "</a>"
|
||||
});
|
||||
|
||||
var dropdownMenuClasses = React.addons.classSet({
|
||||
"native-dropdown-large-parent": true,
|
||||
"standalone-dropdown-menu": true,
|
||||
"visually-hidden": !this.state.showCallOptionsMenu
|
||||
});
|
||||
var tosClasses = React.addons.classSet({
|
||||
"terms-service": true,
|
||||
hide: (localStorage.getItem("has-seen-tos") === "true")
|
||||
});
|
||||
var chevronClasses = React.addons.classSet({
|
||||
"btn-chevron": true,
|
||||
"disabled": this.state.disableCallButton
|
||||
});
|
||||
|
||||
return (
|
||||
React.DOM.div({className: "container"},
|
||||
|
@ -448,47 +473,17 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
urlCreationDateString: this.state.urlCreationDateString}),
|
||||
|
||||
React.DOM.p({className: "standalone-btn-label"},
|
||||
mozL10n.get("initiate_call_button_label2")
|
||||
this.props.title
|
||||
),
|
||||
|
||||
React.DOM.div({id: "messages"}),
|
||||
|
||||
React.DOM.div({className: "btn-group"},
|
||||
React.DOM.div({className: "flex-padding-1"}),
|
||||
React.DOM.div({className: "standalone-btn-chevron-menu-group"},
|
||||
React.DOM.div({className: "btn-group-chevron"},
|
||||
React.DOM.div({className: "btn-group"},
|
||||
|
||||
React.DOM.button({className: "btn btn-large btn-accept",
|
||||
onClick: this._initiateOutgoingCall("audio-video"),
|
||||
disabled: this.state.disableCallButton,
|
||||
title: mozL10n.get("initiate_audio_video_call_tooltip2")},
|
||||
React.DOM.span({className: "standalone-call-btn-text"},
|
||||
mozL10n.get("initiate_audio_video_call_button2")
|
||||
),
|
||||
React.DOM.span({className: "standalone-call-btn-video-icon"})
|
||||
),
|
||||
|
||||
React.DOM.div({className: chevronClasses,
|
||||
onClick: this._toggleCallOptionsMenu}
|
||||
)
|
||||
|
||||
),
|
||||
|
||||
React.DOM.ul({className: dropdownMenuClasses},
|
||||
React.DOM.li(null,
|
||||
/*
|
||||
Button required for disabled state.
|
||||
*/
|
||||
React.DOM.button({className: "start-audio-only-call",
|
||||
onClick: this._initiateOutgoingCall("audio"),
|
||||
disabled: this.state.disableCallButton},
|
||||
mozL10n.get("initiate_audio_call_button2")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
InitiateCallButton({
|
||||
caption: this.props.callButtonLabel,
|
||||
disabled: this.state.disableCallButton,
|
||||
startCall: this.startCall}
|
||||
),
|
||||
React.DOM.div({className: "flex-padding-1"})
|
||||
),
|
||||
|
@ -538,6 +533,26 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
var StartConversationView = React.createClass({displayName: 'StartConversationView',
|
||||
render: function() {
|
||||
return this.transferPropsTo(
|
||||
InitiateConversationView({
|
||||
title: mozL10n.get("initiate_call_button_label2"),
|
||||
callButtonLabel: mozL10n.get("initiate_audio_video_call_button2")})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var FailedConversationView = React.createClass({displayName: 'FailedConversationView',
|
||||
render: function() {
|
||||
return this.transferPropsTo(
|
||||
InitiateConversationView({
|
||||
title: mozL10n.get("call_failed_title"),
|
||||
callButtonLabel: mozL10n.get("retry_call_button")})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* This view manages the outgoing conversation views - from
|
||||
* call initiation through to the actual conversation and call end.
|
||||
|
@ -595,11 +610,19 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
*/
|
||||
render: function() {
|
||||
switch (this.state.callStatus) {
|
||||
case "failure":
|
||||
case "start": {
|
||||
return (
|
||||
StartConversationView({
|
||||
model: this.props.conversation,
|
||||
conversation: this.props.conversation,
|
||||
notifications: this.props.notifications,
|
||||
client: this.props.client}
|
||||
)
|
||||
);
|
||||
}
|
||||
case "failure": {
|
||||
return (
|
||||
FailedConversationView({
|
||||
conversation: this.props.conversation,
|
||||
notifications: this.props.notifications,
|
||||
client: this.props.client}
|
||||
)
|
||||
|
@ -775,18 +798,17 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
/**
|
||||
* Handles call rejection.
|
||||
*
|
||||
* @param {String} reason The reason the call was terminated.
|
||||
* @param {String} reason The reason the call was terminated (reject, busy,
|
||||
* timeout, cancel, media-fail, user-unknown, closed)
|
||||
*/
|
||||
_handleCallTerminated: function(reason) {
|
||||
if (reason !== "cancel") {
|
||||
// XXX This should really display the call failed view - bug 1046959
|
||||
// will implement this.
|
||||
this.props.notifications.errorL10n("call_timeout_notification_text");
|
||||
if (reason === "cancel") {
|
||||
this.setState({callStatus: "start"});
|
||||
return;
|
||||
}
|
||||
// redirects the user to the call start view
|
||||
// XXX should switch callStatus to failed for specific reasons when we
|
||||
// get the call failed view; for now, switch back to start.
|
||||
this.setState({callStatus: "start"});
|
||||
// XXX later, we'll want to display more meaningfull messages (needs UX)
|
||||
this.props.notifications.errorL10n("call_timeout_notification_text");
|
||||
this.setState({callStatus: "failure"});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -893,6 +915,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
CallUrlExpiredView: CallUrlExpiredView,
|
||||
PendingConversationView: PendingConversationView,
|
||||
StartConversationView: StartConversationView,
|
||||
FailedConversationView: FailedConversationView,
|
||||
OutgoingConversationView: OutgoingConversationView,
|
||||
EndedConversationView: EndedConversationView,
|
||||
HomeView: HomeView,
|
||||
|
|
|
@ -14,9 +14,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
loop.config = loop.config || {};
|
||||
loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
|
||||
|
||||
var sharedModels = loop.shared.models,
|
||||
sharedViews = loop.shared.views,
|
||||
sharedUtils = loop.shared.utils;
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
var sharedModels = loop.shared.models;
|
||||
var sharedViews = loop.shared.views;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
|
||||
/**
|
||||
* Homepage view.
|
||||
|
@ -116,7 +117,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
render: function() {
|
||||
return (
|
||||
<h1 className="standalone-header-title">
|
||||
<strong>{mozL10n.get("brandShortname")}</strong> {mozL10n.get("clientShortname")}
|
||||
<strong>{mozL10n.get("brandShortname")}</strong>
|
||||
{mozL10n.get("clientShortname")}
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
@ -234,7 +236,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
<h3 className="call-url">
|
||||
{conversationUrl}
|
||||
</h3>
|
||||
<h4 className={urlCreationDateClasses} >
|
||||
<h4 className={urlCreationDateClasses}>
|
||||
{callUrlCreationDateString}
|
||||
</h4>
|
||||
</header>
|
||||
|
@ -286,72 +288,124 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
<ConversationBranding />
|
||||
</header>
|
||||
|
||||
<div id="cameraPreview"></div>
|
||||
<div id="cameraPreview" />
|
||||
|
||||
<div id="messages"></div>
|
||||
<div id="messages" />
|
||||
|
||||
<p className="standalone-btn-label">
|
||||
{callState}
|
||||
</p>
|
||||
|
||||
<div className="btn-pending-cancel-group btn-group">
|
||||
<div className="flex-padding-1"></div>
|
||||
<div className="flex-padding-1" />
|
||||
<button className="btn btn-large btn-cancel"
|
||||
onClick={this._cancelOutgoingCall} >
|
||||
<span className="standalone-call-btn-text">
|
||||
{mozL10n.get("initiate_call_cancel_button")}
|
||||
</span>
|
||||
</button>
|
||||
<div className="flex-padding-1"></div>
|
||||
<div className="flex-padding-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConversationFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Conversation launcher view. A ConversationModel is associated and attached
|
||||
* as a `model` property.
|
||||
*
|
||||
* Required properties:
|
||||
* - {loop.shared.models.ConversationModel} model Conversation model.
|
||||
* - {loop.shared.models.NotificationCollection} notifications
|
||||
*/
|
||||
var StartConversationView = React.createClass({
|
||||
var InitiateCallButton = React.createClass({
|
||||
mixins: [sharedMixins.DropdownMenuMixin],
|
||||
|
||||
propTypes: {
|
||||
model: React.PropTypes.oneOfType([
|
||||
React.PropTypes.instanceOf(sharedModels.ConversationModel),
|
||||
React.PropTypes.instanceOf(FxOSConversationModel)
|
||||
]).isRequired,
|
||||
// XXX Check more tightly here when we start injecting window.loop.*
|
||||
notifications: React.PropTypes.object.isRequired,
|
||||
client: React.PropTypes.object.isRequired
|
||||
caption: React.PropTypes.string.isRequired,
|
||||
startCall: React.PropTypes.func.isRequired,
|
||||
disabled: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {showCallOptionsMenu: false};
|
||||
return {disabled: false};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var dropdownMenuClasses = React.addons.classSet({
|
||||
"native-dropdown-large-parent": true,
|
||||
"standalone-dropdown-menu": true,
|
||||
"visually-hidden": !this.state.showMenu
|
||||
});
|
||||
var chevronClasses = React.addons.classSet({
|
||||
"btn-chevron": true,
|
||||
"disabled": this.props.disabled
|
||||
});
|
||||
return (
|
||||
<div className="standalone-btn-chevron-menu-group">
|
||||
<div className="btn-group-chevron">
|
||||
<div className="btn-group">
|
||||
<button className="btn btn-large btn-accept"
|
||||
onClick={this.props.startCall("audio-video")}
|
||||
disabled={this.props.disabled}
|
||||
title={mozL10n.get("initiate_audio_video_call_tooltip2")}>
|
||||
<span className="standalone-call-btn-text">
|
||||
{this.props.caption}
|
||||
</span>
|
||||
<span className="standalone-call-btn-video-icon" />
|
||||
</button>
|
||||
<div className={chevronClasses}
|
||||
onClick={this.toggleDropdownMenu}>
|
||||
</div>
|
||||
</div>
|
||||
<ul className={dropdownMenuClasses}>
|
||||
<li>
|
||||
<button className="start-audio-only-call"
|
||||
onClick={this.props.startCall("audio")}
|
||||
disabled={this.props.disabled}>
|
||||
{mozL10n.get("initiate_audio_call_button2")}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Initiate conversation view.
|
||||
*/
|
||||
var InitiateConversationView = React.createClass({
|
||||
mixins: [Backbone.Events],
|
||||
|
||||
propTypes: {
|
||||
conversation: React.PropTypes.oneOfType([
|
||||
React.PropTypes.instanceOf(sharedModels.ConversationModel),
|
||||
React.PropTypes.instanceOf(FxOSConversationModel)
|
||||
]).isRequired,
|
||||
// XXX Check more tightly here when we start injecting window.loop.*
|
||||
notifications: React.PropTypes.object.isRequired,
|
||||
client: React.PropTypes.object.isRequired,
|
||||
title: React.PropTypes.string.isRequired,
|
||||
callButtonLabel: React.PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
urlCreationDateString: '',
|
||||
disableCallButton: false,
|
||||
showCallOptionsMenu: this.props.showCallOptionsMenu
|
||||
disableCallButton: false
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
// Listen for events & hide dropdown menu if user clicks away
|
||||
window.addEventListener("click", this.clickHandler);
|
||||
this.props.model.listenTo(this.props.model, "session:error",
|
||||
this._onSessionError);
|
||||
this.props.model.listenTo(this.props.model, "fxos:app-needed",
|
||||
this._onFxOSAppNeeded);
|
||||
this.props.client.requestCallUrlInfo(this.props.model.get("loopToken"),
|
||||
this._setConversationTimestamp);
|
||||
this.listenTo(this.props.conversation,
|
||||
"session:error", this._onSessionError);
|
||||
this.listenTo(this.props.conversation,
|
||||
"fxos:app-needed", this._onFxOSAppNeeded);
|
||||
this.props.client.requestCallUrlInfo(
|
||||
this.props.conversation.get("loopToken"),
|
||||
this._setConversationTimestamp);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.stopListening(this.props.conversation);
|
||||
localStorage.setItem("has-seen-tos", "true");
|
||||
},
|
||||
|
||||
_onSessionError: function(error, l10nProps) {
|
||||
|
@ -362,11 +416,9 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
|
||||
_onFxOSAppNeeded: function() {
|
||||
this.setState({
|
||||
marketplaceSrc: loop.config.marketplaceUrl
|
||||
});
|
||||
this.setState({
|
||||
onMarketplaceMessage: this.props.model.onMarketplaceMessage.bind(
|
||||
this.props.model
|
||||
marketplaceSrc: loop.config.marketplaceUrl,
|
||||
onMarketplaceMessage: this.props.conversation.onMarketplaceMessage.bind(
|
||||
this.props.conversation
|
||||
)
|
||||
});
|
||||
},
|
||||
|
@ -379,11 +431,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
*
|
||||
* @param {string} User call type choice "audio" or "audio-video"
|
||||
*/
|
||||
_initiateOutgoingCall: function(callType) {
|
||||
startCall: function(callType) {
|
||||
return function() {
|
||||
this.props.model.set("selectedCallType", callType);
|
||||
this.props.conversation.setupOutgoingCall(callType);
|
||||
this.setState({disableCallButton: true});
|
||||
this.props.model.setupOutgoingCall();
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
|
@ -398,47 +449,21 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener("click", this.clickHandler);
|
||||
localStorage.setItem("has-seen-tos", "true");
|
||||
},
|
||||
|
||||
clickHandler: function(e) {
|
||||
if (!e.target.classList.contains('btn-chevron') &&
|
||||
this.state.showCallOptionsMenu) {
|
||||
this._toggleCallOptionsMenu();
|
||||
}
|
||||
},
|
||||
|
||||
_toggleCallOptionsMenu: function() {
|
||||
var state = this.state.showCallOptionsMenu;
|
||||
this.setState({showCallOptionsMenu: !state});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var tos_link_name = mozL10n.get("terms_of_use_link_text");
|
||||
var privacy_notice_name = mozL10n.get("privacy_notice_link_text");
|
||||
var tosLinkName = mozL10n.get("terms_of_use_link_text");
|
||||
var privacyNoticeName = mozL10n.get("privacy_notice_link_text");
|
||||
|
||||
var tosHTML = mozL10n.get("legal_text_and_links", {
|
||||
"terms_of_use_url": "<a target=_blank href='/legal/terms/'>" +
|
||||
tos_link_name + "</a>",
|
||||
tosLinkName + "</a>",
|
||||
"privacy_notice_url": "<a target=_blank href='" +
|
||||
"https://www.mozilla.org/privacy/'>" + privacy_notice_name + "</a>"
|
||||
"https://www.mozilla.org/privacy/'>" + privacyNoticeName + "</a>"
|
||||
});
|
||||
|
||||
var dropdownMenuClasses = React.addons.classSet({
|
||||
"native-dropdown-large-parent": true,
|
||||
"standalone-dropdown-menu": true,
|
||||
"visually-hidden": !this.state.showCallOptionsMenu
|
||||
});
|
||||
var tosClasses = React.addons.classSet({
|
||||
"terms-service": true,
|
||||
hide: (localStorage.getItem("has-seen-tos") === "true")
|
||||
});
|
||||
var chevronClasses = React.addons.classSet({
|
||||
"btn-chevron": true,
|
||||
"disabled": this.state.disableCallButton
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
|
@ -448,49 +473,19 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
urlCreationDateString={this.state.urlCreationDateString} />
|
||||
|
||||
<p className="standalone-btn-label">
|
||||
{mozL10n.get("initiate_call_button_label2")}
|
||||
{this.props.title}
|
||||
</p>
|
||||
|
||||
<div id="messages"></div>
|
||||
|
||||
<div className="btn-group">
|
||||
<div className="flex-padding-1"></div>
|
||||
<div className="standalone-btn-chevron-menu-group">
|
||||
<div className="btn-group-chevron">
|
||||
<div className="btn-group">
|
||||
|
||||
<button className="btn btn-large btn-accept"
|
||||
onClick={this._initiateOutgoingCall("audio-video")}
|
||||
disabled={this.state.disableCallButton}
|
||||
title={mozL10n.get("initiate_audio_video_call_tooltip2")} >
|
||||
<span className="standalone-call-btn-text">
|
||||
{mozL10n.get("initiate_audio_video_call_button2")}
|
||||
</span>
|
||||
<span className="standalone-call-btn-video-icon"></span>
|
||||
</button>
|
||||
|
||||
<div className={chevronClasses}
|
||||
onClick={this._toggleCallOptionsMenu}>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ul className={dropdownMenuClasses}>
|
||||
<li>
|
||||
{/*
|
||||
Button required for disabled state.
|
||||
*/}
|
||||
<button className="start-audio-only-call"
|
||||
onClick={this._initiateOutgoingCall("audio")}
|
||||
disabled={this.state.disableCallButton} >
|
||||
{mozL10n.get("initiate_audio_call_button2")}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-padding-1"></div>
|
||||
<div className="flex-padding-1" />
|
||||
<InitiateCallButton
|
||||
caption={this.props.callButtonLabel}
|
||||
disabled={this.state.disableCallButton}
|
||||
startCall={this.startCall}
|
||||
/>
|
||||
<div className="flex-padding-1" />
|
||||
</div>
|
||||
|
||||
<p className={tosClasses}
|
||||
|
@ -538,6 +533,26 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
var StartConversationView = React.createClass({
|
||||
render: function() {
|
||||
return this.transferPropsTo(
|
||||
<InitiateConversationView
|
||||
title={mozL10n.get("initiate_call_button_label2")}
|
||||
callButtonLabel={mozL10n.get("initiate_audio_video_call_button2")} />
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var FailedConversationView = React.createClass({
|
||||
render: function() {
|
||||
return this.transferPropsTo(
|
||||
<InitiateConversationView
|
||||
title={mozL10n.get("call_failed_title")}
|
||||
callButtonLabel={mozL10n.get("retry_call_button")} />
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* This view manages the outgoing conversation views - from
|
||||
* call initiation through to the actual conversation and call end.
|
||||
|
@ -595,11 +610,19 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
*/
|
||||
render: function() {
|
||||
switch (this.state.callStatus) {
|
||||
case "failure":
|
||||
case "start": {
|
||||
return (
|
||||
<StartConversationView
|
||||
model={this.props.conversation}
|
||||
conversation={this.props.conversation}
|
||||
notifications={this.props.notifications}
|
||||
client={this.props.client}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case "failure": {
|
||||
return (
|
||||
<FailedConversationView
|
||||
conversation={this.props.conversation}
|
||||
notifications={this.props.notifications}
|
||||
client={this.props.client}
|
||||
/>
|
||||
|
@ -775,18 +798,17 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
/**
|
||||
* Handles call rejection.
|
||||
*
|
||||
* @param {String} reason The reason the call was terminated.
|
||||
* @param {String} reason The reason the call was terminated (reject, busy,
|
||||
* timeout, cancel, media-fail, user-unknown, closed)
|
||||
*/
|
||||
_handleCallTerminated: function(reason) {
|
||||
if (reason !== "cancel") {
|
||||
// XXX This should really display the call failed view - bug 1046959
|
||||
// will implement this.
|
||||
this.props.notifications.errorL10n("call_timeout_notification_text");
|
||||
if (reason === "cancel") {
|
||||
this.setState({callStatus: "start"});
|
||||
return;
|
||||
}
|
||||
// redirects the user to the call start view
|
||||
// XXX should switch callStatus to failed for specific reasons when we
|
||||
// get the call failed view; for now, switch back to start.
|
||||
this.setState({callStatus: "start"});
|
||||
// XXX later, we'll want to display more meaningfull messages (needs UX)
|
||||
this.props.notifications.errorL10n("call_timeout_notification_text");
|
||||
this.setState({callStatus: "failure"});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -893,6 +915,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
CallUrlExpiredView: CallUrlExpiredView,
|
||||
PendingConversationView: PendingConversationView,
|
||||
StartConversationView: StartConversationView,
|
||||
FailedConversationView: FailedConversationView,
|
||||
OutgoingConversationView: OutgoingConversationView,
|
||||
EndedConversationView: EndedConversationView,
|
||||
HomeView: HomeView,
|
||||
|
|
|
@ -5,6 +5,7 @@ call_timeout_notification_text=Your call did not go through.
|
|||
missing_conversation_info=Missing conversation information.
|
||||
network_disconnected=The network connection terminated abruptly.
|
||||
peer_ended_conversation2=The person you were calling has ended the conversation.
|
||||
call_failed_title=Call failed.
|
||||
connection_error_see_console_notification=Call failed; see console for details.
|
||||
generic_failure_title=Something went wrong.
|
||||
generic_failure_with_reason2=You can try again or email a link to be reached at later.
|
||||
|
|
|
@ -76,6 +76,19 @@ describe("loop.shared.models", function() {
|
|||
});
|
||||
|
||||
describe("#setupOutgoingCall", function() {
|
||||
it("should set the a custom selected call type", function() {
|
||||
conversation.setupOutgoingCall("audio");
|
||||
|
||||
expect(conversation.get("selectedCallType")).eql("audio");
|
||||
});
|
||||
|
||||
it("should respect the default selected call type when none is passed",
|
||||
function() {
|
||||
conversation.setupOutgoingCall();
|
||||
|
||||
expect(conversation.get("selectedCallType")).eql("audio-video");
|
||||
});
|
||||
|
||||
it("should trigger a `call:outgoing:setup` event", function(done) {
|
||||
conversation.once("call:outgoing:setup", function() {
|
||||
done();
|
||||
|
|
|
@ -196,14 +196,14 @@ describe("loop.webapp", function() {
|
|||
sandbox.stub(notifications, "errorL10n");
|
||||
});
|
||||
|
||||
it("should display the StartConversationView", function() {
|
||||
it("should display the FailedConversationView", function() {
|
||||
ocView._websocket.trigger("progress", {
|
||||
state: "terminated",
|
||||
reason: "reject"
|
||||
});
|
||||
|
||||
TestUtils.findRenderedComponentWithType(ocView,
|
||||
loop.webapp.StartConversationView);
|
||||
loop.webapp.FailedConversationView);
|
||||
});
|
||||
|
||||
it("should display an error message if the reason is not 'cancel'",
|
||||
|
@ -271,14 +271,14 @@ describe("loop.webapp", function() {
|
|||
});
|
||||
|
||||
describe("call:outgoing", function() {
|
||||
it("should set display the StartConversationView if session token is missing",
|
||||
it("should display FailedConversationView if session token is missing",
|
||||
function() {
|
||||
conversation.set("loopToken", "");
|
||||
|
||||
ocView.startCall();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(ocView,
|
||||
loop.webapp.StartConversationView);
|
||||
loop.webapp.FailedConversationView);
|
||||
});
|
||||
|
||||
it("should notify the user if session token is missing", function() {
|
||||
|
@ -400,11 +400,11 @@ describe("loop.webapp", function() {
|
|||
conversation.set("loopToken", "");
|
||||
});
|
||||
|
||||
it("should set display the StartConversationView", function() {
|
||||
it("should display the FailedConversationView", function() {
|
||||
conversation.setupOutgoingCall();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(ocView,
|
||||
loop.webapp.StartConversationView);
|
||||
loop.webapp.FailedConversationView);
|
||||
});
|
||||
|
||||
it("should display an error", function() {
|
||||
|
@ -416,13 +416,12 @@ describe("loop.webapp", function() {
|
|||
|
||||
describe("Has loop token", function() {
|
||||
beforeEach(function() {
|
||||
conversation.set("selectedCallType", "audio-video");
|
||||
sandbox.stub(conversation, "outgoing");
|
||||
});
|
||||
|
||||
it("should call requestCallInfo on the client",
|
||||
function() {
|
||||
conversation.setupOutgoingCall();
|
||||
conversation.setupOutgoingCall("audio-video");
|
||||
|
||||
sinon.assert.calledOnce(client.requestCallInfo);
|
||||
sinon.assert.calledWith(client.requestCallInfo, "fakeToken",
|
||||
|
@ -440,14 +439,14 @@ describe("loop.webapp", function() {
|
|||
loop.webapp.CallUrlExpiredView);
|
||||
});
|
||||
|
||||
it("should set display the StartConversationView on any other error",
|
||||
it("should set display the FailedConversationView on any other error",
|
||||
function() {
|
||||
client.requestCallInfo.callsArgWith(2, {errno: 104});
|
||||
|
||||
conversation.setupOutgoingCall();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(ocView,
|
||||
loop.webapp.StartConversationView);
|
||||
loop.webapp.FailedConversationView);
|
||||
});
|
||||
|
||||
it("should notify the user on any other error", function() {
|
||||
|
@ -585,8 +584,7 @@ describe("loop.webapp", function() {
|
|||
|
||||
describe("StartConversationView", function() {
|
||||
describe("#initiate", function() {
|
||||
var conversation, setupOutgoingCall, view, fakeSubmitEvent,
|
||||
requestCallUrlInfo;
|
||||
var conversation, view, fakeSubmitEvent, requestCallUrlInfo;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new sharedModels.ConversationModel({}, {
|
||||
|
@ -594,7 +592,6 @@ describe("loop.webapp", function() {
|
|||
});
|
||||
|
||||
fakeSubmitEvent = {preventDefault: sinon.spy()};
|
||||
setupOutgoingCall = sinon.stub(conversation, "setupOutgoingCall");
|
||||
|
||||
var standaloneClientStub = {
|
||||
requestCallUrlInfo: function(token, cb) {
|
||||
|
@ -605,7 +602,7 @@ describe("loop.webapp", function() {
|
|||
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.StartConversationView({
|
||||
model: conversation,
|
||||
conversation: conversation,
|
||||
notifications: notifications,
|
||||
client: standaloneClientStub
|
||||
})
|
||||
|
@ -614,20 +611,24 @@ describe("loop.webapp", function() {
|
|||
|
||||
it("should start the audio-video conversation establishment process",
|
||||
function() {
|
||||
var setupOutgoingCall = sinon.stub(conversation, "setupOutgoingCall");
|
||||
|
||||
var button = view.getDOMNode().querySelector(".btn-accept");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
|
||||
sinon.assert.calledOnce(setupOutgoingCall);
|
||||
sinon.assert.calledWithExactly(setupOutgoingCall);
|
||||
sinon.assert.calledWithExactly(setupOutgoingCall, "audio-video");
|
||||
});
|
||||
|
||||
it("should start the audio-only conversation establishment process",
|
||||
function() {
|
||||
var setupOutgoingCall = sinon.stub(conversation, "setupOutgoingCall");
|
||||
|
||||
var button = view.getDOMNode().querySelector(".start-audio-only-call");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
|
||||
sinon.assert.calledOnce(setupOutgoingCall);
|
||||
sinon.assert.calledWithExactly(setupOutgoingCall);
|
||||
sinon.assert.calledWithExactly(setupOutgoingCall, "audio");
|
||||
});
|
||||
|
||||
it("should disable audio-video button once session is initiated",
|
||||
|
@ -650,35 +651,35 @@ describe("loop.webapp", function() {
|
|||
expect(button.disabled).to.eql(true);
|
||||
});
|
||||
|
||||
it("should set selectedCallType to audio", function() {
|
||||
conversation.set("loopToken", "fake");
|
||||
it("should set selectedCallType to audio", function() {
|
||||
conversation.set("loopToken", "fake");
|
||||
|
||||
var button = view.getDOMNode().querySelector(".start-audio-only-call");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
var button = view.getDOMNode().querySelector(".start-audio-only-call");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
|
||||
expect(conversation.get("selectedCallType")).to.eql("audio");
|
||||
});
|
||||
expect(conversation.get("selectedCallType")).to.eql("audio");
|
||||
});
|
||||
|
||||
it("should set selectedCallType to audio-video", function() {
|
||||
conversation.set("loopToken", "fake");
|
||||
it("should set selectedCallType to audio-video", function() {
|
||||
conversation.set("loopToken", "fake");
|
||||
|
||||
var button = view.getDOMNode().querySelector(".standalone-call-btn-video-icon");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
var button = view.getDOMNode().querySelector(".standalone-call-btn-video-icon");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
|
||||
expect(conversation.get("selectedCallType")).to.eql("audio-video");
|
||||
});
|
||||
|
||||
it("should set state.urlCreationDateString to a locale date string",
|
||||
function() {
|
||||
// wrap in a jquery object because text is broken up
|
||||
// into several span elements
|
||||
var date = new Date(0);
|
||||
var options = {year: "numeric", month: "long", day: "numeric"};
|
||||
var timestamp = date.toLocaleDateString(navigator.language, options);
|
||||
|
||||
expect(view.state.urlCreationDateString).to.eql(timestamp);
|
||||
expect(conversation.get("selectedCallType")).to.eql("audio-video");
|
||||
});
|
||||
|
||||
// XXX this test breaks while the feature actually works; find a way to
|
||||
// test this properly.
|
||||
it.skip("should set state.urlCreationDateString to a locale date string",
|
||||
function() {
|
||||
var date = new Date();
|
||||
var options = {year: "numeric", month: "long", day: "numeric"};
|
||||
var timestamp = date.toLocaleDateString(navigator.language, options);
|
||||
var dateElem = view.getDOMNode().querySelector(".call-url-date");
|
||||
|
||||
expect(dateElem.textContent).to.eql(timestamp);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Events", function() {
|
||||
|
@ -697,7 +698,7 @@ describe("loop.webapp", function() {
|
|||
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.StartConversationView({
|
||||
model: conversation,
|
||||
conversation: conversation,
|
||||
notifications: notifications,
|
||||
client: {requestCallUrlInfo: requestCallUrlInfo}
|
||||
})
|
||||
|
@ -782,7 +783,7 @@ describe("loop.webapp", function() {
|
|||
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.StartConversationView({
|
||||
model: conversation,
|
||||
conversation: conversation,
|
||||
notifications: notifications,
|
||||
client: {requestCallUrlInfo: requestCallUrlInfo}
|
||||
})
|
||||
|
@ -798,7 +799,7 @@ describe("loop.webapp", function() {
|
|||
localStorage.setItem("has-seen-tos", "true");
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.StartConversationView({
|
||||
model: conversation,
|
||||
conversation: conversation,
|
||||
notifications: notifications,
|
||||
client: {requestCallUrlInfo: requestCallUrlInfo}
|
||||
})
|
||||
|
@ -888,7 +889,7 @@ describe("loop.webapp", function() {
|
|||
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.StartConversationView({
|
||||
model: conversation,
|
||||
conversation: conversation,
|
||||
notifications: notifications,
|
||||
client: standaloneClientStub
|
||||
})
|
||||
|
@ -1003,7 +1004,7 @@ describe("loop.webapp", function() {
|
|||
before(function() {
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.StartConversationView({
|
||||
model: model,
|
||||
conversation: model,
|
||||
notifications: notifications,
|
||||
client: {requestCallUrlInfo: sandbox.stub()}
|
||||
})
|
||||
|
|
|
@ -38,7 +38,7 @@ function generateSessionTypeVerificationStub(desiredSessionType) {
|
|||
return hawkRequestStub;
|
||||
}
|
||||
|
||||
const origHawkRequest = MozLoopService.oldHawkRequest;
|
||||
const origHawkRequest = MozLoopService.hawkRequest;
|
||||
do_register_cleanup(function() {
|
||||
MozLoopService.hawkRequest = origHawkRequest;
|
||||
});
|
||||
|
|
|
@ -32,12 +32,13 @@
|
|||
<script src="../content/shared/libs/lodash-2.4.1.js"></script>
|
||||
<script src="../content/shared/libs/backbone-1.1.2.js"></script>
|
||||
<script src="../content/shared/js/feedbackApiClient.js"></script>
|
||||
<script src="../content/shared/js/conversationStore.js"></script>
|
||||
<script src="../content/shared/js/actions.js"></script>
|
||||
<script src="../content/shared/js/utils.js"></script>
|
||||
<script src="../content/shared/js/models.js"></script>
|
||||
<script src="../content/shared/js/mixins.js"></script>
|
||||
<script src="../content/shared/js/views.js"></script>
|
||||
<script src="../content/shared/js/websocket.js"></script>
|
||||
<script src="../content/shared/js/conversationStore.js"></script>
|
||||
<script src="../content/js/conversationViews.js"></script>
|
||||
<script src="../content/js/client.js"></script>
|
||||
<script src="../standalone/content/js/webapp.js"></script>
|
||||
|
|
|
@ -19,12 +19,13 @@
|
|||
|
||||
// 2. Standalone webapp
|
||||
var HomeView = loop.webapp.HomeView;
|
||||
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
|
||||
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
|
||||
var CallUrlExpiredView = loop.webapp.CallUrlExpiredView;
|
||||
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
|
||||
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
|
||||
var CallUrlExpiredView = loop.webapp.CallUrlExpiredView;
|
||||
var PendingConversationView = loop.webapp.PendingConversationView;
|
||||
var StartConversationView = loop.webapp.StartConversationView;
|
||||
var EndedConversationView = loop.webapp.EndedConversationView;
|
||||
var StartConversationView = loop.webapp.StartConversationView;
|
||||
var FailedConversationView = loop.webapp.FailedConversationView;
|
||||
var EndedConversationView = loop.webapp.EndedConversationView;
|
||||
|
||||
// 3. Shared components
|
||||
var ConversationToolbar = loop.shared.views.ConversationToolbar;
|
||||
|
@ -175,8 +176,7 @@
|
|||
Example({summary: "Default", dashed: "true", style: {width: "260px", height: "254px"}},
|
||||
React.DOM.div({className: "fx-embedded"},
|
||||
IncomingCallView({model: mockConversationModel,
|
||||
showDeclineMenu: true,
|
||||
video: true})
|
||||
showMenu: true})
|
||||
)
|
||||
)
|
||||
),
|
||||
|
@ -252,10 +252,19 @@
|
|||
Section({name: "StartConversationView"},
|
||||
Example({summary: "Start conversation view", dashed: "true"},
|
||||
React.DOM.div({className: "standalone"},
|
||||
StartConversationView({model: mockConversationModel,
|
||||
StartConversationView({conversation: mockConversationModel,
|
||||
client: mockClient,
|
||||
notifications: notifications,
|
||||
showCallOptionsMenu: true})
|
||||
notifications: notifications})
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
Section({name: "FailedConversationView"},
|
||||
Example({summary: "Failed conversation view", dashed: "true"},
|
||||
React.DOM.div({className: "standalone"},
|
||||
FailedConversationView({conversation: mockConversationModel,
|
||||
client: mockClient,
|
||||
notifications: notifications})
|
||||
)
|
||||
)
|
||||
),
|
||||
|
|
|
@ -19,12 +19,13 @@
|
|||
|
||||
// 2. Standalone webapp
|
||||
var HomeView = loop.webapp.HomeView;
|
||||
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
|
||||
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
|
||||
var CallUrlExpiredView = loop.webapp.CallUrlExpiredView;
|
||||
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
|
||||
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
|
||||
var CallUrlExpiredView = loop.webapp.CallUrlExpiredView;
|
||||
var PendingConversationView = loop.webapp.PendingConversationView;
|
||||
var StartConversationView = loop.webapp.StartConversationView;
|
||||
var EndedConversationView = loop.webapp.EndedConversationView;
|
||||
var StartConversationView = loop.webapp.StartConversationView;
|
||||
var FailedConversationView = loop.webapp.FailedConversationView;
|
||||
var EndedConversationView = loop.webapp.EndedConversationView;
|
||||
|
||||
// 3. Shared components
|
||||
var ConversationToolbar = loop.shared.views.ConversationToolbar;
|
||||
|
@ -175,8 +176,7 @@
|
|||
<Example summary="Default" dashed="true" style={{width: "260px", height: "254px"}}>
|
||||
<div className="fx-embedded" >
|
||||
<IncomingCallView model={mockConversationModel}
|
||||
showDeclineMenu={true}
|
||||
video={true} />
|
||||
showMenu={true} />
|
||||
</div>
|
||||
</Example>
|
||||
</Section>
|
||||
|
@ -252,10 +252,19 @@
|
|||
<Section name="StartConversationView">
|
||||
<Example summary="Start conversation view" dashed="true">
|
||||
<div className="standalone">
|
||||
<StartConversationView model={mockConversationModel}
|
||||
<StartConversationView conversation={mockConversationModel}
|
||||
client={mockClient}
|
||||
notifications={notifications}
|
||||
showCallOptionsMenu={true} />
|
||||
notifications={notifications} />
|
||||
</div>
|
||||
</Example>
|
||||
</Section>
|
||||
|
||||
<Section name="FailedConversationView">
|
||||
<Example summary="Failed conversation view" dashed="true">
|
||||
<div className="standalone">
|
||||
<FailedConversationView conversation={mockConversationModel}
|
||||
client={mockClient}
|
||||
notifications={notifications} />
|
||||
</div>
|
||||
</Example>
|
||||
</Section>
|
||||
|
|
|
@ -158,8 +158,19 @@ function updateIndicators() {
|
|||
showScreenSharingIndicator: ""
|
||||
};
|
||||
|
||||
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageSender);
|
||||
cpmm.sendAsyncMessage("webrtc:UpdatingIndicators");
|
||||
|
||||
// If several iframes in the same page use media streams, it's possible to
|
||||
// have the same top level window several times. We use a Set to avoid
|
||||
// sending duplicate notifications.
|
||||
let contentWindows = new Set();
|
||||
for (let i = 0; i < count; ++i) {
|
||||
let contentWindow = contentWindowSupportsArray.GetElementAt(i);
|
||||
contentWindows.add(contentWindowSupportsArray.GetElementAt(i).top);
|
||||
}
|
||||
|
||||
for (let contentWindow of contentWindows) {
|
||||
let camera = {}, microphone = {}, screen = {}, window = {}, app = {};
|
||||
MediaManagerService.mediaCaptureWindowState(contentWindow, camera,
|
||||
microphone, screen, window, app);
|
||||
|
@ -189,8 +200,6 @@ function updateIndicators() {
|
|||
mm.sendAsyncMessage("webrtc:UpdateBrowserIndicators", tabState);
|
||||
}
|
||||
|
||||
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageSender);
|
||||
cpmm.sendAsyncMessage("webrtc:UpdateGlobalIndicators", state);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,16 +16,13 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||
"resource://gre/modules/PluralForm.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
|
||||
"@mozilla.org/mediaManagerService;1",
|
||||
"nsIMediaManagerService");
|
||||
|
||||
this.webrtcUI = {
|
||||
init: function () {
|
||||
Services.obs.addObserver(maybeAddMenuIndicator, "browser-delayed-startup-finished", false);
|
||||
|
||||
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageBroadcaster);
|
||||
ppmm.addMessageListener("webrtc:UpdatingIndicators", this);
|
||||
ppmm.addMessageListener("webrtc:UpdateGlobalIndicators", this);
|
||||
|
||||
let mm = Cc["@mozilla.org/globalmessagemanager;1"]
|
||||
|
@ -39,6 +36,7 @@ this.webrtcUI = {
|
|||
|
||||
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageBroadcaster);
|
||||
ppmm.removeMessageListener("webrtc:UpdatingIndicators", this);
|
||||
ppmm.removeMessageListener("webrtc:UpdateGlobalIndicators", this);
|
||||
|
||||
let mm = Cc["@mozilla.org/globalmessagemanager;1"]
|
||||
|
@ -52,42 +50,24 @@ this.webrtcUI = {
|
|||
showMicrophoneIndicator: false,
|
||||
showScreenSharingIndicator: "", // either "Application", "Screen" or "Window"
|
||||
|
||||
_streams: [],
|
||||
// The boolean parameters indicate which streams should be included in the result.
|
||||
getActiveStreams: function(aCamera, aMicrophone, aScreen) {
|
||||
let contentWindowSupportsArray = MediaManagerService.activeMediaCaptureWindows;
|
||||
let count = contentWindowSupportsArray.Count();
|
||||
let activeStreams = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
let contentWindow = contentWindowSupportsArray.GetElementAt(i);
|
||||
|
||||
let info = {
|
||||
Camera: {},
|
||||
Microphone: {},
|
||||
Window: {},
|
||||
Screen: {},
|
||||
Application: {}
|
||||
};
|
||||
MediaManagerService.mediaCaptureWindowState(contentWindow, info.Camera,
|
||||
info.Microphone, info.Screen,
|
||||
info.Window, info.Application);
|
||||
if (!(aCamera && info.Camera.value ||
|
||||
aMicrophone && info.Microphone.value ||
|
||||
aScreen && (info.Screen.value || info.Window.value ||
|
||||
info.Application.value)))
|
||||
continue;
|
||||
|
||||
let browser = getBrowserForWindow(contentWindow);
|
||||
return webrtcUI._streams.filter(aStream => {
|
||||
let state = aStream.state;
|
||||
return aCamera && state.camera ||
|
||||
aMicrophone && state.microphone ||
|
||||
aScreen && state.screen;
|
||||
}).map(aStream => {
|
||||
let state = aStream.state;
|
||||
let types = {camera: state.camera, microphone: state.microphone,
|
||||
screen: state.screen};
|
||||
let browser = aStream.browser;
|
||||
let browserWindow = browser.ownerDocument.defaultView;
|
||||
let tab = browserWindow.gBrowser &&
|
||||
browserWindow.gBrowser._getTabForContentWindow(contentWindow.top);
|
||||
activeStreams.push({
|
||||
uri: contentWindow.location.href,
|
||||
tab: tab,
|
||||
browser: browser,
|
||||
types: info
|
||||
});
|
||||
}
|
||||
return activeStreams;
|
||||
browserWindow.gBrowser._getTabForBrowser(browser);
|
||||
return {uri: state.documentURI, tab: tab, browser: browser, types: types};
|
||||
});
|
||||
},
|
||||
|
||||
showSharingDoorhanger: function(aActiveStream, aType) {
|
||||
|
@ -138,10 +118,14 @@ this.webrtcUI = {
|
|||
case "webrtc:Request":
|
||||
prompt(aMessage.target, aMessage.data);
|
||||
break;
|
||||
case "webrtc:UpdatingIndicators":
|
||||
webrtcUI._streams = [];
|
||||
break;
|
||||
case "webrtc:UpdateGlobalIndicators":
|
||||
updateIndicators(aMessage.data)
|
||||
break;
|
||||
case "webrtc:UpdateBrowserIndicators":
|
||||
webrtcUI._streams.push({browser: aMessage.target, state: aMessage.data});
|
||||
updateBrowserSpecificIndicator(aMessage.target, aMessage.data);
|
||||
break;
|
||||
}
|
||||
|
@ -562,16 +546,12 @@ function onTabSharingMenuPopupShowing(e) {
|
|||
for (let streamInfo of streams) {
|
||||
let stringName = "getUserMedia.sharingMenu";
|
||||
let types = streamInfo.types;
|
||||
if (types.Camera.value)
|
||||
if (types.camera)
|
||||
stringName += "Camera";
|
||||
if (types.Microphone.value)
|
||||
if (types.microphone)
|
||||
stringName += "Microphone";
|
||||
if (types.Screen.value)
|
||||
stringName += "Screen";
|
||||
else if (types.Application.value)
|
||||
stringName += "Application";
|
||||
else if (types.Window.value)
|
||||
stringName += "Window";
|
||||
if (types.screen)
|
||||
stringName += types.screen;
|
||||
|
||||
let doc = e.target.ownerDocument;
|
||||
let bundle = doc.defaultView.gNavigatorBundle;
|
||||
|
|
|
@ -14,7 +14,9 @@ namespace dom {
|
|||
struct ScreenDetails {
|
||||
uint32_t id;
|
||||
nsIntRect rect;
|
||||
nsIntRect rectDisplayPix;
|
||||
nsIntRect availRect;
|
||||
nsIntRect availRectDisplayPix;
|
||||
int32_t pixelDepth;
|
||||
int32_t colorDepth;
|
||||
double contentsScaleFactor;
|
||||
|
|
|
@ -161,12 +161,26 @@ ScreenManagerParent::ExtractScreenDetails(nsIScreen* aScreen, ScreenDetails &aDe
|
|||
NS_ENSURE_SUCCESS(rv, false);
|
||||
aDetails.rect() = rect;
|
||||
|
||||
nsIntRect rectDisplayPix;
|
||||
rv = aScreen->GetRectDisplayPix(&rectDisplayPix.x, &rectDisplayPix.y,
|
||||
&rectDisplayPix.width, &rectDisplayPix.height);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
aDetails.rectDisplayPix() = rectDisplayPix;
|
||||
|
||||
nsIntRect availRect;
|
||||
rv = aScreen->GetAvailRect(&availRect.x, &availRect.y, &availRect.width,
|
||||
&availRect.height);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
aDetails.availRect() = availRect;
|
||||
|
||||
nsIntRect availRectDisplayPix;
|
||||
rv = aScreen->GetAvailRectDisplayPix(&availRectDisplayPix.x,
|
||||
&availRectDisplayPix.y,
|
||||
&availRectDisplayPix.width,
|
||||
&availRectDisplayPix.height);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
aDetails.availRectDisplayPix() = availRectDisplayPix;
|
||||
|
||||
int32_t pixelDepth = 0;
|
||||
rv = aScreen->GetPixelDepth(&pixelDepth);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
|
|
@ -662,16 +662,6 @@ public class BrowserApp extends GeckoApp
|
|||
// Set the maximum bits-per-pixel the favicon system cares about.
|
||||
IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth());
|
||||
|
||||
Class<?> mediaManagerClass = getMediaPlayerManager();
|
||||
if (mediaManagerClass != null) {
|
||||
try {
|
||||
Method init = mediaManagerClass.getMethod("init", Context.class);
|
||||
init.invoke(null, this);
|
||||
} catch(Exception ex) {
|
||||
Log.e(LOGTAG, "Error initializing media manager", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (getProfile().inGuestMode()) {
|
||||
GuestSession.showNotification(this);
|
||||
} else {
|
||||
|
@ -945,7 +935,7 @@ public class BrowserApp extends GeckoApp
|
|||
if (itemId == R.id.pasteandgo) {
|
||||
String text = Clipboard.getText();
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
Tabs.getInstance().loadUrl(text);
|
||||
loadUrlOrKeywordSearch(text);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "pasteandgo");
|
||||
}
|
||||
|
@ -1547,6 +1537,19 @@ public class BrowserApp extends GeckoApp
|
|||
}
|
||||
});
|
||||
|
||||
if (AppConstants.MOZ_MEDIA_PLAYER) {
|
||||
// If casting is disabled, these classes aren't built. We use reflection to initialize them.
|
||||
Class<?> mediaManagerClass = getMediaPlayerManager();
|
||||
if (mediaManagerClass != null) {
|
||||
try {
|
||||
Method init = mediaManagerClass.getMethod("init", Context.class);
|
||||
init.invoke(null, this);
|
||||
} catch(Exception ex) {
|
||||
Log.e(LOGTAG, "Error initializing media manager", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED) {
|
||||
// Start (this acts as ping if started already) the stumbler lib; if the stumbler has queued data it will upload it.
|
||||
// Stumbler operates on its own thread, and startup impact is further minimized by delaying work (such as upload) a few seconds.
|
||||
|
@ -1559,6 +1562,7 @@ public class BrowserApp extends GeckoApp
|
|||
}
|
||||
}, oneSecondInMillis);
|
||||
}
|
||||
|
||||
super.handleMessage(event, message);
|
||||
} else if (event.equals("Gecko:Ready")) {
|
||||
// Handle this message in GeckoApp, but also enable the Settings
|
||||
|
@ -1934,7 +1938,10 @@ public class BrowserApp extends GeckoApp
|
|||
//
|
||||
// Expected to be fixed by bug 915825.
|
||||
hideHomePager(url);
|
||||
loadUrlOrKeywordSearch(url);
|
||||
}
|
||||
|
||||
private void loadUrlOrKeywordSearch(final String url) {
|
||||
// Don't do anything if the user entered an empty URL.
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
return;
|
||||
|
|
|
@ -191,6 +191,7 @@ public abstract class GeckoApp
|
|||
private Telemetry.Timer mGeckoReadyStartupTimer;
|
||||
|
||||
private String mPrivateBrowsingSession;
|
||||
private volatile boolean mIsPaused = true;
|
||||
|
||||
private volatile HealthRecorder mHealthRecorder;
|
||||
private volatile Locale mLastLocale;
|
||||
|
@ -246,7 +247,13 @@ public abstract class GeckoApp
|
|||
}
|
||||
|
||||
public void addAppStateListener(GeckoAppShell.AppStateListener listener) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
mAppStateListeners.add(listener);
|
||||
|
||||
// If we're already resumed, make sure the listener gets a notification.
|
||||
if (!mIsPaused) {
|
||||
listener.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAppStateListener(GeckoAppShell.AppStateListener listener) {
|
||||
|
@ -1885,6 +1892,8 @@ public abstract class GeckoApp
|
|||
listener.onResume();
|
||||
}
|
||||
}
|
||||
// Setting this state will force any listeners added after this to have their onResume() method called
|
||||
mIsPaused = false;
|
||||
|
||||
// We use two times: a pseudo-unique wall-clock time to identify the
|
||||
// current session across power cycles, and the elapsed realtime to
|
||||
|
@ -1961,6 +1970,9 @@ public abstract class GeckoApp
|
|||
}
|
||||
});
|
||||
|
||||
// Setting this state will keep any listeners registered after this from having their onResume
|
||||
// method called.
|
||||
mIsPaused = true;
|
||||
if (mAppStateListeners != null) {
|
||||
for(GeckoAppShell.AppStateListener listener: mAppStateListeners) {
|
||||
listener.onPause();
|
||||
|
|
|
@ -127,7 +127,7 @@ class MediaPlayerManager implements NativeEventListener,
|
|||
}
|
||||
} catch(Exception ex) {
|
||||
// This may happen if the device isn't a real Chromecast,
|
||||
// for example Firefly casting devices.
|
||||
// for example Matchstick casting devices.
|
||||
Log.e(LOGTAG, "Couldn't create JSON for display", ex);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.widget.Button;
|
||||
|
||||
public class StartPane extends Activity {
|
||||
|
@ -35,6 +39,10 @@ public class StartPane extends Activity {
|
|||
showBrowser();
|
||||
}
|
||||
});
|
||||
|
||||
if (!HardwareUtils.isTablet() && !HardwareUtils.isTelevision()) {
|
||||
addDismissHandler();
|
||||
}
|
||||
}
|
||||
|
||||
private void showBrowser() {
|
||||
|
@ -48,4 +56,22 @@ public class StartPane extends Activity {
|
|||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
// Add handler for dismissing the StartPane on a single click.
|
||||
private void addDismissHandler() {
|
||||
final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
StartPane.this.finish();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.onboard_content).setOnTouchListener(new OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
return gestureDetector.onTouchEvent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -469,7 +469,6 @@ gbjar.sources += [
|
|||
'widget/IconTabWidget.java',
|
||||
'widget/SquaredImageView.java',
|
||||
'widget/SwipeDismissListViewTouchListener.java',
|
||||
'widget/TabRow.java',
|
||||
'widget/TabThumbnailWrapper.java',
|
||||
'widget/ThumbnailView.java',
|
||||
'widget/TwoWayView.java',
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
- 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/. -->
|
||||
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/favicon"
|
||||
|
@ -13,7 +14,7 @@
|
|||
android:scaleType="centerInside"
|
||||
android:duplicateParentState="true"/>
|
||||
|
||||
<org.mozilla.gecko.widget.ThemedTextView
|
||||
<org.mozilla.gecko.widget.FadedTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -24,6 +25,7 @@
|
|||
android:ellipsize="end"
|
||||
android:textColor="@color/new_tablet_tab_strip_item_title"
|
||||
android:maxLines="1"
|
||||
gecko:fadeWidth="30dip"
|
||||
android:duplicateParentState="true"/>
|
||||
|
||||
<org.mozilla.gecko.widget.ThemedImageButton
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
android:background="@color/onboard_start"
|
||||
android:windowIsFloating="true">
|
||||
|
||||
<ScrollView android:layout_width="match_parent"
|
||||
<ScrollView android:id="@+id/onboard_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:fillViewport="true" >
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
- 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/. -->
|
||||
|
||||
<org.mozilla.gecko.widget.TabRow xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/TabsItem"
|
||||
android:focusable="true"
|
||||
android:id="@+id/info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="6dip"
|
||||
android:paddingBottom="6dip"
|
||||
android:paddingLeft="1dip"
|
||||
android:paddingRight="1dip"
|
||||
android:gravity="center">
|
||||
<org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/TabsItem"
|
||||
android:focusable="true"
|
||||
android:id="@+id/info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="6dip"
|
||||
android:paddingBottom="6dip"
|
||||
android:paddingLeft="1dip"
|
||||
android:paddingRight="1dip"
|
||||
android:gravity="center">
|
||||
|
||||
<RelativeLayout android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -58,4 +58,4 @@
|
|||
|
||||
</RelativeLayout>
|
||||
|
||||
</org.mozilla.gecko.widget.TabRow>
|
||||
</org.mozilla.gecko.tabs.TabsLayoutItemView>
|
||||
|
|
|
@ -3,16 +3,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/. -->
|
||||
|
||||
<org.mozilla.gecko.widget.TabRow xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/TabsItem"
|
||||
android:focusable="true"
|
||||
android:id="@+id/info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="12dip"
|
||||
android:paddingTop="6dip"
|
||||
android:paddingBottom="6dip"
|
||||
android:background="@drawable/tab_row">
|
||||
<org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/TabsItem"
|
||||
android:focusable="true"
|
||||
android:id="@+id/info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="12dip"
|
||||
android:paddingTop="6dip"
|
||||
android:paddingBottom="6dip"
|
||||
android:background="@drawable/tab_row">
|
||||
|
||||
<org.mozilla.gecko.widget.TabThumbnailWrapper
|
||||
android:id="@+id/wrapper"
|
||||
|
@ -52,4 +52,4 @@
|
|||
android:contentDescription="@string/close_tab"
|
||||
android:src="@drawable/tab_close"/>
|
||||
|
||||
</org.mozilla.gecko.widget.TabRow>
|
||||
</org.mozilla.gecko.tabs.TabsLayoutItemView>
|
||||
|
|
|
@ -54,9 +54,8 @@ class TabsGridLayout extends GridView
|
|||
setRecyclerListener(new RecyclerListener() {
|
||||
@Override
|
||||
public void onMovedToScrapHeap(View view) {
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) view.getTag();
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) view;
|
||||
item.thumbnail.setImageDrawable(null);
|
||||
item.close.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -81,34 +80,29 @@ class TabsGridLayout extends GridView
|
|||
mSelectClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
TabsLayoutItemView tab = (TabsLayoutItemView) v.getTag();
|
||||
TabsLayoutItemView tab = (TabsLayoutItemView) v;
|
||||
Tabs.getInstance().selectTab(tab.id);
|
||||
TabsGridLayout.this.autoHidePanel();
|
||||
autoHidePanel();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(int position, ViewGroup parent) {
|
||||
View view = super.newView(position, parent);
|
||||
|
||||
// This is nasty and once we change TabsLayoutItemView to an actual view
|
||||
// we can get rid of it.
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) view.getTag();
|
||||
item.close.setOnClickListener(mCloseClickListener);
|
||||
|
||||
return view;
|
||||
View newView(int position, ViewGroup parent) {
|
||||
final TabsLayoutItemView item = (TabsLayoutItemView) super.newView(position, parent);
|
||||
item.setOnClickListener(mSelectClickListener);
|
||||
item.setCloseOnClickListener(mCloseClickListener);
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Tab tab) {
|
||||
super.bindView(view, tab);
|
||||
|
||||
view.setOnClickListener(mSelectClickListener);
|
||||
((TabsLayoutItemView) view).close.setVisibility(View.VISIBLE);
|
||||
|
||||
// If we're recycling this view, there's a chance it was transformed during
|
||||
// the close animation. Remove any of those properties.
|
||||
TabsGridLayout.this.resetTransforms(view);
|
||||
resetTransforms(view);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,8 +165,7 @@ class TabsGridLayout extends GridView
|
|||
if (view == null)
|
||||
return;
|
||||
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) view.getTag();
|
||||
item.assignValues(tab);
|
||||
((TabsLayoutItemView) view).assignValues(tab);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,8 @@
|
|||
|
||||
package org.mozilla.gecko.tabs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tab;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -16,8 +14,12 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
// Adapter to bind tabs into a list
|
||||
public class TabsLayoutAdapter extends BaseAdapter {
|
||||
public static final String LOGTAG = "Gecko" + TabsLayoutAdapter.class.getSimpleName();
|
||||
|
||||
private Context mContext;
|
||||
private ArrayList<Tab> mTabs;
|
||||
private LayoutInflater mInflater;
|
||||
|
@ -81,14 +83,10 @@ public class TabsLayoutAdapter extends BaseAdapter {
|
|||
}
|
||||
|
||||
View newView(int position, ViewGroup parent) {
|
||||
final View view = mInflater.inflate(R.layout.tabs_layout_item_view, parent, false);
|
||||
final TabsLayoutItemView item = new TabsLayoutItemView(view);
|
||||
view.setTag(item);
|
||||
return view;
|
||||
return mInflater.inflate(R.layout.tabs_layout_item_view, parent, false);
|
||||
}
|
||||
|
||||
void bindView(View view, Tab tab) {
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) view.getTag();
|
||||
item.assignValues(tab);
|
||||
((TabsLayoutItemView) view).assignValues(tab);
|
||||
}
|
||||
}
|
|
@ -8,32 +8,90 @@ import org.mozilla.gecko.R;
|
|||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.widget.TabThumbnailWrapper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class TabsLayoutItemView {
|
||||
public class TabsLayoutItemView extends LinearLayout
|
||||
implements Checkable {
|
||||
private static final String LOGTAG = "Gecko" + TabsLayoutItemView.class.getSimpleName();
|
||||
private static final int[] STATE_CHECKED = { android.R.attr.state_checked };
|
||||
private boolean mChecked;
|
||||
|
||||
// yeah, it's a bit nasty having two different styles for the class members,
|
||||
// this'll be fixed once bug 1058574 is addressed
|
||||
int id;
|
||||
TextView title;
|
||||
ImageView thumbnail;
|
||||
ImageButton close;
|
||||
ViewGroup info;
|
||||
TabThumbnailWrapper thumbnailWrapper;
|
||||
|
||||
public TabsLayoutItemView(View view) {
|
||||
info = (ViewGroup) view;
|
||||
title = (TextView) view.findViewById(R.id.title);
|
||||
thumbnail = (ImageView) view.findViewById(R.id.thumbnail);
|
||||
close = (ImageButton) view.findViewById(R.id.close);
|
||||
thumbnailWrapper = (TabThumbnailWrapper) view.findViewById(R.id.wrapper);
|
||||
public TabsLayoutItemView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
protected void assignValues(Tab tab) {
|
||||
if (tab == null)
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
|
||||
if (mChecked) {
|
||||
mergeDrawableStates(drawableState, STATE_CHECKED);
|
||||
}
|
||||
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
return mChecked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean checked) {
|
||||
if (mChecked == checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
mChecked = checked;
|
||||
refreshDrawableState();
|
||||
|
||||
int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
if (child instanceof Checkable) {
|
||||
((Checkable) child).setChecked(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggle() {
|
||||
mChecked = !mChecked;
|
||||
}
|
||||
|
||||
public void setCloseOnClickListener(OnClickListener mOnClickListener) {
|
||||
close.setOnClickListener(mOnClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
title = (TextView) findViewById(R.id.title);
|
||||
thumbnail = (ImageView) findViewById(R.id.thumbnail);
|
||||
close = (ImageButton) findViewById(R.id.close);
|
||||
thumbnailWrapper = (TabThumbnailWrapper) findViewById(R.id.wrapper);
|
||||
}
|
||||
|
||||
protected void assignValues(Tab tab) {
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
id = tab.getId();
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ package org.mozilla.gecko.tabs;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.mozilla.gecko.AboutPages;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator.Property;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
|
@ -16,7 +15,6 @@ import org.mozilla.gecko.GeckoAppShell;
|
|||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.tabs.TabsLayoutAdapter;
|
||||
import org.mozilla.gecko.tabs.TabsPanel.TabsLayout;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
@ -81,7 +79,7 @@ class TabsListLayout extends TwoWayView
|
|||
setRecyclerListener(new RecyclerListener() {
|
||||
@Override
|
||||
public void onMovedToScrapHeap(View view) {
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) view.getTag();
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) view;
|
||||
item.thumbnail.setImageDrawable(null);
|
||||
item.close.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
@ -89,30 +87,29 @@ class TabsListLayout extends TwoWayView
|
|||
}
|
||||
|
||||
private class TabsListLayoutAdapter extends TabsLayoutAdapter {
|
||||
private Button.OnClickListener mOnClickListener;
|
||||
private Button.OnClickListener mCloseOnClickListener;
|
||||
public TabsListLayoutAdapter (Context context) {
|
||||
super(context);
|
||||
|
||||
mOnClickListener = new Button.OnClickListener() {
|
||||
mCloseOnClickListener = new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
TabsLayoutItemView tab = (TabsLayoutItemView) v.getTag();
|
||||
final int pos = (isVertical() ? tab.info.getWidth() : 0 - tab.info.getHeight());
|
||||
animateClose(tab.info, pos);
|
||||
// The view here is the close button, which has a reference
|
||||
// to the parent TabsLayoutItemView in it's tag, hence the getTag() call
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) v.getTag();
|
||||
final int pos = (isVertical() ? item.getWidth() : 0 - item.getHeight());
|
||||
animateClose(item, pos);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(int position, ViewGroup parent) {
|
||||
View view = super.newView(position, parent);
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) super.newView(position, parent);
|
||||
|
||||
// This is nasty and once we change TabsLayoutItemView to an actual view
|
||||
// we can get rid of it.
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) view.getTag();
|
||||
item.close.setOnClickListener(mOnClickListener);
|
||||
item.setCloseOnClickListener(mCloseOnClickListener);
|
||||
|
||||
return view;
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -121,7 +118,7 @@ class TabsListLayout extends TwoWayView
|
|||
|
||||
// If we're recycling this view, there's a chance it was transformed during
|
||||
// the close animation. Remove any of those properties.
|
||||
TabsListLayout.this.resetTransforms(view);
|
||||
resetTransforms(view);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -185,7 +182,7 @@ class TabsListLayout extends TwoWayView
|
|||
if (view == null)
|
||||
return;
|
||||
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) view.getTag();
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) view;
|
||||
item.assignValues(tab);
|
||||
break;
|
||||
}
|
||||
|
@ -363,8 +360,7 @@ class TabsListLayout extends TwoWayView
|
|||
else
|
||||
animator.attach(view, Property.WIDTH, 1);
|
||||
|
||||
TabsLayoutItemView tab = (TabsLayoutItemView)view.getTag();
|
||||
final int tabId = tab.id;
|
||||
final int tabId = ((TabsLayoutItemView) view).id;
|
||||
|
||||
// Caching this assumes that all rows are the same height
|
||||
if (mOriginalSize == 0) {
|
||||
|
@ -400,7 +396,7 @@ class TabsListLayout extends TwoWayView
|
|||
public void onPropertyAnimationStart() { }
|
||||
@Override
|
||||
public void onPropertyAnimationEnd() {
|
||||
TabsLayoutItemView tab = (TabsLayoutItemView) view.getTag();
|
||||
TabsLayoutItemView tab = (TabsLayoutItemView) view;
|
||||
tab.close.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
@ -498,8 +494,8 @@ class TabsListLayout extends TwoWayView
|
|||
mSwipeView.setPressed(false);
|
||||
|
||||
if (!mSwiping) {
|
||||
TabsLayoutItemView tab = (TabsLayoutItemView) mSwipeView.getTag();
|
||||
Tabs.getInstance().selectTab(tab.id);
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) mSwipeView;
|
||||
Tabs.getInstance().selectTab(item.id);
|
||||
autoHidePanel();
|
||||
|
||||
mVelocityTracker.recycle();
|
||||
|
@ -586,8 +582,7 @@ class TabsListLayout extends TwoWayView
|
|||
mSwiping = true;
|
||||
TabsListLayout.this.requestDisallowInterceptTouchEvent(true);
|
||||
|
||||
TabsLayoutItemView tab = (TabsLayoutItemView) mSwipeView.getTag();
|
||||
tab.close.setVisibility(View.INVISIBLE);
|
||||
((TabsLayoutItemView) mSwipeView).close.setVisibility(View.INVISIBLE);
|
||||
|
||||
// Stops listview from highlighting the touched item
|
||||
// in the list when swiping.
|
||||
|
|
|
@ -435,6 +435,7 @@ public class UpdateService extends IntentService {
|
|||
|
||||
private File downloadUpdatePackage(UpdateInfo info, boolean overwriteExisting) {
|
||||
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
path.mkdirs();
|
||||
String fileName = new File(info.url.getFile()).getName();
|
||||
File downloadFile = new File(path, fileName);
|
||||
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
public class TabRow extends LinearLayout
|
||||
implements Checkable {
|
||||
private static final String LOGTAG = "GeckoTabRow";
|
||||
private static final int[] STATE_CHECKED = { android.R.attr.state_checked };
|
||||
private boolean mChecked;
|
||||
|
||||
public TabRow(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
|
||||
if (mChecked)
|
||||
mergeDrawableStates(drawableState, STATE_CHECKED);
|
||||
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
return mChecked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean checked) {
|
||||
if (mChecked != checked) {
|
||||
mChecked = checked;
|
||||
refreshDrawableState();
|
||||
|
||||
int count = getChildCount();
|
||||
for (int i=0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
if (child instanceof Checkable)
|
||||
((Checkable) child).setChecked(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggle() {
|
||||
mChecked = !mChecked;
|
||||
}
|
||||
}
|
|
@ -21,16 +21,16 @@ var rokuDevice = {
|
|||
extensions: ["mp4"]
|
||||
};
|
||||
|
||||
var fireflyDevice = {
|
||||
id: "firefly:dial",
|
||||
var matchstickDevice = {
|
||||
id: "matchstick:dial",
|
||||
target: "urn:dial-multiscreen-org:service:dial:1",
|
||||
filters: {
|
||||
server: null,
|
||||
modelName: "Eureka Dongle"
|
||||
},
|
||||
factory: function(aService) {
|
||||
Cu.import("resource://gre/modules/FireflyApp.jsm");
|
||||
return new FireflyApp(aService);
|
||||
Cu.import("resource://gre/modules/MatchstickApp.jsm");
|
||||
return new MatchstickApp(aService);
|
||||
},
|
||||
types: ["video/mp4", "video/webm"],
|
||||
extensions: ["mp4", "webm"]
|
||||
|
@ -59,7 +59,7 @@ var CastingApps = {
|
|||
|
||||
// Register targets
|
||||
SimpleServiceDiscovery.registerDevice(rokuDevice);
|
||||
SimpleServiceDiscovery.registerDevice(fireflyDevice);
|
||||
SimpleServiceDiscovery.registerDevice(matchstickDevice);
|
||||
SimpleServiceDiscovery.registerDevice(mediaPlayerDevice);
|
||||
|
||||
// Search for devices continuously every 120 seconds
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["FireflyApp"];
|
||||
this.EXPORTED_SYMBOLS = ["MatchstickApp"];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
|
@ -106,12 +106,12 @@ const PLAYER_STATUS_STOP = 4;
|
|||
const PLAYER_STATUS_IDLE = 5;
|
||||
const PLAYER_STATUS_BUFFERING = 6;
|
||||
|
||||
function FireflyApp(service) {
|
||||
function MatchstickApp(service) {
|
||||
let uri = Services.io.newURI(service.location, null, null);
|
||||
this._ip = uri.host;
|
||||
};
|
||||
|
||||
FireflyApp.prototype = {
|
||||
MatchstickApp.prototype = {
|
||||
_ip: null,
|
||||
_port: 8888,
|
||||
_cmd_socket: null,
|
|
@ -9,12 +9,12 @@ EXTRA_JS_MODULES += [
|
|||
'AndroidLog.jsm',
|
||||
'ContactService.jsm',
|
||||
'dbg-browser-actors.js',
|
||||
'FireflyApp.jsm',
|
||||
'HelperApps.jsm',
|
||||
'Home.jsm',
|
||||
'HomeProvider.jsm',
|
||||
'JNI.jsm',
|
||||
'LightweightThemeConsumer.jsm',
|
||||
'MatchstickApp.jsm',
|
||||
'MediaPlayerApp.jsm',
|
||||
'Messaging.jsm',
|
||||
'NetErrorHelper.jsm',
|
||||
|
|
|
@ -5729,6 +5729,20 @@
|
|||
"n_buckets": "50",
|
||||
"description": "The total number of distinct attempts by third-party sites to place cookies which have been rejected. Measures are normalized per 24h."
|
||||
},
|
||||
"DEVTOOLS_DEBUGGER_RDP_LOCAL_GET_EXECUTABLE_LINES_MS": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": "10000",
|
||||
"n_buckets": "1000",
|
||||
"description": "The time (in milliseconds) that it took a 'getExecutableLines' request to go round trip."
|
||||
},
|
||||
"DEVTOOLS_DEBUGGER_RDP_REMOTE_GET_EXECUTABLE_LINES_MS": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": "10000",
|
||||
"n_buckets": "1000",
|
||||
"description": "The time (in milliseconds) that it took a 'getExecutableLines' request to go round trip."
|
||||
},
|
||||
"DEVTOOLS_DEBUGGER_RDP_LOCAL_BLACKBOX_MS": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
|
|
|
@ -2425,6 +2425,23 @@ SourceClient.prototype = {
|
|||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get Executable Lines from a source
|
||||
*
|
||||
* @param aCallback Function
|
||||
* The callback function called when we receive the response from the server.
|
||||
*/
|
||||
getExecutableLines: function(cb){
|
||||
let packet = {
|
||||
to: this._form.actor,
|
||||
type: "getExecutableLines"
|
||||
};
|
||||
|
||||
this._client.request(packet, res => {
|
||||
cb(res.lines);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a long string grip for this SourceClient's source.
|
||||
*/
|
||||
|
|
|
@ -2642,6 +2642,66 @@ SourceActor.prototype = {
|
|||
return sourceFetched;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all executable lines from the current source
|
||||
* @return Array - Executable lines of the current script
|
||||
**/
|
||||
getExecutableLines: function () {
|
||||
// Check if the original source is source mapped
|
||||
let packet = {
|
||||
from: this.actorID
|
||||
};
|
||||
|
||||
let lines;
|
||||
|
||||
if (this._sourceMap) {
|
||||
lines = new Set();
|
||||
|
||||
// Position of executable lines in the generated source
|
||||
let offsets = this.getExecutableOffsets(this._generatedSource, false);
|
||||
for (let offset of offsets) {
|
||||
let {line, source} = this._sourceMap.originalPositionFor({
|
||||
line: offset.lineNumber,
|
||||
column: offset.columnNumber
|
||||
});
|
||||
|
||||
if (source === this._url) {
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Converting the set given by getExecutableOffsets to an array
|
||||
lines = this.getExecutableOffsets(this._url, true);
|
||||
}
|
||||
|
||||
// Converting the Set into an array
|
||||
packet.lines = [line for (line of lines)];
|
||||
packet.lines.sort((a, b) => {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
return packet;
|
||||
},
|
||||
|
||||
/**
|
||||
* Extract all executable offsets from the given script
|
||||
* @param String url - extract offsets of the script with this url
|
||||
* @param Boolean onlyLine - will return only the line number
|
||||
* @return Set - Executable offsets/lines of the script
|
||||
**/
|
||||
getExecutableOffsets: function (url, onlyLine) {
|
||||
let offsets = new Set();
|
||||
for (let s of this.threadActor.dbg.findScripts(this.threadActor.global)) {
|
||||
if (s.url === url) {
|
||||
for (let offset of s.getAllColumnOffsets()) {
|
||||
offsets.add(onlyLine ? offset.lineNumber : offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return offsets;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "source" packet.
|
||||
*/
|
||||
|
@ -2858,7 +2918,8 @@ SourceActor.prototype.requestTypes = {
|
|||
"blackbox": SourceActor.prototype.onBlackBox,
|
||||
"unblackbox": SourceActor.prototype.onUnblackBox,
|
||||
"prettyPrint": SourceActor.prototype.onPrettyPrint,
|
||||
"disablePrettyPrint": SourceActor.prototype.onDisablePrettyPrint
|
||||
"disablePrettyPrint": SourceActor.prototype.onDisablePrettyPrint,
|
||||
"getExecutableLines": SourceActor.prototype.getExecutableLines
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Test if getExecutableLines return correct information
|
||||
*/
|
||||
|
||||
let gDebuggee;
|
||||
let gClient;
|
||||
let gThreadClient;
|
||||
|
||||
const SOURCE_MAPPED_FILE = getFileUrl("sourcemapped.js");
|
||||
|
||||
function run_test() {
|
||||
initTestDebuggerServer();
|
||||
gDebuggee = addTestGlobal("test-get-executable-lines");
|
||||
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
gClient.connect(function _onConnect() {
|
||||
attachTestTabAndResume(
|
||||
gClient,
|
||||
"test-get-executable-lines",
|
||||
function (aResponse, aTabClient, aThreadClient) {
|
||||
gThreadClient = aThreadClient;
|
||||
test_executable_lines();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_executable_lines() {
|
||||
gClient.addOneTimeListener("newSource", function _onNewSource(evt, packet) {
|
||||
do_check_eq(evt, "newSource");
|
||||
|
||||
gThreadClient.getSources(function ({error, sources}) {
|
||||
do_check_true(!error);
|
||||
let source = gThreadClient.source(sources[0]);
|
||||
source.getExecutableLines(function(lines){
|
||||
do_check_true(arrays_equal([1, 2, 4, 6], lines));
|
||||
finishClient(gClient);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let code = readFile("sourcemapped.js") + "\n//# sourceMappingURL=" +
|
||||
getFileUrl("source-map-data/sourcemapped.map");
|
||||
|
||||
Components.utils.evalInSandbox(code, gDebuggee, "1.8",
|
||||
SOURCE_MAPPED_FILE, 1);
|
||||
}
|
||||
|
||||
function arrays_equal(a,b) {
|
||||
return !(a<b || b<a);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Test if getExecutableLines return correct information
|
||||
*/
|
||||
|
||||
let gDebuggee;
|
||||
let gClient;
|
||||
let gThreadClient;
|
||||
|
||||
const SOURCE_MAPPED_FILE = getFileUrl("sourcemapped.js");
|
||||
|
||||
function run_test() {
|
||||
initTestDebuggerServer();
|
||||
gDebuggee = addTestGlobal("test-get-executable-lines");
|
||||
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
gClient.connect(function _onConnect() {
|
||||
attachTestTabAndResume(
|
||||
gClient,
|
||||
"test-get-executable-lines",
|
||||
function (aResponse, aTabClient, aThreadClient) {
|
||||
gThreadClient = aThreadClient;
|
||||
test_executable_lines();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_executable_lines() {
|
||||
gClient.addOneTimeListener("newSource", function _onNewSource(evt, packet) {
|
||||
do_check_eq(evt, "newSource");
|
||||
|
||||
gThreadClient.getSources(function ({error, sources}) {
|
||||
do_check_true(!error);
|
||||
let source = gThreadClient.source(sources[0]);
|
||||
source.getExecutableLines(function(lines){
|
||||
do_check_true(arrays_equal([2, 3, 5, 6, 7, 8, 12, 14, 16], lines));
|
||||
finishClient(gClient);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let code = readFile("sourcemapped.js");
|
||||
|
||||
Components.utils.evalInSandbox(code, gDebuggee, "1.8",
|
||||
SOURCE_MAPPED_FILE, 1);
|
||||
}
|
||||
|
||||
function arrays_equal(a,b) {
|
||||
return !(a<b || b<a);
|
||||
}
|
|
@ -213,7 +213,5 @@ reason = bug 937197
|
|||
[test_monitor_actor.js]
|
||||
[test_symbols-01.js]
|
||||
[test_symbols-02.js]
|
||||
[test_memory_footprint.js]
|
||||
run-sequentially = measure memory, has to be run solo
|
||||
skip-if = os != 'linux' || debug || asan
|
||||
reason = bug 1014071
|
||||
[test_get-executable-lines.js]
|
||||
[test_get-executable-lines-source-map.js]
|
||||
|
|
|
@ -123,6 +123,9 @@ const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
|
|||
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
|
||||
|
||||
const UNNAMED_PROVIDER = "<unnamed-provider>";
|
||||
function providerName(aProvider) {
|
||||
return aProvider.name || UNNAMED_PROVIDER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preference listener which listens for a change in the
|
||||
|
@ -176,9 +179,18 @@ function safeCall(aCallback, ...aArgs) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report an exception thrown by a provider API method.
|
||||
*/
|
||||
function reportProviderError(aProvider, aMethod, aError) {
|
||||
let method = `provider ${providerName(aProvider)}.${aMethod}`;
|
||||
AddonManagerPrivate.recordException("AMI", method, aError);
|
||||
logger.error("Exception calling " + method, aError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a method on a provider if it exists and consumes any thrown exception.
|
||||
* Any parameters after the dflt parameter are passed to the provider's method.
|
||||
* Any parameters after the aDefault parameter are passed to the provider's method.
|
||||
*
|
||||
* @param aProvider
|
||||
* The provider to call
|
||||
|
@ -187,7 +199,7 @@ function safeCall(aCallback, ...aArgs) {
|
|||
* @param aDefault
|
||||
* A default return value if the provider does not implement the named
|
||||
* method or throws an error.
|
||||
* @return the return value from the provider or dflt if the provider does not
|
||||
* @return the return value from the provider, or aDefault if the provider does not
|
||||
* implement method or throws an error
|
||||
*/
|
||||
function callProvider(aProvider, aMethod, aDefault, ...aArgs) {
|
||||
|
@ -198,11 +210,39 @@ function callProvider(aProvider, aMethod, aDefault, ...aArgs) {
|
|||
return aProvider[aMethod].apply(aProvider, aArgs);
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Exception calling provider " + aMethod, e);
|
||||
reportProviderError(aProvider, aMethod, e);
|
||||
return aDefault;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a method on a provider if it exists and consumes any thrown exception.
|
||||
* Parameters after aMethod are passed to aProvider.aMethod().
|
||||
* The last parameter must be a callback function.
|
||||
* If the provider does not implement the method, or the method throws, calls
|
||||
* the callback with 'undefined'.
|
||||
*
|
||||
* @param aProvider
|
||||
* The provider to call
|
||||
* @param aMethod
|
||||
* The method name to call
|
||||
*/
|
||||
function callProviderAsync(aProvider, aMethod, ...aArgs) {
|
||||
let callback = aArgs[aArgs.length - 1];
|
||||
if (!(aMethod in aProvider)) {
|
||||
callback(undefined);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
return aProvider[aMethod].apply(aProvider, aArgs);
|
||||
}
|
||||
catch (e) {
|
||||
reportProviderError(aProvider, aMethod, e);
|
||||
callback(undefined);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently selected locale for display.
|
||||
* @return the selected locale or "en-US" if none is selected
|
||||
|
@ -628,7 +668,7 @@ var AddonManagerInternal = {
|
|||
|
||||
callProvider(aProvider, "startup", null, aAppChanged, aOldAppVersion, aOldPlatformVersion);
|
||||
if ('shutdown' in aProvider) {
|
||||
let name = aProvider.name || "Provider";
|
||||
let name = providerName(aProvider);
|
||||
let AMProviderShutdown = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.debug("Calling shutdown blocker for " + name);
|
||||
|
@ -893,7 +933,7 @@ var AddonManagerInternal = {
|
|||
// remove the blocker for this provider's shutdown and call it.
|
||||
// If we're already shutting down, just let gShutdownBarrier call it to avoid races.
|
||||
if (gStarted && !gShutdownInProgress) {
|
||||
logger.debug("Unregistering shutdown blocker for " + (aProvider.name || "Provider"));
|
||||
logger.debug("Unregistering shutdown blocker for " + providerName(aProvider));
|
||||
let shutter = this.providerShutdowns.get(aProvider);
|
||||
if (shutter) {
|
||||
this.providerShutdowns.delete(aProvider);
|
||||
|
@ -908,6 +948,8 @@ var AddonManagerInternal = {
|
|||
* Calls a method on all registered providers if it exists and consumes any
|
||||
* thrown exception. Return values are ignored. Any parameters after the
|
||||
* method parameter are passed to the provider's method.
|
||||
* WARNING: Do not use for asynchronous calls; callProviders() does not
|
||||
* invoke callbacks if provider methods throw synchronous exceptions.
|
||||
*
|
||||
* @param aMethod
|
||||
* The method name to call
|
||||
|
@ -925,8 +967,7 @@ var AddonManagerInternal = {
|
|||
provider[aMethod].apply(provider, aArgs);
|
||||
}
|
||||
catch (e) {
|
||||
AddonManagerPrivate.recordException("AMI", "provider " + aMethod, e);
|
||||
logger.error("Exception calling provider " + aMethod, e);
|
||||
reportProviderError(aProvider, aMethod, e);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -976,7 +1017,7 @@ var AddonManagerInternal = {
|
|||
catch(err) {
|
||||
savedError = err;
|
||||
logger.error("Failure during wait for shutdown barrier", err);
|
||||
AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonRepository", err);
|
||||
AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonManager providers", err);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1553,10 +1594,8 @@ var AddonManagerInternal = {
|
|||
|
||||
new AsyncObjectCaller(this.providers, "updateAddonRepositoryData", {
|
||||
nextObject: function updateAddonRepositoryData_nextObject(aCaller, aProvider) {
|
||||
callProvider(aProvider,
|
||||
"updateAddonRepositoryData",
|
||||
null,
|
||||
aCaller.callNext.bind(aCaller));
|
||||
callProviderAsync(aProvider, "updateAddonRepositoryData",
|
||||
aCaller.callNext.bind(aCaller));
|
||||
},
|
||||
noMoreObjects: function updateAddonRepositoryData_noMoreObjects(aCaller) {
|
||||
safeCall(aCallback);
|
||||
|
@ -1635,9 +1674,9 @@ var AddonManagerInternal = {
|
|||
let providers = this.providers.slice(0);
|
||||
for (let provider of providers) {
|
||||
if (callProvider(provider, "supportsMimetype", false, aMimetype)) {
|
||||
callProvider(provider, "getInstallForURL", null,
|
||||
aUrl, aHash, aName, aIcons, aVersion, aLoadGroup,
|
||||
function getInstallForURL_safeCall(aInstall) {
|
||||
callProviderAsync(provider, "getInstallForURL",
|
||||
aUrl, aHash, aName, aIcons, aVersion, aLoadGroup,
|
||||
function getInstallForURL_safeCall(aInstall) {
|
||||
safeCall(aCallback, aInstall);
|
||||
});
|
||||
return;
|
||||
|
@ -1676,8 +1715,8 @@ var AddonManagerInternal = {
|
|||
|
||||
new AsyncObjectCaller(this.providers, "getInstallForFile", {
|
||||
nextObject: function getInstallForFile_nextObject(aCaller, aProvider) {
|
||||
callProvider(aProvider, "getInstallForFile", null, aFile,
|
||||
function getInstallForFile_safeCall(aInstall) {
|
||||
callProviderAsync(aProvider, "getInstallForFile", aFile,
|
||||
function getInstallForFile_safeCall(aInstall) {
|
||||
if (aInstall)
|
||||
safeCall(aCallback, aInstall);
|
||||
else
|
||||
|
@ -1718,8 +1757,8 @@ var AddonManagerInternal = {
|
|||
|
||||
new AsyncObjectCaller(this.providers, "getInstallsByTypes", {
|
||||
nextObject: function getInstallsByTypes_nextObject(aCaller, aProvider) {
|
||||
callProvider(aProvider, "getInstallsByTypes", null, aTypes,
|
||||
function getInstallsByTypes_safeCall(aProviderInstalls) {
|
||||
callProviderAsync(aProvider, "getInstallsByTypes", aTypes,
|
||||
function getInstallsByTypes_safeCall(aProviderInstalls) {
|
||||
installs = installs.concat(aProviderInstalls);
|
||||
aCaller.callNext();
|
||||
});
|
||||
|
@ -1970,8 +2009,8 @@ var AddonManagerInternal = {
|
|||
|
||||
new AsyncObjectCaller(this.providers, "getAddonByID", {
|
||||
nextObject: function getAddonByID_nextObject(aCaller, aProvider) {
|
||||
callProvider(aProvider, "getAddonByID", null, aID,
|
||||
function getAddonByID_safeCall(aAddon) {
|
||||
callProviderAsync(aProvider, "getAddonByID", aID,
|
||||
function getAddonByID_safeCall(aAddon) {
|
||||
if (aAddon)
|
||||
safeCall(aCallback, aAddon);
|
||||
else
|
||||
|
@ -2009,8 +2048,8 @@ var AddonManagerInternal = {
|
|||
|
||||
new AsyncObjectCaller(this.providers, "getAddonBySyncGUID", {
|
||||
nextObject: function getAddonBySyncGUID_nextObject(aCaller, aProvider) {
|
||||
callProvider(aProvider, "getAddonBySyncGUID", null, aGUID,
|
||||
function getAddonBySyncGUID_safeCall(aAddon) {
|
||||
callProviderAsync(aProvider, "getAddonBySyncGUID", aGUID,
|
||||
function getAddonBySyncGUID_safeCall(aAddon) {
|
||||
if (aAddon) {
|
||||
safeCall(aCallback, aAddon);
|
||||
} else {
|
||||
|
@ -2090,8 +2129,8 @@ var AddonManagerInternal = {
|
|||
|
||||
new AsyncObjectCaller(this.providers, "getAddonsByTypes", {
|
||||
nextObject: function getAddonsByTypes_nextObject(aCaller, aProvider) {
|
||||
callProvider(aProvider, "getAddonsByTypes", null, aTypes,
|
||||
function getAddonsByTypes_concatAddons(aProviderAddons) {
|
||||
callProviderAsync(aProvider, "getAddonsByTypes", aTypes,
|
||||
function getAddonsByTypes_concatAddons(aProviderAddons) {
|
||||
addons = addons.concat(aProviderAddons);
|
||||
aCaller.callNext();
|
||||
});
|
||||
|
@ -2150,9 +2189,9 @@ var AddonManagerInternal = {
|
|||
new AsyncObjectCaller(this.providers, "getAddonsWithOperationsByTypes", {
|
||||
nextObject: function getAddonsWithOperationsByTypes_nextObject
|
||||
(aCaller, aProvider) {
|
||||
callProvider(aProvider, "getAddonsWithOperationsByTypes", null, aTypes,
|
||||
function getAddonsWithOperationsByTypes_concatAddons
|
||||
(aProviderAddons) {
|
||||
callProviderAsync(aProvider, "getAddonsWithOperationsByTypes", aTypes,
|
||||
function getAddonsWithOperationsByTypes_concatAddons
|
||||
(aProviderAddons) {
|
||||
addons = addons.concat(aProviderAddons);
|
||||
aCaller.callNext();
|
||||
});
|
||||
|
|
|
@ -27,7 +27,7 @@ function mockAddonProvider(aName) {
|
|||
};
|
||||
mockProvider.donePromise = new Promise((resolve, reject) => {
|
||||
mockProvider.doneResolve = resolve;
|
||||
mockProvider.doneResject = reject;
|
||||
mockProvider.doneReject = reject;
|
||||
});
|
||||
mockProvider.shutdownPromise = new Promise((resolve, reject) => {
|
||||
mockProvider.shutdownResolve = resolve;
|
||||
|
@ -81,7 +81,6 @@ add_task(function* blockRepoShutdown() {
|
|||
yield mockRepo.shutdownPromise;
|
||||
// Check the shutdown state
|
||||
status = MockAsyncShutdown.status();
|
||||
do_print(JSON.stringify(status));
|
||||
equal(status[0].name, "AddonManager: Waiting for providers to shut down.");
|
||||
equal(status[0].state, "Complete");
|
||||
equal(status[1].name, "AddonRepository: async shutdown");
|
||||
|
|
|
@ -2393,7 +2393,11 @@ nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
|
|||
break;
|
||||
|
||||
case NS_THEME_TOOLTIP:
|
||||
CGContextSetRGBFillColor(cgContext, 0.996, 1.000, 0.792, 0.950);
|
||||
if (nsCocoaFeatures::OnYosemiteOrLater()) {
|
||||
CGContextSetRGBFillColor(cgContext, 0.945, 0.942, 0.945, 0.950);
|
||||
} else {
|
||||
CGContextSetRGBFillColor(cgContext, 0.996, 1.000, 0.792, 0.950);
|
||||
}
|
||||
CGContextFillRect(cgContext, macRect);
|
||||
break;
|
||||
|
||||
|
|
|
@ -50,6 +50,23 @@ ScreenProxy::GetRect(int32_t *outLeft,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScreenProxy::GetRectDisplayPix(int32_t *outLeft,
|
||||
int32_t *outTop,
|
||||
int32_t *outWidth,
|
||||
int32_t *outHeight)
|
||||
{
|
||||
if (!EnsureCacheIsValid()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
*outLeft = mRectDisplayPix.x;
|
||||
*outTop = mRectDisplayPix.y;
|
||||
*outWidth = mRectDisplayPix.width;
|
||||
*outHeight = mRectDisplayPix.height;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScreenProxy::GetAvailRect(int32_t *outLeft,
|
||||
int32_t *outTop,
|
||||
|
@ -67,6 +84,23 @@ ScreenProxy::GetAvailRect(int32_t *outLeft,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScreenProxy::GetAvailRectDisplayPix(int32_t *outLeft,
|
||||
int32_t *outTop,
|
||||
int32_t *outWidth,
|
||||
int32_t *outHeight)
|
||||
{
|
||||
if (!EnsureCacheIsValid()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
*outLeft = mAvailRectDisplayPix.x;
|
||||
*outTop = mAvailRectDisplayPix.y;
|
||||
*outWidth = mAvailRectDisplayPix.width;
|
||||
*outHeight = mAvailRectDisplayPix.height;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScreenProxy::GetPixelDepth(int32_t *aPixelDepth)
|
||||
{
|
||||
|
@ -94,7 +128,9 @@ ScreenProxy::PopulateByDetails(ScreenDetails aDetails)
|
|||
{
|
||||
mId = aDetails.id();
|
||||
mRect = nsIntRect(aDetails.rect());
|
||||
mRectDisplayPix = nsIntRect(aDetails.rectDisplayPix());
|
||||
mAvailRect = nsIntRect(aDetails.availRect());
|
||||
mAvailRectDisplayPix = nsIntRect(aDetails.availRectDisplayPix());
|
||||
mPixelDepth = aDetails.pixelDepth();
|
||||
mColorDepth = aDetails.colorDepth();
|
||||
mContentsScaleFactor = aDetails.contentsScaleFactor();
|
||||
|
|
|
@ -29,10 +29,18 @@ public:
|
|||
int32_t* aTop,
|
||||
int32_t* aWidth,
|
||||
int32_t* aHeight) MOZ_OVERRIDE;
|
||||
NS_IMETHOD GetRectDisplayPix(int32_t* aLeft,
|
||||
int32_t* aTop,
|
||||
int32_t* aWidth,
|
||||
int32_t* aHeight) MOZ_OVERRIDE;
|
||||
NS_IMETHOD GetAvailRect(int32_t* aLeft,
|
||||
int32_t* aTop,
|
||||
int32_t* aWidth,
|
||||
int32_t* aHeight) MOZ_OVERRIDE;
|
||||
NS_IMETHOD GetAvailRectDisplayPix(int32_t* aLeft,
|
||||
int32_t* aTop,
|
||||
int32_t* aWidth,
|
||||
int32_t* aHeight) MOZ_OVERRIDE;
|
||||
NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth) MOZ_OVERRIDE;
|
||||
NS_IMETHOD GetColorDepth(int32_t* aColorDepth) MOZ_OVERRIDE;
|
||||
|
||||
|
@ -49,7 +57,9 @@ private:
|
|||
int32_t mPixelDepth;
|
||||
int32_t mColorDepth;
|
||||
nsIntRect mRect;
|
||||
nsIntRect mRectDisplayPix;
|
||||
nsIntRect mAvailRect;
|
||||
nsIntRect mAvailRectDisplayPix;
|
||||
bool mCacheValid;
|
||||
bool mCacheWillInvalidate;
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче