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

This commit is contained in:
Ryan VanderMeulen 2015-08-12 09:12:36 -04:00
commit 3842de2426
129 changed files with 1151 additions and 775 deletions

View File

@ -1601,11 +1601,12 @@ pref("devtools.webconsole.persistlog", false);
pref("devtools.webconsole.timestampMessages", false);
// The number of lines that are displayed in the web console for the Net,
// CSS, JS and Web Developer categories.
pref("devtools.hud.loglimit.network", 200);
pref("devtools.hud.loglimit.cssparser", 200);
pref("devtools.hud.loglimit.exception", 200);
pref("devtools.hud.loglimit.console", 200);
// CSS, JS and Web Developer categories. These defaults should be kept in sync
// with DEFAULT_LOG_LIMIT in the webconsole frontend.
pref("devtools.hud.loglimit.network", 1000);
pref("devtools.hud.loglimit.cssparser", 1000);
pref("devtools.hud.loglimit.exception", 1000);
pref("devtools.hud.loglimit.console", 1000);
// By how many times eyedropper will magnify pixels
pref("devtools.eyedropper.zoom", 6);

View File

@ -19,10 +19,22 @@
}
.tab-close-button[pinned],
.tabbrowser-tabs[closebuttons="activetab"] > * > * > * > .tab-close-button:not([visuallyselected="true"]) {
.tabbrowser-tabs[closebuttons="activetab"] > * > * > * > .tab-close-button:not([visuallyselected="true"]),
.tab-icon-image:not([src]):not([pinned]):not([crashed]),
.tab-icon-image[busy],
.tab-throbber:not([busy]),
.tab-icon-sound:not([soundplaying]):not([muted]),
.tab-icon-sound[pinned],
.tab-icon-overlay {
display: none;
}
.tab-icon-overlay[soundplaying][pinned],
.tab-icon-overlay[muted][pinned],
.tab-icon-overlay[crashed] {
display: -moz-box;
}
.tab-label[pinned] {
width: 0;
margin-left: 0 !important;
@ -51,13 +63,6 @@ tabpanels {
}
}
.tab-icon-image:not([src]):not([pinned]):not([crashed]),
.tab-throbber:not([busy]),
.tab-icon-image[busy],
.tab-icon-overlay[busy] {
display: none;
}
.closing-tabs-spacer {
pointer-events: none;
}

View File

@ -44,7 +44,7 @@
"curly": [2, "all"],
"dot-location": [2, "property"],
"eol-last": 2,
"eqeqeq": 0, // TBD. Might need to be separate for content & chrome
"eqeqeq": [2, "smart"],
"key-spacing": [2, {"beforeColon": false, "afterColon": true }],
"linebreak-style": [2, "unix"],
"new-cap": 0, // TODO: set to 2

View File

@ -58,6 +58,7 @@
"rules": {
"arrow-parens": 0, // TBD
"arrow-spacing": 2,
"eqeqeq": 0, // TBD
"generator-star-spacing": [2, "after"],
// We should fix the errors and enable this (set to 2)
"no-var": 0,

View File

@ -53,7 +53,7 @@ loop.Client = (function() {
}
});
if (properties.length == 1) {
if (properties.length === 1) {
return data[properties[0]];
}

View File

@ -271,7 +271,7 @@ loop.contacts = (function(_, mozL10n) {
canEdit: function() {
// We cannot modify imported contacts. For the moment, the check for
// determining whether the contact is imported is based on its category.
return this.props.contact.category[0] != "google";
return this.props.contact.category[0] !== "google";
},
render: function() {
@ -417,7 +417,7 @@ loop.contacts = (function(_, mozL10n) {
let profile = this.props.mozLoop.userProfile;
let currUid = this._userProfile ? this._userProfile.uid : null;
let newUid = profile ? profile.uid : null;
if (currUid != newUid) {
if (currUid !== newUid) {
// On profile change (login, logout), reload all contacts.
this._userProfile = profile;
// The following will do a forceUpdate() for us.
@ -750,7 +750,7 @@ loop.contacts = (function(_, mozL10n) {
return (
React.createElement("div", {className: contentAreaClasses},
React.createElement("header", null, this.props.mode == "add"
React.createElement("header", null, this.props.mode === "add"
? mozL10n.get("add_contact_title")
: mozL10n.get("edit_contact_title")),
React.createElement("div", {className: cx({"form-content-container": true})},
@ -779,7 +779,7 @@ loop.contacts = (function(_, mozL10n) {
caption: mozL10n.get("cancel_button"),
onClick: this.handleCancelButtonClick}),
React.createElement(Button, {additionalClass: "button-accept",
caption: this.props.mode == "add"
caption: this.props.mode === "add"
? mozL10n.get("add_contact_button")
: mozL10n.get("edit_contact_done_button"),
onClick: this.handleAcceptButtonClick})

View File

@ -271,7 +271,7 @@ loop.contacts = (function(_, mozL10n) {
canEdit: function() {
// We cannot modify imported contacts. For the moment, the check for
// determining whether the contact is imported is based on its category.
return this.props.contact.category[0] != "google";
return this.props.contact.category[0] !== "google";
},
render: function() {
@ -417,7 +417,7 @@ loop.contacts = (function(_, mozL10n) {
let profile = this.props.mozLoop.userProfile;
let currUid = this._userProfile ? this._userProfile.uid : null;
let newUid = profile ? profile.uid : null;
if (currUid != newUid) {
if (currUid !== newUid) {
// On profile change (login, logout), reload all contacts.
this._userProfile = profile;
// The following will do a forceUpdate() for us.
@ -750,7 +750,7 @@ loop.contacts = (function(_, mozL10n) {
return (
<div className={contentAreaClasses}>
<header>{this.props.mode == "add"
<header>{this.props.mode === "add"
? mozL10n.get("add_contact_title")
: mozL10n.get("edit_contact_title")}</header>
<div className={cx({"form-content-container": true})}>
@ -779,7 +779,7 @@ loop.contacts = (function(_, mozL10n) {
caption={mozL10n.get("cancel_button")}
onClick={this.handleCancelButtonClick} />
<Button additionalClass="button-accept"
caption={this.props.mode == "add"
caption={this.props.mode === "add"
? mozL10n.get("add_contact_button")
: mozL10n.get("edit_contact_done_button")}
onClick={this.handleAcceptButtonClick} />

View File

@ -79,7 +79,7 @@ loop.panel = (function(_, mozL10n) {
if (this.props.buttonsHidden.indexOf(tabName) > -1) {
return;
}
var isSelected = (this.state.selectedTab == tabName);
var isSelected = (this.state.selectedTab === tabName);
if (!tab.props.hidden) {
var label = mozL10n.get(tabName + "_tab_button");
tabButtons.push(
@ -861,7 +861,7 @@ loop.panel = (function(_, mozL10n) {
var profile = this.props.mozLoop.userProfile;
var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
var newUid = profile ? profile.uid : null;
if (currUid == newUid) {
if (currUid === newUid) {
// Update the state of hasEncryptionKey as this might have changed now.
this.setState({hasEncryptionKey: this.props.mozLoop.hasEncryptionKey});
} else {

View File

@ -79,7 +79,7 @@ loop.panel = (function(_, mozL10n) {
if (this.props.buttonsHidden.indexOf(tabName) > -1) {
return;
}
var isSelected = (this.state.selectedTab == tabName);
var isSelected = (this.state.selectedTab === tabName);
if (!tab.props.hidden) {
var label = mozL10n.get(tabName + "_tab_button");
tabButtons.push(
@ -861,7 +861,7 @@ loop.panel = (function(_, mozL10n) {
var profile = this.props.mozLoop.userProfile;
var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
var newUid = profile ? profile.uid : null;
if (currUid == newUid) {
if (currUid === newUid) {
// Update the state of hasEncryptionKey as this might have changed now.
this.setState({hasEncryptionKey: this.props.mozLoop.hasEncryptionKey});
} else {

View File

@ -496,7 +496,7 @@ loop.store = loop.store || {};
var context = room.decryptedContext;
var oldRoomName = context.roomName;
var newRoomName = actionData.newRoomName.trim();
if (newRoomName && oldRoomName != newRoomName) {
if (newRoomName && oldRoomName !== newRoomName) {
roomData.roomName = newRoomName;
}
var oldRoomURLs = context.urls;

View File

@ -94,7 +94,7 @@ loop.roomViews = (function(mozL10n) {
var origin = event.currentTarget.dataset.provider;
var provider = this.props.socialShareProviders
.filter(function(socialProvider) {
return socialProvider.origin == origin;
return socialProvider.origin === origin;
})[0];
this.props.dispatcher.dispatch(new sharedActions.ShareRoomUrl({

View File

@ -94,7 +94,7 @@ loop.roomViews = (function(mozL10n) {
var origin = event.currentTarget.dataset.provider;
var provider = this.props.socialShareProviders
.filter(function(socialProvider) {
return socialProvider.origin == origin;
return socialProvider.origin === origin;
})[0];
this.props.dispatcher.dispatch(new sharedActions.ShareRoomUrl({

View File

@ -550,10 +550,8 @@ html[dir="rtl"] .context-content {
display: flex;
flex-flow: row nowrap;
line-height: 1.1em;
}
.context-wrapper:hover {
background-color: #dbf7ff;
/* No underline for the text in the context view. */
text-decoration: none;
}
.context-wrapper > .context-preview {
@ -571,7 +569,7 @@ html[dir="rtl"] .context-wrapper > .context-preview {
margin-right: 0;
}
.context-wrapper > .context-description {
.context-wrapper > .context-info {
flex: 0 1 auto;
display: block;
color: black;
@ -580,9 +578,18 @@ html[dir="rtl"] .context-wrapper > .context-preview {
word-wrap: break-word;
}
.context-wrapper > .context-description > .context-url {
.context-wrapper > .context-info > .context-url {
display: block;
color: #00a9dc;
font-weight: 700;
clear: both;
}
.clicks-allowed.context-wrapper:hover {
background-color: #dbf7ff;
}
/* Only underline the url, not the associated text */
.clicks-allowed.context-wrapper:hover > .context-info > .context-url {
text-decoration: underline;
}

View File

@ -899,9 +899,9 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
}
.context-url-view-wrapper {
padding-left: 1em;
padding-right: 1em;
padding-bottom: 0.5em;
/* 18px for indent of .text-chat-arrow, 1px for border of .text-chat-entry > p,
0.5rem for padding of .text-chat-entry > p */
padding: calc(18px - 1px - 0.5rem);
margin-bottom: 0.5em;
background-color: #E8F6FE;
}
@ -1401,28 +1401,6 @@ html[dir="rtl"] .room-context-btn-close {
max-width: 400px;
}
.standalone-context-url {
color: #fff;
/* Try and keep clear of local video */
height: 40%;
}
.standalone-context-url.screen-share-active {
/* Try and keep clear of remote video when screensharing */
height: 15%;
}
.standalone-context-url > img {
margin: 1em auto;
width: 16px;
height: 16px;
}
.standalone-context-url-description-wrapper {
/* So that we can use max-height for the image */
height: 20%;
}
.standalone .room-conversation .media {
background: #000;
}
@ -1480,12 +1458,14 @@ html[dir="rtl"] .text-chat-entry {
margin-left: .2em;
}
/* If you change this entry, check it doesn't affect the "special" text
chat entries as well (.speical, .room-name, .context-url-view-wrapper */
.text-chat-entry > p {
position: relative;
z-index: 10;
/* Drop the default margins from the 'p' element. */
margin: 0;
padding: .7em;
padding: .5rem;
/* leave some room for the chat bubble arrow */
max-width: 80%;
border-width: 1px;
@ -1647,12 +1627,17 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
font-weight: bold;
text-align: start;
background-color: #E8F6FE;
padding-bottom: 0;
margin-bottom: 0;
margin-right: 0;
}
.text-chat-entry.special.room-name p {
background: #E8F6FE;
max-width: 100%;
/* 18px for indent of .text-chat-arrow, 1px for border of .text-chat-entry > p,
0.5rem for padding of .text-chat-entry > p */
padding: calc(18px - 1px - 0.5rem);
padding-bottom: 0px;
}
.text-chat-entry.special > p {
@ -1666,7 +1651,7 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
.text-chat-box > form > input {
width: 100%;
height: 40px;
padding: 0 .5em .5em;
padding: 0 .4rem .4rem;
font-size: 1.1em;
border: 0;
border-top: 1px solid #d8d8d8;
@ -1703,12 +1688,6 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
}
@media screen and (max-width:640px) {
.standalone-context-url {
/* XXX We haven't got UX for standalone yet, so temporarily not displaying
on narrow window widths. See bug 1153827. */
display: none;
}
/* Rooms specific responsive styling */
.standalone .room-conversation {
background: #000;
@ -1749,7 +1728,6 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
/* e.g. very narrow widths similar to conversation window */
@media screen and (max-width:300px) {
.text-chat-view {
flex: 0 0 auto;
display: flex;
flex-flow: column nowrap;
/* 120px max-height of .text-chat-entries plus 40px of .text-chat-box */
@ -1758,8 +1736,11 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
min-height: 100px;
/* The !important is to override the values defined above which have more
specificity when we fix bug 1184559, we should be able to remove it,
but this should be tests first. */
but this should be tested first. */
height: auto !important;
/* Let the view be the minimum size it needs to be - don't flex to take up
more. */
flex: 0 0 auto !important;
}
.text-chat-entries {
@ -1771,6 +1752,12 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
min-height: 60px;
}
.text-chat-view.text-chat-disabled {
/* When we don't have text chat enabled, limit the view to the same height
as the entries, to avoid unnecessary whitespace */
max-height: 120px;
}
.text-chat-entries-empty.text-chat-disabled {
display: none;
}

View File

@ -533,7 +533,7 @@ loop.store = loop.store || {};
if (err) {
console.error("Failed to get outgoing call data", err);
var failureReason = "setup";
if (err.errno == REST_ERRNOS.USER_UNAVAILABLE) {
if (err.errno === REST_ERRNOS.USER_UNAVAILABLE) {
failureReason = REST_ERRNOS.USER_UNAVAILABLE;
}
this.dispatcher.dispatch(

View File

@ -923,9 +923,9 @@ loop.OTSdkDriver = (function() {
* Handles publishing of property changes to a stream.
*/
_onStreamPropertyChanged: function(event) {
if (event.changedProperty == STREAM_PROPERTIES.VIDEO_DIMENSIONS) {
if (event.changedProperty === STREAM_PROPERTIES.VIDEO_DIMENSIONS) {
this.dispatcher.dispatch(new sharedActions.VideoDimensionsChanged({
isLocal: event.stream.connection.id == this.session.connection.id,
isLocal: event.stream.connection.id === this.session.connection.id,
videoType: event.stream.videoType,
dimensions: event.stream[STREAM_PROPERTIES.VIDEO_DIMENSIONS]
}));
@ -1084,8 +1084,8 @@ loop.OTSdkDriver = (function() {
return;
}
if (startTime == this.CONNECTION_START_TIME_ALREADY_NOTED ||
startTime == this.CONNECTION_START_TIME_UNINITIALIZED ||
if (startTime === this.CONNECTION_START_TIME_ALREADY_NOTED ||
startTime === this.CONNECTION_START_TIME_UNINITIALIZED ||
startTime > endTime) {
if (this._debugTwoWayMediaTelemetry) {
console.log("_noteConnectionLengthIfNeeded called with " +

View File

@ -122,7 +122,7 @@ loop.store.TextChatStore = (function() {
// Notify MozLoopService if appropriate that a message has been appended
// and it should therefore check if we need a different sized window or not.
if (message.contentType != CHAT_CONTENT_TYPES.ROOM_NAME) {
if (message.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME) {
window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
}
},
@ -135,7 +135,7 @@ loop.store.TextChatStore = (function() {
receivedTextChatMessage: function(actionData) {
// If we don't know how to deal with this content, then skip it
// as this version doesn't support it.
if (actionData.contentType != CHAT_CONTENT_TYPES.TEXT) {
if (actionData.contentType !== CHAT_CONTENT_TYPES.TEXT) {
return;
}

View File

@ -116,13 +116,21 @@ loop.shared.views.chat = (function(mozL10n) {
};
},
_hasChatMessages: function() {
return this.props.messageList.some(function(message) {
return message.contentType === CHAT_CONTENT_TYPES.TEXT;
});
},
componentWillUpdate: function() {
var node = this.getDOMNode();
if (!node) {
return;
}
// Scroll only if we're right at the bottom of the display.
this.shouldScroll = node.scrollHeight === node.scrollTop + node.clientHeight;
// Scroll only if we're right at the bottom of the display, or if we've
// not had any chat messages so far.
this.shouldScroll = !this._hasChatMessages() ||
node.scrollHeight === node.scrollTop + node.clientHeight;
},
componentWillReceiveProps: function(nextProps) {
@ -138,7 +146,9 @@ loop.shared.views.chat = (function(mozL10n) {
},
componentDidUpdate: function() {
if (this.shouldScroll) {
// Don't scroll if we haven't got any chat messages yet - e.g. for context
// display, we want to display starting at the top.
if (this.shouldScroll && this._hasChatMessages()) {
// This ensures the paint is complete.
window.requestAnimationFrame(function() {
try {
@ -370,21 +380,21 @@ loop.shared.views.chat = (function(mozL10n) {
render: function() {
var messageList;
var hasNonSpecialMessages;
if (this.props.showRoomName) {
messageList = this.state.messageList;
hasNonSpecialMessages = messageList.some(function(item) {
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
});
} else {
messageList = this.state.messageList.filter(function(item) {
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME;
});
hasNonSpecialMessages = !!messageList.length;
}
// Only show the placeholder if we've sent messages.
var hasSentMessages = messageList.some(function(item) {
return item.type === CHAT_MESSAGE_TYPES.SENT;
});
var textChatViewClasses = React.addons.classSet({
"text-chat-view": true,
"text-chat-disabled": !this.state.textChatEnabled,
@ -399,7 +409,7 @@ loop.shared.views.chat = (function(mozL10n) {
useDesktopPaths: this.props.useDesktopPaths}),
React.createElement(TextChatInputView, {
dispatcher: this.props.dispatcher,
showPlaceholder: !hasNonSpecialMessages,
showPlaceholder: !hasSentMessages,
textChatEnabled: this.state.textChatEnabled})
)
);

View File

@ -116,13 +116,21 @@ loop.shared.views.chat = (function(mozL10n) {
};
},
_hasChatMessages: function() {
return this.props.messageList.some(function(message) {
return message.contentType === CHAT_CONTENT_TYPES.TEXT;
});
},
componentWillUpdate: function() {
var node = this.getDOMNode();
if (!node) {
return;
}
// Scroll only if we're right at the bottom of the display.
this.shouldScroll = node.scrollHeight === node.scrollTop + node.clientHeight;
// Scroll only if we're right at the bottom of the display, or if we've
// not had any chat messages so far.
this.shouldScroll = !this._hasChatMessages() ||
node.scrollHeight === node.scrollTop + node.clientHeight;
},
componentWillReceiveProps: function(nextProps) {
@ -138,7 +146,9 @@ loop.shared.views.chat = (function(mozL10n) {
},
componentDidUpdate: function() {
if (this.shouldScroll) {
// Don't scroll if we haven't got any chat messages yet - e.g. for context
// display, we want to display starting at the top.
if (this.shouldScroll && this._hasChatMessages()) {
// This ensures the paint is complete.
window.requestAnimationFrame(function() {
try {
@ -370,21 +380,21 @@ loop.shared.views.chat = (function(mozL10n) {
render: function() {
var messageList;
var hasNonSpecialMessages;
if (this.props.showRoomName) {
messageList = this.state.messageList;
hasNonSpecialMessages = messageList.some(function(item) {
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
});
} else {
messageList = this.state.messageList.filter(function(item) {
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME;
});
hasNonSpecialMessages = !!messageList.length;
}
// Only show the placeholder if we've sent messages.
var hasSentMessages = messageList.some(function(item) {
return item.type === CHAT_MESSAGE_TYPES.SENT;
});
var textChatViewClasses = React.addons.classSet({
"text-chat-view": true,
"text-chat-disabled": !this.state.textChatEnabled,
@ -399,7 +409,7 @@ loop.shared.views.chat = (function(mozL10n) {
useDesktopPaths={this.props.useDesktopPaths} />
<TextChatInputView
dispatcher={this.props.dispatcher}
showPlaceholder={!hasNonSpecialMessages}
showPlaceholder={!hasSentMessages}
textChatEnabled={this.state.textChatEnabled} />
</div>
);

View File

@ -687,7 +687,7 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
var prop;
for (var i = 0, lA = propsA.length; i < lA; ++i) {
prop = propsA[i];
if (propsB.indexOf(prop) == -1) {
if (propsB.indexOf(prop) === -1) {
diff.removed.push(prop);
} else if (a[prop] !== b[prop]) {
diff.updated.push(prop);
@ -696,7 +696,7 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
for (var j = 0, lB = propsB.length; j < lB; ++j) {
prop = propsB[j];
if (propsA.indexOf(prop) == -1) {
if (propsA.indexOf(prop) === -1) {
diff.added.push(prop);
}
}

View File

@ -115,7 +115,7 @@ loop.validate = (function() {
try {
return typeof Type === "undefined" || // skip checking
Type === null && value === null || // null type
value.constructor == Type || // native type
value.constructor === Type || // native type
Type.prototype.isPrototypeOf(value) || // custom type
typeName(value) === typeName(Type); // type string eq.
} catch (e) {

View File

@ -813,18 +813,25 @@ loop.shared.views = (function(_, mozL10n) {
"shared/img/icons-16x16.svg#globe";
}
var wrapperClasses = React.addons.classSet({
"context-wrapper": true,
"clicks-allowed": this.props.allowClick
});
return (
React.createElement("div", {className: "context-content"},
this.renderContextTitle(),
React.createElement("div", {className: "context-wrapper"},
React.createElement("img", {className: "context-preview", src: thumbnail}),
React.createElement("span", {className: "context-description"},
this.props.description,
React.createElement("a", {className: "context-url",
React.createElement("a", {className: wrapperClasses,
href: this.props.allowClick ? this.props.url : null,
onClick: this.handleLinkClick,
rel: "noreferrer",
target: "_blank"}, hostname)
target: "_blank"},
React.createElement("img", {className: "context-preview", src: thumbnail}),
React.createElement("span", {className: "context-info"},
this.props.description,
React.createElement("span", {className: "context-url"},
hostname
)
)
)
)
@ -988,7 +995,7 @@ loop.shared.views = (function(_, mozL10n) {
componentWillReceiveProps: function(nextProps) {
// This is all for the ui-showcase's benefit.
if (this.props.matchMedia != nextProps.matchMedia) {
if (this.props.matchMedia !== nextProps.matchMedia) {
this.updateLocalMediaState(null, nextProps.matchMedia);
}
},
@ -1003,7 +1010,7 @@ loop.shared.views = (function(_, mozL10n) {
updateLocalMediaState: function(event, matchMedia) {
var newState = this.isLocalMediaAbsolutelyPositioned(matchMedia);
if (this.state.localMediaAboslutelyPositioned != newState) {
if (this.state.localMediaAboslutelyPositioned !== newState) {
this.setState({
localMediaAboslutelyPositioned: newState
});

View File

@ -813,20 +813,27 @@ loop.shared.views = (function(_, mozL10n) {
"shared/img/icons-16x16.svg#globe";
}
var wrapperClasses = React.addons.classSet({
"context-wrapper": true,
"clicks-allowed": this.props.allowClick
});
return (
<div className="context-content">
{this.renderContextTitle()}
<div className="context-wrapper">
<img className="context-preview" src={thumbnail} />
<span className="context-description">
{this.props.description}
<a className="context-url"
<a className={wrapperClasses}
href={this.props.allowClick ? this.props.url : null}
onClick={this.handleLinkClick}
rel="noreferrer"
target="_blank">{hostname}</a>
target="_blank">
<img className="context-preview" src={thumbnail} />
<span className="context-info">
{this.props.description}
<span className="context-url">
{hostname}
</span>
</div>
</span>
</a>
</div>
);
}
@ -988,7 +995,7 @@ loop.shared.views = (function(_, mozL10n) {
componentWillReceiveProps: function(nextProps) {
// This is all for the ui-showcase's benefit.
if (this.props.matchMedia != nextProps.matchMedia) {
if (this.props.matchMedia !== nextProps.matchMedia) {
this.updateLocalMediaState(null, nextProps.matchMedia);
}
},
@ -1003,7 +1010,7 @@ loop.shared.views = (function(_, mozL10n) {
updateLocalMediaState: function(event, matchMedia) {
var newState = this.isLocalMediaAbsolutelyPositioned(matchMedia);
if (this.state.localMediaAboslutelyPositioned != newState) {
if (this.state.localMediaAboslutelyPositioned !== newState) {
this.setState({
localMediaAboslutelyPositioned: newState
});

View File

@ -50,7 +50,8 @@ loop.store.StandaloneMetricsStore = (function() {
"mediaConnected",
"recordClick",
"remotePeerConnected",
"retryAfterRoomFailure"
"retryAfterRoomFailure",
"windowUnload"
],
/**
@ -257,6 +258,17 @@ loop.store.StandaloneMetricsStore = (function() {
var muteState = muted ? "mute" : "unmute";
this._storeEvent(METRICS_GA_CATEGORY.general, muteType, muteState);
},
/**
* Called when the window is unloaded, either by code, or by the user
* explicitly closing it. Expected to do any necessary housekeeping, such
* as shutting down the call cleanly and adding any relevant telemetry data.
*/
windowUnload: function() {
if (this.activeRoomStore) {
this.stopListening(this.activeRoomStore);
}
}
});

View File

@ -90,6 +90,29 @@ loop.standaloneRoomViews = (function(mozL10n) {
roomUsed: React.PropTypes.bool.isRequired
},
componentDidMount: function() {
// Watch for messages from the waiting-tile iframe
window.addEventListener("message", this.recordTileClick);
},
componentWillUnmount: function() {
window.removeEventListener("message", this.recordTileClick);
},
recordTileClick: function(event) {
if (event.data === "tile-click") {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: "Tiles iframe click"
}));
}
},
recordTilesSupport: function() {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: "Tiles support link click"
}));
},
_renderCallToActionLink: function() {
if (this.props.isFirefox) {
return (
@ -155,7 +178,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
React.createElement("p", {className: "room-waiting-area"},
mozL10n.get("rooms_read_while_wait_offer"),
React.createElement("a", {href: loop.config.tilesSupportUrl,
onClick: this.recordClick,
onClick: this.recordTilesSupport,
rel: "noreferrer",
target: "_blank"},
React.createElement("i", {className: "room-waiting-help"})
@ -414,6 +437,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
case ROOM_STATES.FAILED:
case ROOM_STATES.CLOSING:
case ROOM_STATES.FULL:
case ROOM_STATES.ENDED:
// the other person has shown up, so we don't want to show an avatar
return true;
@ -519,6 +544,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
return {
StandaloneRoomFooter: StandaloneRoomFooter,
StandaloneRoomHeader: StandaloneRoomHeader,
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
StandaloneRoomView: StandaloneRoomView
};
})(navigator.mozL10n);

View File

@ -90,6 +90,29 @@ loop.standaloneRoomViews = (function(mozL10n) {
roomUsed: React.PropTypes.bool.isRequired
},
componentDidMount: function() {
// Watch for messages from the waiting-tile iframe
window.addEventListener("message", this.recordTileClick);
},
componentWillUnmount: function() {
window.removeEventListener("message", this.recordTileClick);
},
recordTileClick: function(event) {
if (event.data === "tile-click") {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: "Tiles iframe click"
}));
}
},
recordTilesSupport: function() {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: "Tiles support link click"
}));
},
_renderCallToActionLink: function() {
if (this.props.isFirefox) {
return (
@ -155,7 +178,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
<p className="room-waiting-area">
{mozL10n.get("rooms_read_while_wait_offer")}
<a href={loop.config.tilesSupportUrl}
onClick={this.recordClick}
onClick={this.recordTilesSupport}
rel="noreferrer"
target="_blank">
<i className="room-waiting-help"></i>
@ -414,6 +437,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
case ROOM_STATES.FAILED:
case ROOM_STATES.CLOSING:
case ROOM_STATES.FULL:
case ROOM_STATES.ENDED:
// the other person has shown up, so we don't want to show an avatar
return true;
@ -519,6 +544,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
return {
StandaloneRoomFooter: StandaloneRoomFooter,
StandaloneRoomHeader: StandaloneRoomHeader,
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
StandaloneRoomView: StandaloneRoomView
};
})(navigator.mozL10n);

View File

@ -93,7 +93,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
render: function() {
if (this.props.isFirefox) {
return React.createElement("div", null);
return null;
}
return (
React.createElement("div", {className: "promote-firefox"},
@ -649,8 +649,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
React.PropTypes.instanceOf(FxOSConversationModel)
]).isRequired,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
.isRequired,
isFirefox: React.PropTypes.bool.isRequired,
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection).isRequired,
sdk: React.PropTypes.object.isRequired
},
@ -738,7 +738,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
}
case "expired": {
return (
React.createElement(CallUrlExpiredView, null)
React.createElement(CallUrlExpiredView, {isFirefox: this.props.isFirefox})
);
}
default: {
@ -986,6 +986,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
client: this.props.client,
conversation: this.props.conversation,
dispatcher: this.props.dispatcher,
isFirefox: this.state.isFirefox,
notifications: this.props.notifications,
sdk: this.props.sdk})
);

View File

@ -93,7 +93,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
render: function() {
if (this.props.isFirefox) {
return <div />;
return null;
}
return (
<div className="promote-firefox">
@ -649,8 +649,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
React.PropTypes.instanceOf(FxOSConversationModel)
]).isRequired,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
.isRequired,
isFirefox: React.PropTypes.bool.isRequired,
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection).isRequired,
sdk: React.PropTypes.object.isRequired
},
@ -738,7 +738,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
}
case "expired": {
return (
<CallUrlExpiredView />
<CallUrlExpiredView isFirefox={this.props.isFirefox}/>
);
}
default: {
@ -986,6 +986,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
client={this.props.client}
conversation={this.props.conversation}
dispatcher={this.props.dispatcher}
isFirefox={this.state.isFirefox}
notifications={this.props.notifications}
sdk={this.props.sdk} />
);

View File

@ -112,7 +112,7 @@ describe("loop.Client", function() {
sinon.assert.calledOnce(callback);
sinon.assert.calledWithExactly(callback, sinon.match(function(err) {
return err.code == 400 && err.message == "invalid token";
return err.code === 400 && err.message === "invalid token";
}));
});

View File

@ -87,21 +87,21 @@ describe("loop.contacts", function() {
navigator.mozLoop = {
getStrings: function(entityName) {
var textContentValue = "fakeText";
if (entityName == "add_contact_title") {
if (entityName === "add_contact_title") {
textContentValue = fakeAddContactTitleText;
} else if (entityName == "add_contact_button") {
} else if (entityName === "add_contact_button") {
textContentValue = fakeAddContactButtonText;
} else if (entityName == "edit_contact_title") {
} else if (entityName === "edit_contact_title") {
textContentValue = fakeEditContactButtonText;
} else if (entityName == "edit_contact_done_button") {
} else if (entityName === "edit_contact_done_button") {
textContentValue = fakeDoneButtonText;
}
return JSON.stringify({textContent: textContentValue});
},
getLoopPref: function(pref) {
if (pref == "contacts.gravatars.promo") {
if (pref === "contacts.gravatars.promo") {
return true;
} else if (pref == "contacts.gravatars.show") {
} else if (pref === "contacts.gravatars.show") {
return false;
}
return "";
@ -170,9 +170,9 @@ describe("loop.contacts", function() {
it("should not show the gravatars promo box when the 'contacts.gravatars.promo' pref is set", function() {
sandbox.stub(navigator.mozLoop, "getLoopPref", function(pref) {
if (pref == "contacts.gravatars.promo") {
if (pref === "contacts.gravatars.promo") {
return false;
} else if (pref == "contacts.gravatars.show") {
} else if (pref === "contacts.gravatars.show") {
return true;
}
return "";

View File

@ -26,7 +26,7 @@ describe("loop.conversation", function() {
},
setLoopPref: setLoopPrefStub,
getLoopPref: function(prefName) {
if (prefName == "debug.sdk") {
if (prefName === "debug.sdk") {
return false;
}

View File

@ -295,7 +295,7 @@ describe("loop.roomViews", function () {
beforeEach(function() {
sandbox.stub(dispatcher, "dispatch");
fakeMozLoop.getLoopPref = function(prefName) {
if (prefName == "contextInConversations.enabled") {
if (prefName === "contextInConversations.enabled") {
return true;
}
return "test";

View File

@ -14,6 +14,7 @@
<li><a href="desktop-local/">Local tests</a></li>
<li><a href="standalone/">Standalone tests</a></li>
<li><a href="coverage/">Code Coverage</a></li>
<li><a href="../ui/">UI Showcase</a></li>
</ul>
</body>
</html>

View File

@ -5,4 +5,7 @@
// DOMContentLoaded proved to lead to race conditions.
sinon.stub(document, "addEventListener");
console.log("[stubs.js] addEventListener stubbed to prevent race conditions");
console.log("[head.js] addEventListener stubbed to prevent race conditions");
document.body.appendChild(document.createElement("div")).id = "fixtures";
console.log("[head.js] div#fixtures added to attach DOM elements");

View File

@ -16,7 +16,7 @@ module.exports = function(config) {
"content/shared/libs/lodash-3.9.3.js",
"content/shared/libs/backbone-1.2.1.js",
"test/shared/vendor/*.js",
"test/karma/stubs.js", // Stub out DOM event listener due to races.
"test/karma/head.js", // Stub out DOM event listener due to races.
"content/shared/js/utils.js",
"content/shared/js/models.js",
"content/shared/js/mixins.js",

View File

@ -17,6 +17,7 @@ module.exports = function(config) {
"content/shared/libs/react-0.12.2.js",
"content/shared/libs/sdk.js",
"test/shared/vendor/*.js",
"test/karma/head.js", // Add test fixture container
"content/shared/js/utils.js",
"content/shared/js/store.js",
"content/shared/js/models.js",

View File

@ -10,6 +10,7 @@ describe("loop.shared.views.TextChatView", function () {
var TestUtils = React.addons.TestUtils;
var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
var fixtures = document.querySelector("#fixtures");
var dispatcher, fakeSdkDriver, sandbox, store, fakeClock;
@ -35,6 +36,7 @@ describe("loop.shared.views.TextChatView", function () {
afterEach(function() {
sandbox.restore();
React.unmountComponentAtNode(fixtures);
});
describe("TextChatEntriesView", function() {
@ -52,6 +54,18 @@ describe("loop.shared.views.TextChatView", function () {
_.extend(basicProps, extraProps)));
}
function mountAsRealComponent(extraProps, container) {
var basicProps = {
dispatcher: dispatcher,
messageList: [],
useDesktopPaths: false
};
return React.render(
React.createElement(loop.shared.views.chat.TextChatEntriesView,
_.extend(basicProps, extraProps)), container);
}
beforeEach(function() {
store.setStoreState({ textChatEnabled: true });
});
@ -208,6 +222,115 @@ describe("loop.shared.views.TextChatView", function () {
expect(node.querySelectorAll(".text-chat-entry-timestamp").length)
.to.eql(1);
});
describe("Scrolling", function() {
beforeEach(function() {
sandbox.stub(window, "requestAnimationFrame", function(callback) {
callback();
});
// We're using scrolling, so we need to mount as a real one.
view = mountAsRealComponent({}, fixtures);
sandbox.stub(view, "play");
// We need some basic styling to ensure scrolling.
view.getDOMNode().style.overflow = "scroll";
view.getDOMNode().style["max-height"] = "4ch";
});
it("should scroll when a text message is added", function() {
var messageList = [{
type: CHAT_MESSAGE_TYPES.RECEIVED,
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!",
receivedTimestamp: "2015-06-25T17:53:55.357Z"
}];
view.setProps({ messageList: messageList });
node = view.getDOMNode();
expect(node.scrollTop).eql(node.scrollHeight - node.clientHeight);
});
it("should not scroll when a context tile is added", function() {
var messageList = [{
type: CHAT_MESSAGE_TYPES.SPECIAL,
contentType: CHAT_CONTENT_TYPES.CONTEXT,
message: "Awesome!",
extraData: {
location: "http://invalid.com"
}
}];
view.setProps({ messageList: messageList });
node = view.getDOMNode();
expect(node.scrollTop).eql(0);
});
it("should scroll when a message is received after a context tile", function() {
// The context tile.
var messageList = [{
type: CHAT_MESSAGE_TYPES.SPECIAL,
contentType: CHAT_CONTENT_TYPES.CONTEXT,
message: "Awesome!",
extraData: {
location: "http://invalid.com"
}
}];
view.setProps({ messageList: messageList });
// Now add a message. Don't use the same list as this is a shared object,
// that messes with React.
var messageList1 = [
messageList[0], {
type: CHAT_MESSAGE_TYPES.RECEIVED,
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!",
receivedTimestamp: "2015-06-25T17:53:55.357Z"
}
];
view.setProps({ messageList: messageList1 });
node = view.getDOMNode();
expect(node.scrollTop).eql(node.scrollHeight - node.clientHeight);
});
it("should not scroll when receiving a message and the scroll is not at the bottom", function() {
node = view.getDOMNode();
var messageList = [{
type: CHAT_MESSAGE_TYPES.RECEIVED,
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!",
receivedTimestamp: "2015-06-25T17:53:55.357Z"
}];
view.setProps({ messageList: messageList });
node.scrollTop = 0;
// Don't use the same list as this is a shared object, that messes with React.
var messageList1 = [
messageList[0], {
type: CHAT_MESSAGE_TYPES.RECEIVED,
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!",
receivedTimestamp: "2015-06-25T17:53:55.357Z"
}
];
view.setProps({ messageList: messageList1 });
expect(node.scrollTop).eql(0);
});
});
});
describe("TextChatEntry", function() {
@ -285,6 +408,10 @@ describe("loop.shared.views.TextChatView", function () {
// Fake server to catch all XHR requests.
fakeServer = sinon.fakeServer.create();
store.setStoreState({ textChatEnabled: true });
sandbox.stub(navigator.mozL10n, "get", function(string) {
return string;
});
});
afterEach(function() {
@ -482,5 +609,34 @@ describe("loop.shared.views.TextChatView", function () {
sinon.assert.notCalled(dispatcher.dispatch);
});
it("should show a placeholder when no messages have been sent", function() {
view = mountTestComponent();
store.receivedTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Foo",
sentTimestamp: "1970-01-01T00:03:00.000Z",
receivedTimestamp: "1970-01-01T00:03:00.000Z"
});
var textBox = view.getDOMNode().querySelector(".text-chat-box input");
expect(textBox.placeholder).contain("placeholder");
});
it("should not show a placeholder when messages have been sent", function() {
view = mountTestComponent();
store.sendTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Foo",
sentTimestamp: "2015-06-25T17:53:55.357Z"
});
var textBox = view.getDOMNode().querySelector(".text-chat-box input");
expect(textBox.placeholder).not.contain("placeholder");
});
});
});

View File

@ -850,6 +850,28 @@ describe("loop.shared.views", function() {
React.createElement(sharedViews.ContextUrlView, props));
}
it("should set a clicks-allowed class if clicks are allowed", function() {
view = mountTestComponent({
allowClick: true,
url: "http://wonderful.invalid"
});
var wrapper = view.getDOMNode().querySelector(".context-wrapper");
expect(wrapper.classList.contains("clicks-allowed")).eql(true);
});
it("should not set a clicks-allowed class if clicks are not allowed", function() {
view = mountTestComponent({
allowClick: false,
url: "http://wonderful.invalid"
});
var wrapper = view.getDOMNode().querySelector(".context-wrapper");
expect(wrapper.classList.contains("clicks-allowed")).eql(false);
});
it("should display nothing if the url is invalid", function() {
view = mountTestComponent({
url: "fjrTykyw"
@ -900,7 +922,7 @@ describe("loop.shared.views", function() {
url: "http://wonderful.invalid"
});
expect(view.getDOMNode().querySelector(".context-url").href)
expect(view.getDOMNode().querySelector(".context-wrapper").href)
.eql("http://wonderful.invalid/");
});
@ -910,7 +932,7 @@ describe("loop.shared.views", function() {
url: "http://wonderful.invalid"
});
var linkNode = view.getDOMNode().querySelector(".context-url");
var linkNode = view.getDOMNode().querySelector(".context-wrapper");
TestUtils.Simulate.click(linkNode);
@ -920,6 +942,19 @@ describe("loop.shared.views", function() {
linkInfo: "Shared URL"
}));
});
it("should not dispatch an action if clicks are not allowed", function() {
view = mountTestComponent({
allowClick: false,
url: "http://wonderful.invalid"
});
var linkNode = view.getDOMNode().querySelector(".context-wrapper");
TestUtils.Simulate.click(linkNode);
sinon.assert.notCalled(dispatcher.dispatch);
});
});
describe("MediaView", function() {

View File

@ -84,8 +84,8 @@
});
describe("Unexpected Warnings Check", function() {
it("should long only the warnings we expect", function() {
chai.expect(caughtWarnings.length).to.eql(10);
it("should log only the warnings we expect", function() {
chai.expect(caughtWarnings.length).to.eql(0);
});
});

View File

@ -195,5 +195,22 @@ describe("loop.store.StandaloneMetricsStore", function() {
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.audioMute,
"mute");
});
describe("Event listeners", function() {
it("should call windowUnload when action is dispatched", function() {
sandbox.stub(store, "windowUnload");
dispatcher.dispatch(new sharedActions.WindowUnload());
sinon.assert.calledOnce(store.windowUnload);
});
it("should stop listening to activeRoomStore", function() {
var stopListeningStub = sandbox.stub(store, "stopListening");
store.windowUnload();
sinon.assert.calledOnce(stopListeningStub);
sinon.assert.calledWithExactly(stopListeningStub, store.activeRoomStore);
});
});
});
});

View File

@ -14,6 +14,7 @@ describe("loop.standaloneRoomViews", function() {
var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var fixtures = document.querySelector("#fixtures");
var sandbox, dispatcher, activeRoomStore, dispatch;
var fakeWindow;
@ -60,6 +61,7 @@ describe("loop.standaloneRoomViews", function() {
afterEach(function() {
loop.shared.mixins.setRootObject(window);
sandbox.restore();
React.unmountComponentAtNode(fixtures);
});
describe("StandaloneRoomHeader", function() {
@ -84,6 +86,39 @@ describe("loop.standaloneRoomViews", function() {
});
});
describe("StandaloneRoomInfoArea in fixture", function() {
it("should dispatch a RecordClick action when the tile is clicked", function(done) {
// Point the iframe to a page that will auto-"click"
loop.config.tilesIframeUrl = "data:text/html,<script>parent.postMessage('tile-click', '*');</script>";
// Render the iframe into the fixture to cause it to load
React.render(
React.createElement(
loop.standaloneRoomViews.StandaloneRoomInfoArea, {
activeRoomStore: activeRoomStore,
dispatcher: dispatcher,
isFirefox: true,
joinRoom: sandbox.stub(),
roomState: ROOM_STATES.JOINED,
roomUsed: false
}), fixtures);
// Wait for the iframe to load and trigger a message that should also
// cause the RecordClick action
window.addEventListener("message", function onMessage() {
window.removeEventListener("message", onMessage);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.RecordClick({
linkInfo: "Tiles iframe click"
}));
done();
});
});
});
describe("StandaloneRoomView", function() {
function mountTestComponent() {
return TestUtils.renderIntoDocument(
@ -181,20 +216,6 @@ describe("loop.standaloneRoomViews", function() {
.not.eql(null);
});
it("should display a waiting room message and tile iframe on JOINED",
function() {
var DUMMY_TILE_URL = "http://tile/";
loop.config.tilesIframeUrl = DUMMY_TILE_URL;
activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
expect(view.getDOMNode().querySelector(".room-waiting-area"))
.not.eql(null);
var tile = view.getDOMNode().querySelector(".room-waiting-tile");
expect(tile).not.eql(null);
expect(tile.src).eql(DUMMY_TILE_URL);
});
it("should display an empty room message on SESSION_CONNECTED",
function() {
activeRoomStore.setStoreState({roomState: ROOM_STATES.SESSION_CONNECTED});
@ -212,6 +233,33 @@ describe("loop.standaloneRoomViews", function() {
});
});
describe("Empty room tile offer", function() {
it("should display a waiting room message and tile iframe on JOINED", function() {
var DUMMY_TILE_URL = "http://tile/";
loop.config.tilesIframeUrl = DUMMY_TILE_URL;
activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
expect(view.getDOMNode().querySelector(".room-waiting-area")).not.eql(null);
var tile = view.getDOMNode().querySelector(".room-waiting-tile");
expect(tile).not.eql(null);
expect(tile.src).eql(DUMMY_TILE_URL);
});
it("should dispatch a RecordClick action when the tile support link is clicked", function() {
activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
TestUtils.Simulate.click(view.getDOMNode().querySelector(".room-waiting-area a"));
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.RecordClick({
linkInfo: "Tiles support link click"
}));
});
});
describe("Prompt media message", function() {
it("should display a prompt for user media on MEDIA_WAIT",
function() {

View File

@ -17,6 +17,7 @@ describe("loop.webapp", function() {
stubGetPermsAndCacheMedia,
fakeAudioXHR,
dispatcher,
mozL10nGet,
WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
beforeEach(function() {
@ -27,6 +28,10 @@ describe("loop.webapp", function() {
stubGetPermsAndCacheMedia = sandbox.stub(
loop.standaloneMedia._MultiplexGum.prototype, "getPermsAndCacheMedia");
mozL10nGet = sandbox.stub(navigator.mozL10n, "get", function(x) {
return "translated:" + x;
});
fakeAudioXHR = {
open: sinon.spy(),
send: function() {},
@ -114,6 +119,7 @@ describe("loop.webapp", function() {
ocView = mountTestComponent({
client: client,
conversation: conversation,
isFirefox: true,
notifications: notifications,
sdk: {
on: sandbox.stub()
@ -677,8 +683,7 @@ describe("loop.webapp", function() {
it("should display the UnsupportedDeviceView for `unsupportedDevice` window type",
function() {
standaloneAppStore.setStoreState({windowType: "unsupportedDevice"});
standaloneAppStore.setStoreState({windowType: "unsupportedDevice", unsupportedPlatform: "ios"});
var webappRootView = mountTestComponent();
TestUtils.findRenderedComponentWithType(webappRootView,
@ -687,7 +692,7 @@ describe("loop.webapp", function() {
it("should display the UnsupportedBrowserView for `unsupportedBrowser` window type",
function() {
standaloneAppStore.setStoreState({windowType: "unsupportedBrowser"});
standaloneAppStore.setStoreState({windowType: "unsupportedBrowser", isFirefox: false});
var webappRootView = mountTestComponent();
@ -697,7 +702,7 @@ describe("loop.webapp", function() {
it("should display the OutgoingConversationView for `outgoing` window type",
function() {
standaloneAppStore.setStoreState({windowType: "outgoing"});
standaloneAppStore.setStoreState({windowType: "outgoing", isFirefox: true});
var webappRootView = mountTestComponent();
@ -707,7 +712,7 @@ describe("loop.webapp", function() {
it("should display the StandaloneRoomView for `room` window type",
function() {
standaloneAppStore.setStoreState({windowType: "room"});
standaloneAppStore.setStoreState({windowType: "room", isFirefox: true});
var webappRootView = mountTestComponent();
@ -716,7 +721,7 @@ describe("loop.webapp", function() {
});
it("should display the HomeView for `home` window type", function() {
standaloneAppStore.setStoreState({windowType: "home"});
standaloneAppStore.setStoreState({windowType: "home", isFirefox: true});
var webappRootView = mountTestComponent();
@ -1092,17 +1097,17 @@ describe("loop.webapp", function() {
isFirefox: true
}));
expect(comp.getDOMNode().querySelectorAll("h3").length).eql(0);
expect(comp.getDOMNode()).eql(null);
});
it("should render when not using Firefox", function() {
var comp = TestUtils.renderIntoDocument(
React.createElement(
loop.webapp.PromoteFirefoxView, {
React.createElement(loop.webapp.PromoteFirefoxView, {
isFirefox: false
}));
expect(comp.getDOMNode().querySelectorAll("h3").length).eql(1);
sinon.assert.calledWith(mozL10nGet, "promote_firefox_hello_heading");
sinon.assert.calledWith(mozL10nGet, "get_firefox_button");
});
});
});

View File

@ -387,7 +387,7 @@
roomOwner: "fake",
roomUrl: "http://showcase",
urls: [{
description: "A wonderful page!",
description: "1171925 - Clicking the title or favicon for context (in the conversation/standalone windows) should appear to be part of the link and open the webpage",
location: "http://wonderful.invalid"
// use the fallback thumbnail
}]
@ -1541,7 +1541,7 @@
// This simulates the mocha layout for errors which means we can run
// this alongside our other unit tests but use the same harness.
var expectedWarningsCount = 18;
var expectedWarningsCount = 16;
var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
if (uncaughtError || warningsMismatch) {
$("#results").append("<div class='failures'><em>" +

View File

@ -387,7 +387,7 @@
roomOwner: "fake",
roomUrl: "http://showcase",
urls: [{
description: "A wonderful page!",
description: "1171925 - Clicking the title or favicon for context (in the conversation/standalone windows) should appear to be part of the link and open the webpage",
location: "http://wonderful.invalid"
// use the fallback thumbnail
}]
@ -1541,7 +1541,7 @@
// This simulates the mocha layout for errors which means we can run
// this alongside our other unit tests but use the same harness.
var expectedWarningsCount = 18;
var expectedWarningsCount = 16;
var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
if (uncaughtError || warningsMismatch) {
$("#results").append("<div class='failures'><em>" +

View File

@ -113,7 +113,10 @@ function screenshot() {
}
let WebProgressListener = {
onLocationChange: function onLocationChange(aWebProgress) {
onLocationChange(webProgress, request, URI, flags) {
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
return;
}
makeScrollbarsFloating();
},
QueryInterface: function QueryInterface(aIID) {

View File

@ -33,7 +33,7 @@
background-position: -48px center;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#eyedropper-button {
background-image: url("chrome://browser/skin/devtools/command-eyedropper@2x.png");
}

View File

@ -42,7 +42,7 @@
position: relative;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.breakpoint {
background-image: url("chrome://browser/skin/devtools/editor-breakpoint@2x.png");
}
@ -56,7 +56,7 @@
background-image: url("chrome://browser/skin/devtools/editor-debug-location.png");
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.debugLocation {
background-image: url("chrome://browser/skin/devtools/editor-debug-location@2x.png");
}
@ -68,7 +68,7 @@
url("chrome://browser/skin/devtools/editor-breakpoint.png");
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.breakpoint.debugLocation {
background-image:
url("chrome://browser/skin/devtools/editor-debug-location@2x.png"),

View File

@ -65,7 +65,7 @@ const SEARCH_DELAY = 200;
// The number of lines that are displayed in the console output by default, for
// each category. The user can change this number by adjusting the hidden
// "devtools.hud.loglimit.{network,cssparser,exception,console}" preferences.
const DEFAULT_LOG_LIMIT = 200;
const DEFAULT_LOG_LIMIT = 1000;
// The various categories of messages. We start numbering at zero so we can
// use these as indexes into the MESSAGE_PREFERENCE_KEYS matrix below.

View File

@ -1189,12 +1189,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
}
#urlbar-reload-button:not([disabled]):hover {
background-image: radial-gradient(circle closest-side, hsla(200,100%,70%,.2), transparent);
-moz-image-region: rect(14px, 14px, 28px, 0);
}
#urlbar-reload-button:not([disabled]):hover:active {
background-image: radial-gradient(circle closest-side, hsla(200,100%,60%,.1), transparent);
-moz-image-region: rect(28px, 14px, 42px, 0);
}
@ -1207,12 +1205,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
}
#urlbar-go-button:hover {
background-image: radial-gradient(circle closest-side, hsla(110,70%,50%,.2), transparent);
-moz-image-region: rect(14px, 42px, 28px, 28px);
}
#urlbar-go-button:hover:active {
background-image: radial-gradient(circle closest-side, hsla(110,70%,50%,.1), transparent);
-moz-image-region: rect(28px, 42px, 42px, 28px);
}
@ -1225,12 +1221,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
}
#urlbar-stop-button:not([disabled]):hover {
background-image: radial-gradient(circle closest-side, hsla(5,100%,75%,.3), transparent);
-moz-image-region: rect(14px, 28px, 28px, 14px);
}
#urlbar-stop-button:hover:active {
background-image: radial-gradient(circle closest-side, hsla(5,100%,75%,.1), transparent);
-moz-image-region: rect(28px, 28px, 42px, 14px);
}

View File

@ -1716,7 +1716,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
.urlbar-history-dropmarker[open="true"],
.urlbar-history-dropmarker:hover:active {
-moz-image-region: var(--urlbar-dropmarker-active-region);
background-image: radial-gradient(circle closest-side, hsla(205,100%,70%,.3), transparent);
}
@media (min-resolution: 2dppx) {
@ -1743,11 +1742,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
padding: 0 3px;
}
.urlbar-icon[open="true"],
.urlbar-icon:hover:active {
background-image: radial-gradient(circle closest-side, hsla(205,100%,70%,.3), transparent);
}
#urlbar-search-footer {
border-top: 1px solid hsla(210,4%,10%,.14);
background-color: hsla(210,4%,10%,.07);
@ -1920,10 +1914,6 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
list-style-image: url("chrome://browser/skin/reload-stop-go.png");
}
#urlbar > toolbarbutton:not([disabled]):hover:active {
background-image: radial-gradient(circle closest-side, hsla(205,100%,70%,.3), transparent);
}
#urlbar-go-button {
-moz-image-region: rect(0, 42px, 14px, 28px);
}

View File

@ -99,7 +99,7 @@ body {
background-image: url("debugger-play.png");
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#element-picker::before {
background-image: url("chrome://browser/skin/devtools/command-pick@2x.png");
background-size: 64px;
@ -362,7 +362,7 @@ body {
background-image: url(rewind.png);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.timeline .toggle::before {
background-image: url(debugger-pause@2x.png);
}

View File

@ -145,7 +145,7 @@
list-style-image: url(debugger-step-out.png);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#resume {
list-style-image: url(debugger-play@2x.png);
-moz-image-region: rect(0px,64px,32px,32px);
@ -250,7 +250,7 @@
background-size: 12px;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.selected .call-item-gutter {
background-image: url("editor-debug-location@2x.png");
}

View File

@ -82,7 +82,7 @@
-moz-image-region: rect(0px, 64px, 16px, 48px);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#developer-toolbar-toolbox-button {
list-style-image: url("chrome://browser/skin/devtools/toggle-tools@2x.png");
-moz-image-region: rect(0px, 32px, 32px, 0px);
@ -111,7 +111,7 @@
opacity: 0.6;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#developer-toolbar-closebutton {
list-style-image: url("chrome://browser/skin/devtools/close@2x.png");
}
@ -199,7 +199,7 @@ html|*#gcli-output-frame {
background-position: -16px center;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.gclitoolbar-input-node::before {
background-image: url("chrome://browser/skin/devtools/commandline-icon@2x.png");
}

View File

@ -61,7 +61,7 @@ body {
background-size: 5px 8px;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.property-value, .other-property-value {
background-image: url(arrow-e@2x.png);
}

View File

@ -334,7 +334,7 @@ div.CodeMirror span.eval-text {
background-position: -42px 0;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.theme-twisty, .theme-checkbox {
background-image: url("chrome://browser/skin/devtools/controls@2x.png");
}
@ -372,7 +372,7 @@ div.CodeMirror span.eval-text {
margin-left: -4px;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.theme-tooltip-panel .panel-arrow[side="top"],
.theme-tooltip-panel .panel-arrow[side="bottom"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-vertical-dark@2x.png");

View File

@ -66,7 +66,7 @@
list-style-image: url(debugger-blackbox.png);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#black-box {
list-style-image: url(debugger-blackbox@2x.png);
}
@ -76,7 +76,7 @@
list-style-image: url(debugger-prettyprint.png);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#pretty-print {
list-style-image: url(debugger-prettyprint@2x.png);
}
@ -86,7 +86,7 @@
list-style-image: url(debugger-toggleBreakpoints.png);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#toggle-breakpoints {
list-style-image: url(debugger-toggleBreakpoints@2x.png);
}
@ -104,7 +104,7 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#sources-toolbar .devtools-toolbarbutton:not([label]) {
-moz-image-region: rect(0px,32px,32px,0px);
}
@ -138,7 +138,7 @@
-moz-margin-end: 5px;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#black-boxed-message-button > .button-box > .button-icon {
background-image: url(debugger-blackbox@2x.png);
}
@ -226,7 +226,7 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#trace {
list-style-image: url(tracer-icon@2x.png);
-moz-image-region: rect(0px,32px,32px,0px);
@ -328,7 +328,7 @@
margin: 2px;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.dbg-expression-arrow {
background-image: url(commandline-icon@2x.png);
}
@ -564,7 +564,7 @@
list-style-image: url(debugger-play.png);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#resume {
list-style-image: url(debugger-pause@2x.png);
-moz-image-region: rect(0px,32px,32px,0px);
@ -596,7 +596,7 @@
list-style-image: url(debugger-step-out.png);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#step-over {
list-style-image: url(debugger-step-over@2x.png);
}
@ -626,7 +626,7 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#instruments-pane-toggle {
list-style-image: url(debugger-collapse@2x.png);
-moz-image-region: rect(0px,32px,32px,0px);

View File

@ -60,7 +60,7 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#inspector-pane-toggle {
list-style-image: url(debugger-collapse@2x.png);
-moz-image-region: rect(0px,32px,32px,0px);

View File

@ -343,7 +343,7 @@ div.CodeMirror span.eval-text {
background-position: -14px 0;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.theme-twisty, .theme-checkbox {
background-image: url("chrome://browser/skin/devtools/controls@2x.png");
}
@ -381,7 +381,7 @@ div.CodeMirror span.eval-text {
margin-left: -4px;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.theme-tooltip-panel .panel-arrow[side="top"],
.theme-tooltip-panel .panel-arrow[side="bottom"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-vertical-light@2x.png");

View File

@ -416,7 +416,7 @@ box.requests-menu-status[code^="5"] {
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#details-pane-toggle {
list-style-image: url("chrome://browser/skin/devtools/debugger-collapse@2x.png");
-moz-image-region: rect(0px,32px,32px,0px);
@ -556,7 +556,7 @@ box.requests-menu-status[code^="5"] {
height: 12px;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.security-warning-icon {
background-image: url(alerticon-warning@2x.png);
}

View File

@ -155,7 +155,7 @@
list-style-image: url("chrome://browser/skin/devtools/responsiveui-rotate.png");
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.devtools-responsiveui-close {
list-style-image: url("chrome://browser/skin/devtools/close@2x.png");
}
@ -174,7 +174,7 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.devtools-responsiveui-touch {
list-style-image: url("chrome://browser/skin/devtools/responsiveui-touch@2x.png");
-moz-image-region: rect(0px,32px,32px,0px);
@ -189,7 +189,7 @@
list-style-image: url("chrome://browser/skin/devtools/responsiveui-screenshot.png");
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.devtools-responsiveui-screenshot {
list-style-image: url("chrome://browser/skin/devtools/responsiveui-screenshot@2x.png");
}
@ -324,7 +324,7 @@
border-bottom-left-radius: 12px;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.devtools-responsiveui-resizebarV {
background-image: url("chrome://browser/skin/devtools/responsive-vertical-resizer@2x.png");
}

View File

@ -111,7 +111,7 @@
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.ruleview-warning {
background-image: url(alerticon-warning@2x.png);
}
@ -198,7 +198,7 @@
background-size: 1em;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.ruleview-bezierswatch {
background: url("chrome://browser/skin/devtools/cubic-bezier-swatch@2x.png");
background-size: 1em;

View File

@ -60,7 +60,7 @@
border: 0;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.side-menu-widget-item-checkbox .checkbox-check {
background-image: url(itemToggle@2x.png);
}

View File

@ -115,7 +115,7 @@
height: 40px;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.stylesheet-enabled {
background-image: url(itemToggle@2x.png);
}

View File

@ -317,7 +317,7 @@
opacity: 0.5;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.devtools-button::before {
background-size: 32px;
}
@ -438,7 +438,7 @@
-moz-image-region: rect(0, 32px, 16px, 16px);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.theme-dark .devtools-searchinput {
background-image: url(magnifying-glass@2x.png);
}
@ -768,7 +768,7 @@
background-image: url("chrome://browser/skin/devtools/command-rulers.png");
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#command-button-paintflashing > image {
background-image: url("chrome://browser/skin/devtools/command-paintflashing@2x.png");
}

View File

@ -187,7 +187,7 @@ text {
-moz-box-flex: 1;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#inspector-pane-toggle {
list-style-image: url(debugger-collapse@2x.png);
-moz-image-region: rect(0px,32px,32px,0px);

View File

@ -360,7 +360,7 @@ a {
background-size: 16px 16px;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.jsterm-input-node {
background-image: -moz-image-rect(url('chrome://browser/skin/devtools/commandline-icon@2x.png'), 0, 64, 32, 32);
}

View File

@ -111,7 +111,7 @@
padding: 0;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.scrollbutton-up > .toolbarbutton-icon,
.scrollbutton-down > .toolbarbutton-icon {
background-image: url("breadcrumbs-scrollbutton@2x.png");
@ -641,7 +641,7 @@
height: 16px;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.variable-or-property-non-writable-icon {
background-image: url("chrome://browser/skin/devtools/vview-lock@2x.png");
}
@ -741,7 +741,7 @@
height: 16px;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.variables-view-delete {
background-image: url("chrome://browser/skin/devtools/vview-delete@2x.png");
}
@ -767,7 +767,7 @@
cursor: pointer;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.variables-view-edit {
background-image: url("chrome://browser/skin/devtools/vview-edit@2x.png");
}
@ -793,7 +793,7 @@
cursor: pointer;
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.variables-view-open-inspector {
background-image: url("chrome://browser/skin/devtools/vview-open-inspector@2x.png");
}
@ -1424,7 +1424,7 @@
}
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
.tree-widget-item:before {
background-image: url("chrome://browser/skin/devtools/controls@2x.png");
}

View File

@ -95,7 +95,6 @@
height: 16px;
margin-top: -12px;
-moz-margin-start: -16px;
display: none;
position: relative;
}
@ -103,35 +102,29 @@
list-style-image: url("chrome://browser/skin/tabbrowser/crashed.svg");
}
.tab-icon-overlay[crashed],
.tab-icon-overlay[soundplaying][pinned],
.tab-icon-overlay[muted][pinned]:not([crashed]) {
display: -moz-box;
}
.tab-icon-overlay[soundplaying][pinned],
.tab-icon-overlay[muted][pinned]:not([crashed]) {
.tab-icon-overlay[soundplaying],
.tab-icon-overlay[muted]:not([crashed]) {
border-radius: 8px;
}
.tab-icon-overlay[soundplaying][pinned]:hover,
.tab-icon-overlay[muted][pinned]:not([crashed]):hover {
.tab-icon-overlay[soundplaying]:hover,
.tab-icon-overlay[muted]:not([crashed]):hover {
background-color: white;
}
.tab-icon-overlay[soundplaying][pinned] {
.tab-icon-overlay[soundplaying] {
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio");
}
.tab-icon-overlay[muted][pinned]:not([crashed]) {
.tab-icon-overlay[muted]:not([crashed]) {
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted");
}
#TabsToolbar[brighttext] .tab-icon-overlay[soundplaying][pinned]:not(:hover) {
#TabsToolbar[brighttext] .tab-icon-overlay[soundplaying]:not(:hover) {
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white");
}
#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned]:not([crashed]):not(:hover) {
#TabsToolbar[brighttext] .tab-icon-overlay[muted]:not([crashed]):not(:hover) {
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white-muted");
}
@ -161,11 +154,6 @@
padding: 0;
}
.tab-icon-sound:not([soundplaying]):not([muted]),
.tab-icon-sound[pinned] {
display: none;
}
.tab-icon-sound[soundplaying] {
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-backgroundTab");
}

View File

@ -1383,15 +1383,6 @@ html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
padding: 2px 2px;
}
.urlbar-icon:hover {
background-image: radial-gradient(circle closest-side, hsla(200,100%,70%,.3), transparent);
}
.urlbar-icon[open="true"],
.urlbar-icon:hover:active {
background-image: radial-gradient(circle closest-side, hsla(200,100%,70%,.1), transparent);
}
#urlbar-search-footer {
border-top: 1px solid hsla(210,4%,10%,.14);
background-color: hsla(210,4%,10%,.07);
@ -1478,13 +1469,11 @@ html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
}
.urlbar-history-dropmarker:hover {
background-image: radial-gradient(circle closest-side, hsla(205,100%,70%,.3), transparent);
-moz-image-region: var(--urlbar-dropmarker-hover-region);
}
.urlbar-history-dropmarker:hover:active,
.urlbar-history-dropmarker[open="true"] {
background-image: radial-gradient(circle closest-side, hsla(205,100%,70%,.1), transparent);
-moz-image-region: var(--urlbar-dropmarker-active-region);
}
@ -1669,12 +1658,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
}
#urlbar-reload-button:not([disabled]):hover {
background-image: radial-gradient(circle closest-side, hsla(200,100%,70%,.2), transparent);
-moz-image-region: rect(14px, 14px, 28px, 0);
}
#urlbar-reload-button:not([disabled]):hover:active {
background-image: radial-gradient(circle closest-side, hsla(200,100%,60%,.1), transparent);
-moz-image-region: rect(28px, 14px, 42px, 0);
}
@ -1687,12 +1674,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
}
#urlbar-go-button:hover {
background-image: radial-gradient(circle closest-side, hsla(110,70%,50%,.2), transparent);
-moz-image-region: rect(14px, 42px, 28px, 28px);
}
#urlbar-go-button:hover:active {
background-image: radial-gradient(circle closest-side, hsla(110,70%,50%,.1), transparent);
-moz-image-region: rect(28px, 42px, 42px, 28px);
}
@ -1705,12 +1690,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
}
#urlbar-stop-button:not([disabled]):hover {
background-image: radial-gradient(circle closest-side, hsla(5,100%,75%,.3), transparent);
-moz-image-region: rect(14px, 28px, 28px, 14px);
}
#urlbar-stop-button:hover:active {
background-image: radial-gradient(circle closest-side, hsla(5,100%,75%,.1), transparent);
-moz-image-region: rect(28px, 28px, 42px, 14px);
}

View File

@ -6,8 +6,11 @@
package org.mozilla.gecko;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.JSONUtils;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.WebActivityMapper;
import org.json.JSONArray;
@ -26,16 +29,21 @@ import java.net.URLEncoder;
import java.util.Arrays;
import java.util.List;
public final class IntentHelper implements GeckoEventListener {
public final class IntentHelper implements GeckoEventListener,
NativeEventListener {
private static final String LOGTAG = "GeckoIntentHelper";
private static final String[] EVENTS = {
"Intent:GetHandlers",
"Intent:Open",
"Intent:OpenForResult",
"Intent:OpenNoHandler",
"WebActivity:Open"
};
private static final String[] NATIVE_EVENTS = {
"Intent:OpenNoHandler",
};
// via http://developer.android.com/distribute/tools/promote/linking.html
private static String MARKET_INTENT_URI_PACKAGE_PREFIX = "market://details?id=";
private static String EXTRA_BROWSER_FALLBACK_URL = "browser_fallback_url";
@ -49,7 +57,8 @@ public final class IntentHelper implements GeckoEventListener {
private IntentHelper(Activity activity) {
this.activity = activity;
EventDispatcher.getInstance().registerGeckoThreadListener(this, EVENTS);
EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this, EVENTS);
EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this, NATIVE_EVENTS);
}
public static IntentHelper init(Activity activity) {
@ -64,11 +73,19 @@ public final class IntentHelper implements GeckoEventListener {
public static void destroy() {
if (instance != null) {
EventDispatcher.getInstance().unregisterGeckoThreadListener(instance, EVENTS);
EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) instance, EVENTS);
EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) instance, NATIVE_EVENTS);
instance = null;
}
}
@Override
public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) {
if (event.equals("Intent:OpenNoHandler")) {
openNoHandler(message, callback);
}
}
@Override
public void handleMessage(String event, JSONObject message) {
try {
@ -78,8 +95,6 @@ public final class IntentHelper implements GeckoEventListener {
open(message);
} else if (event.equals("Intent:OpenForResult")) {
openForResult(message);
} else if (event.equals("Intent:OpenNoHandler")) {
openNoHandler(message);
} else if (event.equals("WebActivity:Open")) {
openWebActivity(message);
}
@ -132,13 +147,17 @@ public final class IntentHelper implements GeckoEventListener {
* and we can bring the user directly to the application page in an app market. If a package is
* not specified and there is a fallback url in the intent extras, we open that url. If neither
* is present, we alert the user that we were unable to open the link.
*
* @param msg A message with the uri with no handlers as the value for the "uri" key
* @param callback A callback that will be called with success & no params if Java loads a page, or with error and
* the uri to load if Java does not load a page
*/
private void openNoHandler(final JSONObject msg) {
final String uri = msg.optString("uri");
private void openNoHandler(final NativeJSObject msg, final EventCallback callback) {
final String uri = msg.getString("uri");
if (TextUtils.isEmpty(uri)) {
openUnknownProtocolErrorPage("");
Log.w(LOGTAG, "Received empty URL - loading about:neterror");
callback.sendError(getUnknownProtocolErrorPageUri(""));
return;
}
@ -147,14 +166,16 @@ public final class IntentHelper implements GeckoEventListener {
// TODO (bug 1173626): This will not handle android-app uris on non 5.1 devices.
intent = Intent.parseUri(uri, 0);
} catch (final URISyntaxException e) {
String errorUri;
try {
openUnknownProtocolErrorPage(URLEncoder.encode(uri, "UTF-8"));
errorUri = getUnknownProtocolErrorPageUri(URLEncoder.encode(uri, "UTF-8"));
} catch (final UnsupportedEncodingException encodingE) {
openUnknownProtocolErrorPage("");
errorUri = getUnknownProtocolErrorPageUri("");
}
// Don't log the exception to prevent leaking URIs.
Log.w(LOGTAG, "Unable to parse Intent URI - loading about:neterror");
callback.sendError(errorUri);
return;
}
@ -174,28 +195,31 @@ public final class IntentHelper implements GeckoEventListener {
final Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(marketUri));
marketIntent.addCategory(Intent.CATEGORY_BROWSABLE);
marketIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// (Bug 1192436) We don't know if marketIntent matches any Activities (e.g. non-Play
// Store devices). If it doesn't, clicking the link will cause no action to occur.
activity.startActivity(marketIntent);
callback.sendSuccess(null);
} else if (intent.hasExtra(EXTRA_BROWSER_FALLBACK_URL)) {
final String fallbackUrl = intent.getStringExtra(EXTRA_BROWSER_FALLBACK_URL);
Tabs.getInstance().loadUrl(fallbackUrl);
callback.sendError(fallbackUrl);
} else {
openUnknownProtocolErrorPage(intent.getData().toString());
// Don't log the URI to prevent leaking it.
Log.w(LOGTAG, "Unable to open URI, default case - loading about:neterror");
callback.sendError(getUnknownProtocolErrorPageUri(intent.getData().toString()));
}
}
/**
* Opens about:neterror with the unknownProtocolFound text.
* Returns an about:neterror uri with the unknownProtocolFound text as a parameter.
* @param encodedUri The encoded uri. While the page does not open correctly without specifying
* a uri parameter, it happily accepts the empty String so this argument may
* be the empty String.
*/
private void openUnknownProtocolErrorPage(final String encodedUri) {
final String errorUri = UNKNOWN_PROTOCOL_URI_PREFIX + encodedUri;
Tabs.getInstance().loadUrl(errorUri);
private String getUnknownProtocolErrorPageUri(final String encodedUri) {
return UNKNOWN_PROTOCOL_URI_PREFIX + encodedUri;
}
private void openWebActivity(JSONObject message) throws JSONException {

View File

@ -41,7 +41,7 @@ public class RestrictedProfiles {
return configuration;
}
if (isGuestProfile()) {
if (isGuestProfile(context)) {
return new GuestProfileConfiguration();
} else if(isRestrictedProfile(context)) {
return new RestrictedProfileConfiguration(context);
@ -50,8 +50,13 @@ public class RestrictedProfiles {
}
}
private static boolean isGuestProfile() {
return GeckoAppShell.getGeckoInterface().getProfile().inGuestMode();
private static boolean isGuestProfile(Context context) {
GeckoAppShell.GeckoInterface geckoInterface = GeckoAppShell.getGeckoInterface();
if (geckoInterface != null) {
return geckoInterface.getProfile().inGuestMode();
}
return GeckoProfile.get(context).inGuestMode();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)

View File

@ -564,14 +564,10 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!ENTITY identity_connection_insecure "Insecure connection">
<!-- Mixed content notifications in site identity popup -->
<!ENTITY mixed_content_blocked_all "&brandShortName; has blocked insecure elements on this page.">
<!ENTITY mixed_content_blocked_some "&brandShortName; has blocked some insecure elements on this page.">
<!ENTITY mixed_content_display_loaded "This page has some insecure elements.">
<!ENTITY mixed_content_protection_disabled "You have disabled protection from insecure elements.">
<!ENTITY loaded_mixed_content_message "This page is displaying content that isn\'t secure.">
<!ENTITY blocked_mixed_content_message_top "&brandShortName; has blocked content that isn\'t secure.">
<!ENTITY blocked_mixed_content_message_bottom "Most websites will still work properly even when this content is blocked.">
<!ENTITY mixed_content_blocked_all1 "&brandShortName; has blocked insecure content on this page.">
<!ENTITY mixed_content_blocked_some1 "&brandShortName; has blocked some of the insecure content on this page.">
<!ENTITY mixed_content_display_loaded1 "Parts of this page are not secure (such as images).">
<!ENTITY mixed_content_protection_disabled1 "You have disabled protection from insecure content.">
<!-- Tracking content notifications in site identity popup -->
<!ENTITY doorhanger_tracking_title "Tracking protection">

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,69 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical"
android:fillViewport="true">
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/firstrun_min_height"
android:orientation="vertical">
<LinearLayout android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="21"
android:orientation="vertical"
android:gravity="center"
android:padding="10dp">
<ImageView android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:src="@drawable/large_icon"/>
</LinearLayout>
<LinearLayout android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="29"
android:orientation="vertical"
android:gravity="center_horizontal"
android:background="@color/android:white">
<TextView android:layout_width="@dimen/firstrun_content_width"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:paddingTop="30dp"
android:textAppearance="@style/TextAppearance.FirstrunLight.Main"
android:text="@string/firstrun_welcome_message"/>
<TextView android:layout_width="@dimen/firstrun_content_width"
android:layout_height="wrap_content"
android:paddingTop="20dp"
android:paddingBottom="30dp"
android:gravity="center"
android:textAppearance="@style/TextAppearance.FirstrunRegular.Body"
android:text="@string/firstrun_welcome_subtext"/>
<Button android:id="@+id/welcome_account"
style="@style/Widget.Firstrun.Button"
android:background="@drawable/button_background_action_orange_round"
android:layout_gravity="center"
android:text="@string/firstrun_welcome_button_account"/>
<TextView android:id="@+id/welcome_browse"
android:layout_width="@dimen/firstrun_content_width"
android:layout_height="wrap_content"
android:minHeight="92dp"
android:padding="20dp"
android:gravity="center"
android:textAppearance="@style/TextAppearance.FirstrunRegular.Link"
android:text="@string/firstrun_welcome_button_browser"/>
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:id="@+id/trackingprotection_title"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
<TextView android:id="@+id/trackingprotection_enabled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Bold"/>
<TextView android:id="@+id/trackingprotection_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -13,31 +13,29 @@
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="@dimen/firstrun_min_height"
android:background="@color/android:white"
android:gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="21"
android:orientation="vertical"
android:gravity="center"
android:padding="10dp">
<FrameLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/link_blue">
<ImageView android:layout_width="100dp"
android:layout_height="100dp"
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/large_icon"/>
android:adjustViewBounds="true"
android:src="@drawable/firstrun_background_coffee"/>
</LinearLayout>
<LinearLayout android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="29"
android:orientation="vertical"
android:gravity="center_horizontal"
android:background="@color/android:white">
<ImageView android:layout_width="@dimen/firstrun_brand_size"
android:layout_height="@dimen/firstrun_brand_size"
android:src="@drawable/large_icon"
android:layout_gravity="center"/>
</FrameLayout>
<TextView android:layout_width="@dimen/firstrun_content_width"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:gravity="center"
android:paddingTop="30dp"
android:textAppearance="@style/TextAppearance.FirstrunLight.Main"
android:text="@string/firstrun_welcome_message"/>
@ -64,5 +62,4 @@
android:textAppearance="@style/TextAppearance.FirstrunRegular.Link"
android:text="@string/firstrun_welcome_button_browser"/>
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -24,6 +24,8 @@
<dimen name="browser_toolbar_site_security_padding_vertical">21dp</dimen>
<dimen name="browser_toolbar_site_security_padding_horizontal">8dp</dimen>
<dimen name="firstrun_brand_size">72dp</dimen>
<dimen name="tabs_panel_indicator_width">72dp</dimen>
<dimen name="tabs_panel_button_width">60dp</dimen>
<dimen name="panel_grid_view_column_width">200dp</dimen>

View File

@ -60,6 +60,7 @@
<dimen name="firstrun_content_width">300dp</dimen>
<dimen name="firstrun_min_height">180dp</dimen>
<dimen name="firstrun_brand_size">48dp</dimen>
<dimen name="overlay_prompt_content_width">260dp</dimen>
<dimen name="overlay_prompt_button_width">148dp</dimen>

View File

@ -469,10 +469,10 @@
<string name="identity_connection_secure">&identity_connection_secure;</string>
<string name="identity_connection_insecure">&identity_connection_insecure;</string>
<string name="mixed_content_blocked_all">&mixed_content_blocked_all;</string>
<string name="mixed_content_blocked_some">&mixed_content_blocked_some;</string>
<string name="mixed_content_display_loaded">&mixed_content_display_loaded;</string>
<string name="mixed_content_protection_disabled">&mixed_content_protection_disabled;</string>
<string name="mixed_content_blocked_all">&mixed_content_blocked_all1;</string>
<string name="mixed_content_blocked_some">&mixed_content_blocked_some1;</string>
<string name="mixed_content_display_loaded">&mixed_content_display_loaded1;</string>
<string name="mixed_content_protection_disabled">&mixed_content_protection_disabled1;</string>
<string name="doorhanger_tracking_title">&doorhanger_tracking_title;</string>
<string name="doorhanger_tracking_state_enabled">&doorhanger_tracking_state_enabled;</string>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Some files were not shown because too many files have changed in this diff Show More