Merge fx-team to m-c a=merge despite the CLOSED TREE

This commit is contained in:
Wes Kocher 2014-10-01 16:02:06 -07:00
Родитель fb0de08341 2cff510db7
Коммит 60713994dc
50 изменённых файлов: 1010 добавлений и 639 удалений

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

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