Merge m-c to m-i

This commit is contained in:
Phil Ringnalda 2015-07-03 19:15:06 -07:00
commit ac27299b59
118 changed files with 1734 additions and 1420 deletions

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c2438f746a3236398735202c0d79fab28cd019ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="75071a1117605a7ac79c51025caf6ffcfc4dfa95"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c2438f746a3236398735202c0d79fab28cd019ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="75071a1117605a7ac79c51025caf6ffcfc4dfa95"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c2438f746a3236398735202c0d79fab28cd019ae"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="75071a1117605a7ac79c51025caf6ffcfc4dfa95"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c2438f746a3236398735202c0d79fab28cd019ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="75071a1117605a7ac79c51025caf6ffcfc4dfa95"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="49192a4e48d080e44a0d66f059e6897f07cf67f8"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c2438f746a3236398735202c0d79fab28cd019ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="75071a1117605a7ac79c51025caf6ffcfc4dfa95"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c2438f746a3236398735202c0d79fab28cd019ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="75071a1117605a7ac79c51025caf6ffcfc4dfa95"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c2438f746a3236398735202c0d79fab28cd019ae"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="75071a1117605a7ac79c51025caf6ffcfc4dfa95"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c2438f746a3236398735202c0d79fab28cd019ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="75071a1117605a7ac79c51025caf6ffcfc4dfa95"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "c2438f746a3236398735202c0d79fab28cd019ae",
"git_revision": "75071a1117605a7ac79c51025caf6ffcfc4dfa95",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "0312e33fddf5c92fad3933fd16f39075d6e6190f",
"revision": "22c58f4fec82075c7110bbc59ca7f6aad0a232a8",
"repo_path": "integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c2438f746a3236398735202c0d79fab28cd019ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="75071a1117605a7ac79c51025caf6ffcfc4dfa95"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="49192a4e48d080e44a0d66f059e6897f07cf67f8"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c2438f746a3236398735202c0d79fab28cd019ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="75071a1117605a7ac79c51025caf6ffcfc4dfa95"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -313,7 +313,7 @@ let gFxAccounts = {
fxAccounts.getSignedInUser().then(userData => {
// userData may be null here when the user is not signed-in, but that's expected
updateWithUserData(userData);
return fxAccounts.getSignedInUserProfile();
return userData ? fxAccounts.getSignedInUserProfile() : null;
}).then(profile => {
if (!profile) {
return;

View File

@ -318,50 +318,6 @@ let AboutNetErrorListener = {
AboutNetErrorListener.init(this);
// An event listener for custom "WebChannelMessageToChrome" events on pages
addEventListener("WebChannelMessageToChrome", function (e) {
// if target is window then we want the document principal, otherwise fallback to target itself.
let principal = e.target.nodePrincipal ? e.target.nodePrincipal : e.target.document.nodePrincipal;
if (e.detail) {
sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal);
} else {
Cu.reportError("WebChannel message failed. No message detail.");
}
}, true, true);
// Add message listener for "WebChannelMessageToContent" messages from chrome scripts
addMessageListener("WebChannelMessageToContent", function (e) {
if (e.data) {
// e.objects.eventTarget will be defined if sending a response to
// a WebChannelMessageToChrome event. An unsolicited send
// may not have an eventTarget defined, in this case send to the
// main content window.
let eventTarget = e.objects.eventTarget || content;
// if eventTarget is window then we want the document principal,
// otherwise use target itself.
let targetPrincipal = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget.document.nodePrincipal : eventTarget.nodePrincipal;
if (e.principal.subsumes(targetPrincipal)) {
// if eventTarget is a window, use it as the targetWindow, otherwise
// find the window that owns the eventTarget.
let targetWindow = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget : eventTarget.ownerDocument.defaultView;
eventTarget.dispatchEvent(new targetWindow.CustomEvent("WebChannelMessageToContent", {
detail: Cu.cloneInto({
id: e.data.id,
message: e.data.message,
}, targetWindow),
}));
} else {
Cu.reportError("WebChannel message failed. Principal mismatch.");
}
} else {
Cu.reportError("WebChannel message failed. No message data.");
}
});
let ClickEventHandler = {
init: function init() {

View File

@ -11,6 +11,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
const HTTP_PATH = "http://example.com";
const HTTP_ENDPOINT = "/browser/browser/base/content/test/general/browser_web_channel.html";
// Keep this synced with /mobile/android/tests/browser/robocop/testWebChannel.js
// as much as possible. (We only have that since we can't run browser chrome
// tests on Android. Yet?)
let gTests = [
{
desc: "WebChannel generic message",

View File

@ -1631,8 +1631,12 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete"))
return;
if (this._matchCount > 0 && this.selectedIndex == -1)
if (this._matchCount > 0 && this.selectedIndex == -1) {
// Don't handle this as a user-initiated action.
this._ignoreNextSelect = true;
this.selectedIndex = 0;
this._ignoreNextSelect = false;
}
this.input.gotResultForCurrentQuery = true;
if (this.input.handleEnterWhenGotResult) {
@ -1644,6 +1648,18 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
</method>
</implementation>
<handlers>
<handler event="select"><![CDATA[
// When the user selects one of matches, stop the search to avoid
// changing the underlying result unexpectedly.
if (!this._ignoreNextSelect && this.selectedIndex >= 0) {
let controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
controller.stopSearch();
}
]]></handler>
</handlers>
</binding>
<binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">

View File

@ -36,6 +36,7 @@
// Eslint built-in rules are documented at <http://eslint.org/docs/rules/>
"camelcase": 0, // TODO: Remove (use default)
"computed-property-spacing": [2, "never"],
"consistent-return": 0, // TODO: Remove (use default)
dot-location: 0, // [2, property],
"eqeqeq": 0, // TBD. Might need to be separate for content & chrome
@ -52,11 +53,14 @@
"no-redeclare": 0, // TODO: Remove (use default)
"no-return-assign": 0, // TODO: Remove (use default)
"no-underscore-dangle": 0, // Leave as 0. Commonly used for private variables.
"no-unexpected-multiline": 2,
"no-unneeded-ternary": 2,
"no-unused-expressions": 0, // TODO: Remove (use default)
"no-unused-vars": 0, // TODO: Remove (use default)
"no-use-before-define": 0, // TODO: Remove (use default)
"object-curly-spacing": 0, // [2, "always"],
"quotes": [2, "double", "avoid-escape"],
"spaced-comment": [2, "always"],
"strict": 0, // [2, "function"],
// eslint-plugin-react rules. These are documented at
// <https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules>

View File

@ -129,7 +129,9 @@ loop.contacts = (function(_, mozL10n) {
});
return (
React.createElement("div", {className: "contacts-gravatar-promo"},
React.createElement(Button, {additionalClass: "button-close", onClick: this.handleCloseButtonClick}),
React.createElement(Button, {additionalClass: "button-close",
caption: "",
onClick: this.handleCloseButtonClick}),
React.createElement("p", {dangerouslySetInnerHTML: {__html: message},
onClick: this.handleLinkClick}),
React.createElement(ButtonGroup, null,

View File

@ -129,7 +129,9 @@ loop.contacts = (function(_, mozL10n) {
});
return (
<div className="contacts-gravatar-promo">
<Button additionalClass="button-close" onClick={this.handleCloseButtonClick}/>
<Button additionalClass="button-close"
caption=""
onClick={this.handleCloseButtonClick} />
<p dangerouslySetInnerHTML={{__html: message}}
onClick={this.handleLinkClick}></p>
<ButtonGroup>

View File

@ -659,6 +659,7 @@ loop.conversationViews = (function(mozL10n) {
React.createElement("div", {className: "video_wrapper remote_wrapper"},
React.createElement("div", {className: "video_inner remote focus-stream"},
React.createElement(sharedViews.MediaView, {displayAvatar: !this.shouldRenderRemoteVideo(),
isLoading: false,
mediaType: "remote",
posterUrl: this.props.remotePosterUrl,
srcVideoObject: this.state.remoteSrcVideoObject})
@ -666,6 +667,7 @@ loop.conversationViews = (function(mozL10n) {
),
React.createElement("div", {className: localStreamClasses},
React.createElement(sharedViews.MediaView, {displayAvatar: !this.props.video.enabled,
isLoading: false,
mediaType: "local",
posterUrl: this.props.localPosterUrl,
srcVideoObject: this.state.localSrcVideoObject})
@ -674,6 +676,7 @@ loop.conversationViews = (function(mozL10n) {
React.createElement(loop.shared.views.ConversationToolbar, {
audio: this.props.audio,
dispatcher: this.props.dispatcher,
edit: { visible: false, enabled: false},
hangup: this.hangup,
publishStream: this.publishStream,
video: this.props.video})

View File

@ -659,6 +659,7 @@ loop.conversationViews = (function(mozL10n) {
<div className="video_wrapper remote_wrapper">
<div className="video_inner remote focus-stream">
<sharedViews.MediaView displayAvatar={!this.shouldRenderRemoteVideo()}
isLoading={false}
mediaType="remote"
posterUrl={this.props.remotePosterUrl}
srcVideoObject={this.state.remoteSrcVideoObject} />
@ -666,6 +667,7 @@ loop.conversationViews = (function(mozL10n) {
</div>
<div className={localStreamClasses}>
<sharedViews.MediaView displayAvatar={!this.props.video.enabled}
isLoading={false}
mediaType="local"
posterUrl={this.props.localPosterUrl}
srcVideoObject={this.state.localSrcVideoObject} />
@ -674,6 +676,7 @@ loop.conversationViews = (function(mozL10n) {
<loop.shared.views.ConversationToolbar
audio={this.props.audio}
dispatcher={this.props.dispatcher}
edit={{ visible: false, enabled: false }}
hangup={this.hangup}
publishStream={this.publishStream}
video={this.props.video} />

View File

@ -153,18 +153,19 @@ loop.roomViews = (function(mozL10n) {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
error: React.PropTypes.object,
mozLoop: React.PropTypes.object.isRequired,
onAddContextClick: React.PropTypes.func,
onEditContextClose: React.PropTypes.func,
// This data is supplied by the activeRoomStore.
roomData: React.PropTypes.object.isRequired,
savingContext: React.PropTypes.bool,
show: React.PropTypes.bool.isRequired,
showContext: React.PropTypes.bool.isRequired,
showEditContext: React.PropTypes.bool.isRequired,
socialShareProviders: React.PropTypes.array
},
getInitialState: function() {
return {
copiedUrl: false,
editMode: false,
newRoomName: ""
};
},
@ -207,11 +208,15 @@ loop.roomViews = (function(mozL10n) {
handleAddContextClick: function(event) {
event.preventDefault();
this.handleEditModeChange(true);
if (this.props.onAddContextClick) {
this.props.onAddContextClick();
}
},
handleEditModeChange: function(newEditMode) {
this.setState({ editMode: newEditMode });
handleEditContextClose: function() {
if (this.props.onEditContextClose) {
this.props.onEditContextClose();
}
},
render: function() {
@ -220,13 +225,16 @@ loop.roomViews = (function(mozL10n) {
}
var canAddContext = this.props.mozLoop.getLoopPref("contextInConversations.enabled") &&
!this.props.showContext && !this.state.editMode;
// Don't show the link when we're showing the edit form already:
!this.props.showEditContext &&
// Don't show the link when there's already context data available:
!(this.props.roomData.roomContextUrls || this.props.roomData.roomDescription);
var cx = React.addons.classSet;
return (
React.createElement("div", {className: "room-invitation-overlay"},
React.createElement("div", {className: "room-invitation-content"},
React.createElement("p", {className: cx({hide: this.state.editMode})},
React.createElement("p", {className: cx({hide: this.props.showEditContext})},
mozL10n.get("invite_header_text")
),
React.createElement("a", {className: cx({hide: !canAddContext, "room-invitation-addcontext": true}),
@ -237,7 +245,7 @@ loop.roomViews = (function(mozL10n) {
React.createElement("div", {className: cx({
"btn-group": true,
"call-action-group": true,
hide: this.state.editMode
hide: this.props.showEditContext
})},
React.createElement("button", {className: "btn btn-info btn-email",
onClick: this.handleEmailButtonClick},
@ -260,62 +268,45 @@ loop.roomViews = (function(mozL10n) {
roomUrl: this.props.roomData.roomUrl,
show: this.state.showMenu,
socialShareProviders: this.props.socialShareProviders}),
React.createElement(DesktopRoomContextView, {
React.createElement(DesktopRoomEditContextView, {
dispatcher: this.props.dispatcher,
editMode: this.state.editMode,
error: this.props.error,
mozLoop: this.props.mozLoop,
onEditModeChange: this.handleEditModeChange,
onClose: this.handleEditContextClose,
roomData: this.props.roomData,
savingContext: this.props.savingContext,
show: this.props.showContext || this.state.editMode})
show: this.props.showEditContext})
)
);
}
});
var DesktopRoomContextView = React.createClass({displayName: "DesktopRoomContextView",
var DesktopRoomEditContextView = React.createClass({displayName: "DesktopRoomEditContextView",
mixins: [React.addons.LinkedStateMixin],
propTypes: {
// Only used for tests.
availableContext: React.PropTypes.object,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
editMode: React.PropTypes.bool,
error: React.PropTypes.object,
mozLoop: React.PropTypes.object.isRequired,
onEditModeChange: React.PropTypes.func,
onClose: React.PropTypes.func,
// This data is supplied by the activeRoomStore.
roomData: React.PropTypes.object.isRequired,
savingContext: React.PropTypes.bool.isRequired,
show: React.PropTypes.bool.isRequired
},
componentWillMount: function() {
this._fetchMetadata();
},
componentWillReceiveProps: function(nextProps) {
var newState = {};
// When the 'show' prop is changed from outside this component, we do need
// to update the state.
if (("show" in nextProps) && nextProps.show !== this.props.show) {
newState.show = nextProps.show;
}
if (("editMode" in nextProps && nextProps.editMode !== this.props.editMode)) {
newState.editMode = nextProps.editMode;
// If we're switching to edit mode, fetch the metadata of the current tab.
// But _only_ if there's no context currently attached to the room; the
// checkbox will be disabled in that case.
if (nextProps.editMode) {
this.props.mozLoop.getSelectedTabMetadata(function(metadata) {
var previewImage = metadata.favicon || "";
var description = metadata.title || metadata.description;
var metaUrl = metadata.url;
this.setState({
availableContext: {
previewImage: previewImage,
description: description,
url: metaUrl
}
});
}.bind(this));
if (nextProps.show) {
this._fetchMetadata();
}
}
// When we receive an update for the `roomData` property, make sure that
@ -341,11 +332,15 @@ loop.roomViews = (function(mozL10n) {
}
}
// Make sure we do not show the edit-mode when we just successfully saved
// context.
if (this.props.savingContext && nextProps.savingContext !== this.props.savingContext &&
!nextProps.error && this.state.editMode) {
newState.editMode = false;
// Feature support: when a context save completed without error, we can
// close the context edit form.
if (("savingContext" in nextProps) && this.props.savingContext &&
this.props.savingContext !== nextProps.savingContext && this.state.show
&& !this.props.error && !nextProps.error) {
newState.show = false;
if (this.props.onClose) {
this.props.onClose();
}
}
if (Object.getOwnPropertyNames(newState).length) {
@ -353,16 +348,11 @@ loop.roomViews = (function(mozL10n) {
}
},
getDefaultProps: function() {
return { editMode: false };
},
getInitialState: function() {
var url = this._getURL();
return {
// `availableContext` prop only used in tests.
availableContext: this.props.availableContext,
editMode: this.props.editMode,
availableContext: null,
show: this.props.show,
newRoomName: this.props.roomData.roomName || "",
newRoomURL: url && url.location || "",
@ -371,17 +361,29 @@ loop.roomViews = (function(mozL10n) {
};
},
_fetchMetadata: function() {
this.props.mozLoop.getSelectedTabMetadata(function(metadata) {
var previewImage = metadata.favicon || "";
var description = metadata.title || metadata.description;
var metaUrl = metadata.url;
this.setState({
availableContext: {
previewImage: previewImage,
description: description,
url: metaUrl
}
});
}.bind(this));
},
handleCloseClick: function(event) {
event.stopPropagation();
event.preventDefault();
if (this.state.editMode) {
this.setState({ editMode: false });
if (this.props.onEditModeChange) {
this.props.onEditModeChange(false);
}
return;
}
this.setState({ show: false });
if (this.props.onClose) {
this.props.onClose();
}
},
handleContextClick: function(event) {
@ -396,15 +398,6 @@ loop.roomViews = (function(mozL10n) {
this.props.mozLoop.openURL(url.location);
},
handleEditClick: function(event) {
event.preventDefault();
this.setState({ editMode: true });
if (this.props.onEditModeChange) {
this.props.onEditModeChange(true);
}
},
handleCheckboxChange: function(state) {
if (state.checked) {
// The checkbox was checked, prefill the fields with the values available
@ -478,7 +471,7 @@ loop.roomViews = (function(mozL10n) {
},
render: function() {
if (!this.state.show && !this.state.editMode) {
if (!this.state.show) {
return null;
}
@ -486,93 +479,55 @@ loop.roomViews = (function(mozL10n) {
var thumbnail = url && url.thumbnail || "loop/shared/img/icons-16x16.svg#globe";
var urlDescription = url && url.description || "";
var location = url && url.location || "";
var locationData = null;
if (location) {
locationData = checkboxLabel = sharedUtils.formatURL(location);
}
if (!checkboxLabel) {
try {
checkboxLabel = sharedUtils.formatURL((this.state.availableContext ?
this.state.availableContext.url : ""));
} catch (ex) {}
}
var cx = React.addons.classSet;
if (this.state.editMode) {
var availableContext = this.state.availableContext;
// The checkbox shows as checked when there's already context data
// attached to this room.
var checked = !!urlDescription;
var checkboxLabel = urlDescription || (availableContext && availableContext.url ?
availableContext.description : "");
return (
React.createElement("div", {className: "room-context editMode"},
React.createElement("p", {className: cx({"error": !!this.props.error,
"error-display-area": true})},
mozL10n.get("rooms_change_failed_label")
),
React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")),
React.createElement(sharedViews.Checkbox, {
additionalClass: cx({ hide: !checkboxLabel }),
checked: checked,
disabled: checked,
label: checkboxLabel,
onChange: this.handleCheckboxChange,
value: location}),
React.createElement("form", {onSubmit: this.handleFormSubmit},
React.createElement("input", {className: "room-context-name",
onKeyDown: this.handleTextareaKeyDown,
placeholder: mozL10n.get("context_edit_name_placeholder"),
type: "text",
valueLink: this.linkState("newRoomName")}),
React.createElement("input", {className: "room-context-url",
disabled: availableContext && availableContext.url === this.state.newRoomURL,
onKeyDown: this.handleTextareaKeyDown,
placeholder: "https://",
type: "text",
valueLink: this.linkState("newRoomURL")}),
React.createElement("textarea", {className: "room-context-comments",
onKeyDown: this.handleTextareaKeyDown,
placeholder: mozL10n.get("context_edit_comments_placeholder"),
rows: "3", type: "text",
valueLink: this.linkState("newRoomDescription")})
),
React.createElement("button", {className: "btn btn-info",
disabled: this.props.savingContext,
onClick: this.handleFormSubmit},
mozL10n.get("context_save_label2")
),
React.createElement("button", {className: "room-context-btn-close",
onClick: this.handleCloseClick,
title: mozL10n.get("cancel_button")})
)
);
}
if (!locationData) {
return null;
}
var availableContext = this.state.availableContext;
// The checkbox shows as checked when there's already context data
// attached to this room.
var checked = !!urlDescription;
var checkboxLabel = urlDescription || (availableContext && availableContext.url ?
availableContext.description : "");
return (
React.createElement("div", {className: "room-context"},
React.createElement("p", {className: cx({"error": !!this.props.error,
"error-display-area": true})},
mozL10n.get("rooms_change_failed_label")
),
React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")),
React.createElement("div", {className: "room-context-content",
onClick: this.handleContextClick},
React.createElement("img", {className: "room-context-thumbnail", src: thumbnail}),
React.createElement("div", {className: "room-context-description",
title: urlDescription},
this._truncate(urlDescription),
React.createElement("a", {className: "room-context-url",
title: locationData.location}, locationData.hostname)
)
React.createElement(sharedViews.Checkbox, {
additionalClass: cx({ hide: !checkboxLabel }),
checked: checked,
disabled: checked,
label: checkboxLabel,
onChange: this.handleCheckboxChange,
value: location}),
React.createElement("form", {onSubmit: this.handleFormSubmit},
React.createElement("input", {className: "room-context-name",
onKeyDown: this.handleTextareaKeyDown,
placeholder: mozL10n.get("context_edit_name_placeholder"),
type: "text",
valueLink: this.linkState("newRoomName")}),
React.createElement("input", {className: "room-context-url",
disabled: availableContext && availableContext.url === this.state.newRoomURL,
onKeyDown: this.handleTextareaKeyDown,
placeholder: "https://",
type: "text",
valueLink: this.linkState("newRoomURL")}),
React.createElement("textarea", {className: "room-context-comments",
onKeyDown: this.handleTextareaKeyDown,
placeholder: mozL10n.get("context_edit_comments_placeholder"),
rows: "3", type: "text",
valueLink: this.linkState("newRoomDescription")})
),
React.createElement("button", {className: "btn btn-info",
disabled: this.props.savingContext,
onClick: this.handleFormSubmit},
mozL10n.get("context_save_label2")
),
React.createElement("button", {className: "room-context-btn-close",
onClick: this.handleCloseClick,
title: mozL10n.get("context_hide_tooltip")}),
React.createElement("button", {className: "room-context-btn-edit",
onClick: this.handleEditClick,
title: mozL10n.get("context_edit_tooltip")})
title: mozL10n.get("cancel_button")})
)
);
}
@ -595,7 +550,15 @@ loop.roomViews = (function(mozL10n) {
// The poster URLs are for UI-showcase testing and development.
localPosterUrl: React.PropTypes.string,
mozLoop: React.PropTypes.object.isRequired,
remotePosterUrl: React.PropTypes.string
remotePosterUrl: React.PropTypes.string,
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
},
getInitialState: function() {
return {
contextEnabled: this.props.mozLoop.getLoopPref("contextInConversations.enabled"),
showEditContext: false
};
},
componentWillUpdate: function(nextProps, nextState) {
@ -641,13 +604,6 @@ loop.roomViews = (function(mozL10n) {
return (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS);
},
_shouldRenderContextView: function() {
return !!(
this.props.mozLoop.getLoopPref("contextInConversations.enabled") &&
(this.state.roomContextUrls || this.state.roomDescription)
);
},
/**
* Works out if remote video should be rended or not, depending on the
* room state and other flags.
@ -712,9 +668,21 @@ loop.roomViews = (function(mozL10n) {
* @private
*/
_shouldRenderRemoteLoading: function() {
return this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
!this.state.remoteSrcVideoObject &&
!this.state.mediaConnected;
return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
!this.state.remoteSrcVideoObject &&
!this.state.mediaConnected);
},
handleAddContextClick: function() {
this.setState({ showEditContext: true });
},
handleEditContextClick: function() {
this.setState({ showEditContext: !this.state.showEditContext });
},
handleEditContextClose: function() {
this.setState({ showEditContext: false });
},
render: function() {
@ -730,12 +698,12 @@ loop.roomViews = (function(mozL10n) {
});
var screenShareData = {
state: this.state.screenSharingState,
state: this.state.screenSharingState || SCREEN_SHARE_STATES.INACTIVE,
visible: true
};
var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
var shouldRenderContextView = this._shouldRenderContextView();
var shouldRenderEditContextView = this.state.contextEnabled && this.state.showEditContext;
var roomData = this.props.roomStore.getStoreState("activeRoom");
switch(this.state.roomState) {
@ -759,18 +727,20 @@ loop.roomViews = (function(mozL10n) {
return (
React.createElement("div", {className: "room-conversation-wrapper"},
React.createElement(DesktopRoomInvitationView, {
dispatcher: this.props.dispatcher,
error: this.state.error,
mozLoop: this.props.mozLoop,
roomData: roomData,
savingContext: this.state.savingContext,
show: shouldRenderInvitationOverlay,
showContext: shouldRenderContextView,
socialShareProviders: this.state.socialShareProviders}),
React.createElement("div", {className: "video-layout-wrapper"},
React.createElement("div", {className: "conversation room-conversation"},
React.createElement("div", {className: "media nested"},
React.createElement(DesktopRoomInvitationView, {
dispatcher: this.props.dispatcher,
error: this.state.error,
mozLoop: this.props.mozLoop,
onAddContextClick: this.handleAddContextClick,
onEditContextClose: this.handleEditContextClose,
roomData: roomData,
savingContext: this.state.savingContext,
show: shouldRenderInvitationOverlay,
showEditContext: shouldRenderInvitationOverlay && shouldRenderEditContextView,
socialShareProviders: this.state.socialShareProviders}),
React.createElement("div", {className: "video_wrapper remote_wrapper"},
React.createElement("div", {className: "video_inner remote focus-stream"},
React.createElement(sharedViews.MediaView, {displayAvatar: !this.shouldRenderRemoteVideo(),
@ -791,23 +761,27 @@ loop.roomViews = (function(mozL10n) {
React.createElement(sharedViews.ConversationToolbar, {
audio: {enabled: !this.state.audioMuted, visible: true},
dispatcher: this.props.dispatcher,
edit: { visible: this.state.contextEnabled, enabled: !this.state.showEditContext},
hangup: this.leaveRoom,
onEditClick: this.handleEditContextClick,
publishStream: this.publishStream,
screenShare: screenShareData,
video: {enabled: !this.state.videoMuted, visible: true}})
)
),
React.createElement(DesktopRoomContextView, {
React.createElement(DesktopRoomEditContextView, {
dispatcher: this.props.dispatcher,
error: this.state.error,
mozLoop: this.props.mozLoop,
onClose: this.handleEditContextClose,
roomData: roomData,
savingContext: this.state.savingContext,
show: !shouldRenderInvitationOverlay && shouldRenderContextView}),
show: !shouldRenderInvitationOverlay && shouldRenderEditContextView}),
React.createElement(sharedViews.chat.TextChatView, {
dispatcher: this.props.dispatcher,
showAlways: false,
showRoomName: false})
showRoomName: false,
useDesktopPaths: true})
)
);
}
@ -818,7 +792,7 @@ loop.roomViews = (function(mozL10n) {
return {
ActiveRoomStoreMixin: ActiveRoomStoreMixin,
SocialShareDropdown: SocialShareDropdown,
DesktopRoomContextView: DesktopRoomContextView,
DesktopRoomEditContextView: DesktopRoomEditContextView,
DesktopRoomConversationView: DesktopRoomConversationView,
DesktopRoomInvitationView: DesktopRoomInvitationView
};

View File

@ -153,18 +153,19 @@ loop.roomViews = (function(mozL10n) {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
error: React.PropTypes.object,
mozLoop: React.PropTypes.object.isRequired,
onAddContextClick: React.PropTypes.func,
onEditContextClose: React.PropTypes.func,
// This data is supplied by the activeRoomStore.
roomData: React.PropTypes.object.isRequired,
savingContext: React.PropTypes.bool,
show: React.PropTypes.bool.isRequired,
showContext: React.PropTypes.bool.isRequired,
showEditContext: React.PropTypes.bool.isRequired,
socialShareProviders: React.PropTypes.array
},
getInitialState: function() {
return {
copiedUrl: false,
editMode: false,
newRoomName: ""
};
},
@ -207,11 +208,15 @@ loop.roomViews = (function(mozL10n) {
handleAddContextClick: function(event) {
event.preventDefault();
this.handleEditModeChange(true);
if (this.props.onAddContextClick) {
this.props.onAddContextClick();
}
},
handleEditModeChange: function(newEditMode) {
this.setState({ editMode: newEditMode });
handleEditContextClose: function() {
if (this.props.onEditContextClose) {
this.props.onEditContextClose();
}
},
render: function() {
@ -220,13 +225,16 @@ loop.roomViews = (function(mozL10n) {
}
var canAddContext = this.props.mozLoop.getLoopPref("contextInConversations.enabled") &&
!this.props.showContext && !this.state.editMode;
// Don't show the link when we're showing the edit form already:
!this.props.showEditContext &&
// Don't show the link when there's already context data available:
!(this.props.roomData.roomContextUrls || this.props.roomData.roomDescription);
var cx = React.addons.classSet;
return (
<div className="room-invitation-overlay">
<div className="room-invitation-content">
<p className={cx({hide: this.state.editMode})}>
<p className={cx({hide: this.props.showEditContext})}>
{mozL10n.get("invite_header_text")}
</p>
<a className={cx({hide: !canAddContext, "room-invitation-addcontext": true})}
@ -237,7 +245,7 @@ loop.roomViews = (function(mozL10n) {
<div className={cx({
"btn-group": true,
"call-action-group": true,
hide: this.state.editMode
hide: this.props.showEditContext
})}>
<button className="btn btn-info btn-email"
onClick={this.handleEmailButtonClick}>
@ -260,62 +268,45 @@ loop.roomViews = (function(mozL10n) {
roomUrl={this.props.roomData.roomUrl}
show={this.state.showMenu}
socialShareProviders={this.props.socialShareProviders} />
<DesktopRoomContextView
<DesktopRoomEditContextView
dispatcher={this.props.dispatcher}
editMode={this.state.editMode}
error={this.props.error}
mozLoop={this.props.mozLoop}
onEditModeChange={this.handleEditModeChange}
onClose={this.handleEditContextClose}
roomData={this.props.roomData}
savingContext={this.props.savingContext}
show={this.props.showContext || this.state.editMode} />
show={this.props.showEditContext} />
</div>
);
}
});
var DesktopRoomContextView = React.createClass({
var DesktopRoomEditContextView = React.createClass({
mixins: [React.addons.LinkedStateMixin],
propTypes: {
// Only used for tests.
availableContext: React.PropTypes.object,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
editMode: React.PropTypes.bool,
error: React.PropTypes.object,
mozLoop: React.PropTypes.object.isRequired,
onEditModeChange: React.PropTypes.func,
onClose: React.PropTypes.func,
// This data is supplied by the activeRoomStore.
roomData: React.PropTypes.object.isRequired,
savingContext: React.PropTypes.bool.isRequired,
show: React.PropTypes.bool.isRequired
},
componentWillMount: function() {
this._fetchMetadata();
},
componentWillReceiveProps: function(nextProps) {
var newState = {};
// When the 'show' prop is changed from outside this component, we do need
// to update the state.
if (("show" in nextProps) && nextProps.show !== this.props.show) {
newState.show = nextProps.show;
}
if (("editMode" in nextProps && nextProps.editMode !== this.props.editMode)) {
newState.editMode = nextProps.editMode;
// If we're switching to edit mode, fetch the metadata of the current tab.
// But _only_ if there's no context currently attached to the room; the
// checkbox will be disabled in that case.
if (nextProps.editMode) {
this.props.mozLoop.getSelectedTabMetadata(function(metadata) {
var previewImage = metadata.favicon || "";
var description = metadata.title || metadata.description;
var metaUrl = metadata.url;
this.setState({
availableContext: {
previewImage: previewImage,
description: description,
url: metaUrl
}
});
}.bind(this));
if (nextProps.show) {
this._fetchMetadata();
}
}
// When we receive an update for the `roomData` property, make sure that
@ -341,11 +332,15 @@ loop.roomViews = (function(mozL10n) {
}
}
// Make sure we do not show the edit-mode when we just successfully saved
// context.
if (this.props.savingContext && nextProps.savingContext !== this.props.savingContext &&
!nextProps.error && this.state.editMode) {
newState.editMode = false;
// Feature support: when a context save completed without error, we can
// close the context edit form.
if (("savingContext" in nextProps) && this.props.savingContext &&
this.props.savingContext !== nextProps.savingContext && this.state.show
&& !this.props.error && !nextProps.error) {
newState.show = false;
if (this.props.onClose) {
this.props.onClose();
}
}
if (Object.getOwnPropertyNames(newState).length) {
@ -353,16 +348,11 @@ loop.roomViews = (function(mozL10n) {
}
},
getDefaultProps: function() {
return { editMode: false };
},
getInitialState: function() {
var url = this._getURL();
return {
// `availableContext` prop only used in tests.
availableContext: this.props.availableContext,
editMode: this.props.editMode,
availableContext: null,
show: this.props.show,
newRoomName: this.props.roomData.roomName || "",
newRoomURL: url && url.location || "",
@ -371,17 +361,29 @@ loop.roomViews = (function(mozL10n) {
};
},
_fetchMetadata: function() {
this.props.mozLoop.getSelectedTabMetadata(function(metadata) {
var previewImage = metadata.favicon || "";
var description = metadata.title || metadata.description;
var metaUrl = metadata.url;
this.setState({
availableContext: {
previewImage: previewImage,
description: description,
url: metaUrl
}
});
}.bind(this));
},
handleCloseClick: function(event) {
event.stopPropagation();
event.preventDefault();
if (this.state.editMode) {
this.setState({ editMode: false });
if (this.props.onEditModeChange) {
this.props.onEditModeChange(false);
}
return;
}
this.setState({ show: false });
if (this.props.onClose) {
this.props.onClose();
}
},
handleContextClick: function(event) {
@ -396,15 +398,6 @@ loop.roomViews = (function(mozL10n) {
this.props.mozLoop.openURL(url.location);
},
handleEditClick: function(event) {
event.preventDefault();
this.setState({ editMode: true });
if (this.props.onEditModeChange) {
this.props.onEditModeChange(true);
}
},
handleCheckboxChange: function(state) {
if (state.checked) {
// The checkbox was checked, prefill the fields with the values available
@ -478,7 +471,7 @@ loop.roomViews = (function(mozL10n) {
},
render: function() {
if (!this.state.show && !this.state.editMode) {
if (!this.state.show) {
return null;
}
@ -486,93 +479,55 @@ loop.roomViews = (function(mozL10n) {
var thumbnail = url && url.thumbnail || "loop/shared/img/icons-16x16.svg#globe";
var urlDescription = url && url.description || "";
var location = url && url.location || "";
var locationData = null;
if (location) {
locationData = checkboxLabel = sharedUtils.formatURL(location);
}
if (!checkboxLabel) {
try {
checkboxLabel = sharedUtils.formatURL((this.state.availableContext ?
this.state.availableContext.url : ""));
} catch (ex) {}
}
var cx = React.addons.classSet;
if (this.state.editMode) {
var availableContext = this.state.availableContext;
// The checkbox shows as checked when there's already context data
// attached to this room.
var checked = !!urlDescription;
var checkboxLabel = urlDescription || (availableContext && availableContext.url ?
availableContext.description : "");
return (
<div className="room-context editMode">
<p className={cx({"error": !!this.props.error,
"error-display-area": true})}>
{mozL10n.get("rooms_change_failed_label")}
</p>
<div className="room-context-label">{mozL10n.get("context_inroom_label")}</div>
<sharedViews.Checkbox
additionalClass={cx({ hide: !checkboxLabel })}
checked={checked}
disabled={checked}
label={checkboxLabel}
onChange={this.handleCheckboxChange}
value={location} />
<form onSubmit={this.handleFormSubmit}>
<input className="room-context-name"
onKeyDown={this.handleTextareaKeyDown}
placeholder={mozL10n.get("context_edit_name_placeholder")}
type="text"
valueLink={this.linkState("newRoomName")} />
<input className="room-context-url"
disabled={availableContext && availableContext.url === this.state.newRoomURL}
onKeyDown={this.handleTextareaKeyDown}
placeholder="https://"
type="text"
valueLink={this.linkState("newRoomURL")} />
<textarea className="room-context-comments"
onKeyDown={this.handleTextareaKeyDown}
placeholder={mozL10n.get("context_edit_comments_placeholder")}
rows="3" type="text"
valueLink={this.linkState("newRoomDescription")} />
</form>
<button className="btn btn-info"
disabled={this.props.savingContext}
onClick={this.handleFormSubmit}>
{mozL10n.get("context_save_label2")}
</button>
<button className="room-context-btn-close"
onClick={this.handleCloseClick}
title={mozL10n.get("cancel_button")}/>
</div>
);
}
if (!locationData) {
return null;
}
var availableContext = this.state.availableContext;
// The checkbox shows as checked when there's already context data
// attached to this room.
var checked = !!urlDescription;
var checkboxLabel = urlDescription || (availableContext && availableContext.url ?
availableContext.description : "");
return (
<div className="room-context">
<p className={cx({"error": !!this.props.error,
"error-display-area": true})}>
{mozL10n.get("rooms_change_failed_label")}
</p>
<div className="room-context-label">{mozL10n.get("context_inroom_label")}</div>
<div className="room-context-content"
onClick={this.handleContextClick}>
<img className="room-context-thumbnail" src={thumbnail} />
<div className="room-context-description"
title={urlDescription}>
{this._truncate(urlDescription)}
<a className="room-context-url"
title={locationData.location}>{locationData.hostname}</a>
</div>
</div>
<sharedViews.Checkbox
additionalClass={cx({ hide: !checkboxLabel })}
checked={checked}
disabled={checked}
label={checkboxLabel}
onChange={this.handleCheckboxChange}
value={location} />
<form onSubmit={this.handleFormSubmit}>
<input className="room-context-name"
onKeyDown={this.handleTextareaKeyDown}
placeholder={mozL10n.get("context_edit_name_placeholder")}
type="text"
valueLink={this.linkState("newRoomName")} />
<input className="room-context-url"
disabled={availableContext && availableContext.url === this.state.newRoomURL}
onKeyDown={this.handleTextareaKeyDown}
placeholder="https://"
type="text"
valueLink={this.linkState("newRoomURL")} />
<textarea className="room-context-comments"
onKeyDown={this.handleTextareaKeyDown}
placeholder={mozL10n.get("context_edit_comments_placeholder")}
rows="3" type="text"
valueLink={this.linkState("newRoomDescription")} />
</form>
<button className="btn btn-info"
disabled={this.props.savingContext}
onClick={this.handleFormSubmit}>
{mozL10n.get("context_save_label2")}
</button>
<button className="room-context-btn-close"
onClick={this.handleCloseClick}
title={mozL10n.get("context_hide_tooltip")}/>
<button className="room-context-btn-edit"
onClick={this.handleEditClick}
title={mozL10n.get("context_edit_tooltip")}/>
title={mozL10n.get("cancel_button")}/>
</div>
);
}
@ -595,7 +550,15 @@ loop.roomViews = (function(mozL10n) {
// The poster URLs are for UI-showcase testing and development.
localPosterUrl: React.PropTypes.string,
mozLoop: React.PropTypes.object.isRequired,
remotePosterUrl: React.PropTypes.string
remotePosterUrl: React.PropTypes.string,
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
},
getInitialState: function() {
return {
contextEnabled: this.props.mozLoop.getLoopPref("contextInConversations.enabled"),
showEditContext: false
};
},
componentWillUpdate: function(nextProps, nextState) {
@ -641,13 +604,6 @@ loop.roomViews = (function(mozL10n) {
return (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS);
},
_shouldRenderContextView: function() {
return !!(
this.props.mozLoop.getLoopPref("contextInConversations.enabled") &&
(this.state.roomContextUrls || this.state.roomDescription)
);
},
/**
* Works out if remote video should be rended or not, depending on the
* room state and other flags.
@ -712,9 +668,21 @@ loop.roomViews = (function(mozL10n) {
* @private
*/
_shouldRenderRemoteLoading: function() {
return this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
!this.state.remoteSrcVideoObject &&
!this.state.mediaConnected;
return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
!this.state.remoteSrcVideoObject &&
!this.state.mediaConnected);
},
handleAddContextClick: function() {
this.setState({ showEditContext: true });
},
handleEditContextClick: function() {
this.setState({ showEditContext: !this.state.showEditContext });
},
handleEditContextClose: function() {
this.setState({ showEditContext: false });
},
render: function() {
@ -730,12 +698,12 @@ loop.roomViews = (function(mozL10n) {
});
var screenShareData = {
state: this.state.screenSharingState,
state: this.state.screenSharingState || SCREEN_SHARE_STATES.INACTIVE,
visible: true
};
var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
var shouldRenderContextView = this._shouldRenderContextView();
var shouldRenderEditContextView = this.state.contextEnabled && this.state.showEditContext;
var roomData = this.props.roomStore.getStoreState("activeRoom");
switch(this.state.roomState) {
@ -759,18 +727,20 @@ loop.roomViews = (function(mozL10n) {
return (
<div className="room-conversation-wrapper">
<DesktopRoomInvitationView
dispatcher={this.props.dispatcher}
error={this.state.error}
mozLoop={this.props.mozLoop}
roomData={roomData}
savingContext={this.state.savingContext}
show={shouldRenderInvitationOverlay}
showContext={shouldRenderContextView}
socialShareProviders={this.state.socialShareProviders} />
<div className="video-layout-wrapper">
<div className="conversation room-conversation">
<div className="media nested">
<DesktopRoomInvitationView
dispatcher={this.props.dispatcher}
error={this.state.error}
mozLoop={this.props.mozLoop}
onAddContextClick={this.handleAddContextClick}
onEditContextClose={this.handleEditContextClose}
roomData={roomData}
savingContext={this.state.savingContext}
show={shouldRenderInvitationOverlay}
showEditContext={shouldRenderInvitationOverlay && shouldRenderEditContextView}
socialShareProviders={this.state.socialShareProviders} />
<div className="video_wrapper remote_wrapper">
<div className="video_inner remote focus-stream">
<sharedViews.MediaView displayAvatar={!this.shouldRenderRemoteVideo()}
@ -791,23 +761,27 @@ loop.roomViews = (function(mozL10n) {
<sharedViews.ConversationToolbar
audio={{enabled: !this.state.audioMuted, visible: true}}
dispatcher={this.props.dispatcher}
edit={{ visible: this.state.contextEnabled, enabled: !this.state.showEditContext }}
hangup={this.leaveRoom}
onEditClick={this.handleEditContextClick}
publishStream={this.publishStream}
screenShare={screenShareData}
video={{enabled: !this.state.videoMuted, visible: true}} />
</div>
</div>
<DesktopRoomContextView
<DesktopRoomEditContextView
dispatcher={this.props.dispatcher}
error={this.state.error}
mozLoop={this.props.mozLoop}
onClose={this.handleEditContextClose}
roomData={roomData}
savingContext={this.state.savingContext}
show={!shouldRenderInvitationOverlay && shouldRenderContextView} />
show={!shouldRenderInvitationOverlay && shouldRenderEditContextView} />
<sharedViews.chat.TextChatView
dispatcher={this.props.dispatcher}
showAlways={false}
showRoomName={false} />
showRoomName={false}
useDesktopPaths={true} />
</div>
);
}
@ -818,7 +792,7 @@ loop.roomViews = (function(mozL10n) {
return {
ActiveRoomStoreMixin: ActiveRoomStoreMixin,
SocialShareDropdown: SocialShareDropdown,
DesktopRoomContextView: DesktopRoomContextView,
DesktopRoomEditContextView: DesktopRoomEditContextView,
DesktopRoomConversationView: DesktopRoomConversationView,
DesktopRoomInvitationView: DesktopRoomInvitationView
};

View File

@ -54,7 +54,7 @@
margin-right: 16px;
}
.btn-screen-share-entry {
.btn-edit-entry {
float: right !important;
border-left: 1px solid #5a5a5a;
}
@ -210,10 +210,15 @@
}
/* Screen share button */
.btn-mute-edit,
.btn-screen-share {
position: relative;
background-image: url(../img/icons-16x16.svg#screen-white);
background-image: url(../img/icons-10x10.svg#edit-white);
background-size: 16px 16px;
}
.btn-screen-share {
background-image: url(../img/icons-16x16.svg#screen-white);
width: 42px;
}
@ -225,6 +230,10 @@
.btn-screen-share.active {
background-image: url(../img/icons-16x16.svg#screenmute-white);
background-color: #6CB23E;
}
.btn-mute-edit.muted,
.btn-screen-share.active {
opacity: 1;
}
@ -930,7 +939,7 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
}
.room-context {
background: rgba(0,0,0,.6);
background: rgba(0,0,0,.8);
border-top: 2px solid #444;
border-bottom: 2px solid #444;
padding: .5rem;
@ -939,6 +948,9 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
left: 0;
bottom: 0;
width: 100%;
/* Stretch to the maximum available space whilst not covering the conversation
toolbar (26px). */
height: calc(100% - 26px);
font-size: .9em;
display: flex;
flex-flow: column nowrap;
@ -950,42 +962,14 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
z-index: 2;
}
.room-context.editMode {
/* Stretch to the maximum available space whilst not covering the conversation
toolbar (26px). */
height: calc(100% - 26px);
}
.room-invitation-overlay .room-context {
position: relative;
left: auto;
bottom: auto;
flex: 0 1 auto;
}
.room-invitation-overlay .room-context.editMode {
height: 100%;
}
.room-context-content {
flex: 1 1 auto;
text-align: start;
display: flex;
flex-flow: row nowrap;
font-size: .9em;
}
.room-context-thumbnail {
/* 16px icon size + 3px border width. */
width: 19px;
max-height: 19px;
border: 3px solid #fff;
border-radius: 3px;
background-color: #fff;
-moz-margin-end: 1ch;
flex: 0 1 auto;
}
.room-context > .error-display-area.error {
display: block;
background-color: rgba(215,67,69,.8);
@ -1011,7 +995,7 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
margin-bottom: 1em;
}
.room-context-description,
.room-context-label,
.room-context > .checkbox-wrapper > label {
color: #fff;
}
@ -1020,7 +1004,6 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
color: #707070;
}
.room-context-description,
.room-context-comment {
word-wrap: break-word;
}
@ -1079,8 +1062,7 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
align-self: flex-end;
}
.room-context-btn-close,
.room-context-btn-edit {
.room-context-btn-close {
position: absolute;
right: 8px;
/* 8px offset + 2px border-top */
@ -1096,31 +1078,16 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
cursor: pointer;
}
.room-context-btn-edit {
right: 20px;
background-image: url("../img/icons-10x10.svg#edit-darkergrey");
}
.room-context-btn-edit:hover,
.room-context-btn-edit:hover:active {
background-image: url("../img/icons-10x10.svg#edit-active");
}
.room-context-btn-close:hover,
.room-context-btn-close:hover:active {
background-image: url("../img/icons-10x10.svg#close-active");
}
html[dir="rtl"] .room-context-btn-close,
html[dir="rtl"] .room-context-btn-edit {
html[dir="rtl"] .room-context-btn-close {
right: auto;
left: 8px;
}
html[dir="rtl"] .room-context-btn-edit {
left: 20px;
}
.media-layout {
/* 50px is the header, 3em is the footer. */
height: calc(100% - 50px - 3em);

View File

@ -17,14 +17,11 @@
fill: #0095dd;
}
use[id$="-white"] {
fill: rgba(255,255,255,0.8);
fill: #fff;
}
use[id$="-disabled"] {
fill: rgba(255,255,255,0.4);
}
use[id$="-darkergrey"] {
fill: #999;
}
</style>
<defs>
<polygon id="close-shape" points="10,1.717 8.336,0.049 5.024,3.369 1.663,0 0,1.668 3.36,5.037 0.098,8.307 1.762,9.975 5.025,6.705 8.311,10 9.975,8.332 6.688,5.037"/>
@ -44,7 +41,7 @@
<use id="edit" xlink:href="#edit-shape"/>
<use id="edit-active" xlink:href="#edit-shape"/>
<use id="edit-disabled" xlink:href="#edit-shape"/>
<use id="edit-darkergrey" xlink:href="#edit-shape"/>
<use id="edit-white" xlink:href="#edit-shape"/>
<use id="expand" xlink:href="#expand-shape"/>
<use id="expand-active" xlink:href="#expand-shape"/>
<use id="expand-disabled" xlink:href="#expand-shape"/>

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -100,12 +100,29 @@ loop.store.TextChatStore = (function() {
sentTimestamp: messageData.sentTimestamp,
receivedTimestamp: messageData.receivedTimestamp
};
var newList = this._storeState.messageList.concat(message);
var newList = [].concat(this._storeState.messageList);
var isContext = message.contentType === CHAT_CONTENT_TYPES.CONTEXT;
if (isContext) {
var contextUpdated = false;
for (var i = 0, l = newList.length; i < l; ++i) {
// Replace the current context message with the provided update.
if (newList[i].contentType === CHAT_CONTENT_TYPES.CONTEXT) {
newList[i] = message;
contextUpdated = true;
break;
}
}
if (!contextUpdated) {
newList.push(message);
}
} else {
newList.push(message);
}
this.setStoreState({ messageList: newList });
// 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 (type != CHAT_MESSAGE_TYPES.SPECIAL) {
if (message.contentType != CHAT_CONTENT_TYPES.ROOM_NAME) {
window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
}
},

View File

@ -68,7 +68,8 @@ loop.shared.views.chat = (function(mozL10n) {
mixins: [React.addons.PureRenderMixin],
propTypes: {
message: React.PropTypes.string.isRequired
message: React.PropTypes.string.isRequired,
useDesktopPaths: React.PropTypes.bool.isRequired
},
render: function() {
@ -97,7 +98,8 @@ loop.shared.views.chat = (function(mozL10n) {
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
messageList: React.PropTypes.array.isRequired
messageList: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
useDesktopPaths: React.PropTypes.bool.isRequired
},
getInitialState: function() {
@ -157,7 +159,12 @@ loop.shared.views.chat = (function(mozL10n) {
if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
switch (entry.contentType) {
case CHAT_CONTENT_TYPES.ROOM_NAME:
return React.createElement(TextChatRoomName, {key: i, message: entry.message});
return (
React.createElement(TextChatRoomName, {
key: i,
message: entry.message,
useDesktopPaths: this.props.useDesktopPaths})
);
case CHAT_CONTENT_TYPES.CONTEXT:
return (
React.createElement("div", {className: "context-url-view-wrapper", key: i},
@ -168,7 +175,7 @@ loop.shared.views.chat = (function(mozL10n) {
showContextTitle: true,
thumbnail: entry.extraData.thumbnail,
url: entry.extraData.location,
useDesktopPaths: false})
useDesktopPaths: this.props.useDesktopPaths})
)
);
default:
@ -334,11 +341,8 @@ loop.shared.views.chat = (function(mozL10n) {
* as a field for entering new messages.
*
* @property {loop.Dispatcher} dispatcher
* @property {Boolean} showAlways If false, the view will not be rendered
* if text chat is not enabled and the
* message list is empty.
* @property {Boolean} showRoomName Set to true to show the room name special
* list item.
* @property {Boolean} showRoomName Set to true to show the room name
* special list item.
*/
var TextChatView = React.createClass({displayName: "TextChatView",
mixins: [
@ -348,8 +352,8 @@ loop.shared.views.chat = (function(mozL10n) {
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
showAlways: React.PropTypes.bool.isRequired,
showRoomName: React.PropTypes.bool.isRequired
showRoomName: React.PropTypes.bool.isRequired,
useDesktopPaths: React.PropTypes.bool.isRequired
},
getInitialState: function() {
@ -366,14 +370,14 @@ loop.shared.views.chat = (function(mozL10n) {
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
});
} else {
// XXX Desktop should be showing the initial context here (bug 1171940).
messageList = this.state.messageList.filter(function(item) {
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME;
});
hasNonSpecialMessages = !!messageList.length;
}
if (!this.props.showAlways && !this.state.textChatEnabled && !messageList.length) {
if (!this.state.textChatEnabled && !messageList.length) {
return null;
}
@ -386,7 +390,8 @@ loop.shared.views.chat = (function(mozL10n) {
React.createElement("div", {className: textChatViewClasses},
React.createElement(TextChatEntriesView, {
dispatcher: this.props.dispatcher,
messageList: messageList}),
messageList: messageList,
useDesktopPaths: this.props.useDesktopPaths}),
React.createElement(TextChatInputView, {
dispatcher: this.props.dispatcher,
showPlaceholder: !hasNonSpecialMessages,

View File

@ -68,7 +68,8 @@ loop.shared.views.chat = (function(mozL10n) {
mixins: [React.addons.PureRenderMixin],
propTypes: {
message: React.PropTypes.string.isRequired
message: React.PropTypes.string.isRequired,
useDesktopPaths: React.PropTypes.bool.isRequired
},
render: function() {
@ -97,7 +98,8 @@ loop.shared.views.chat = (function(mozL10n) {
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
messageList: React.PropTypes.array.isRequired
messageList: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
useDesktopPaths: React.PropTypes.bool.isRequired
},
getInitialState: function() {
@ -157,7 +159,12 @@ loop.shared.views.chat = (function(mozL10n) {
if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
switch (entry.contentType) {
case CHAT_CONTENT_TYPES.ROOM_NAME:
return <TextChatRoomName key={i} message={entry.message}/>;
return (
<TextChatRoomName
key={i}
message={entry.message}
useDesktopPaths={this.props.useDesktopPaths} />
);
case CHAT_CONTENT_TYPES.CONTEXT:
return (
<div className="context-url-view-wrapper" key={i}>
@ -168,7 +175,7 @@ loop.shared.views.chat = (function(mozL10n) {
showContextTitle={true}
thumbnail={entry.extraData.thumbnail}
url={entry.extraData.location}
useDesktopPaths={false} />
useDesktopPaths={this.props.useDesktopPaths} />
</div>
);
default:
@ -334,11 +341,8 @@ loop.shared.views.chat = (function(mozL10n) {
* as a field for entering new messages.
*
* @property {loop.Dispatcher} dispatcher
* @property {Boolean} showAlways If false, the view will not be rendered
* if text chat is not enabled and the
* message list is empty.
* @property {Boolean} showRoomName Set to true to show the room name special
* list item.
* @property {Boolean} showRoomName Set to true to show the room name
* special list item.
*/
var TextChatView = React.createClass({
mixins: [
@ -348,8 +352,8 @@ loop.shared.views.chat = (function(mozL10n) {
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
showAlways: React.PropTypes.bool.isRequired,
showRoomName: React.PropTypes.bool.isRequired
showRoomName: React.PropTypes.bool.isRequired,
useDesktopPaths: React.PropTypes.bool.isRequired
},
getInitialState: function() {
@ -366,14 +370,14 @@ loop.shared.views.chat = (function(mozL10n) {
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
});
} else {
// XXX Desktop should be showing the initial context here (bug 1171940).
messageList = this.state.messageList.filter(function(item) {
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME;
});
hasNonSpecialMessages = !!messageList.length;
}
if (!this.props.showAlways && !this.state.textChatEnabled && !messageList.length) {
if (!this.state.textChatEnabled && !messageList.length) {
return null;
}
@ -386,7 +390,8 @@ loop.shared.views.chat = (function(mozL10n) {
<div className={textChatViewClasses}>
<TextChatEntriesView
dispatcher={this.props.dispatcher}
messageList={messageList} />
messageList={messageList}
useDesktopPaths={this.props.useDesktopPaths} />
<TextChatInputView
dispatcher={this.props.dispatcher}
showPlaceholder={!hasNonSpecialMessages}

View File

@ -112,7 +112,6 @@ loop.validate = (function() {
*/
_dependencyMatchTypes: function(value, types) {
return types.some(function(Type) {
/*jshint eqeqeq:false*/
try {
return typeof Type === "undefined" || // skip checking
Type === null && value === null || // null type

View File

@ -26,6 +26,7 @@ loop.shared.views = (function(_, l10n) {
action: React.PropTypes.func.isRequired,
enabled: React.PropTypes.bool.isRequired,
scope: React.PropTypes.string.isRequired,
title: React.PropTypes.string,
type: React.PropTypes.string.isRequired,
visible: React.PropTypes.bool.isRequired
},
@ -54,6 +55,10 @@ loop.shared.views = (function(_, l10n) {
},
_getTitle: function(enabled) {
if (this.props.title) {
return this.props.title;
}
var prefix = this.props.enabled ? "mute" : "unmute";
var suffix = "button_title";
var msgId = [prefix, this.props.scope, this.props.type, suffix].join("_");
@ -183,6 +188,7 @@ loop.shared.views = (function(_, l10n) {
return {
video: {enabled: true, visible: true},
audio: {enabled: true, visible: true},
edit: {enabled: false, visible: false},
screenShare: {state: SCREEN_SHARE_STATES.INACTIVE, visible: false},
enableHangup: true
};
@ -191,9 +197,11 @@ loop.shared.views = (function(_, l10n) {
propTypes: {
audio: React.PropTypes.object.isRequired,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
edit: React.PropTypes.object.isRequired,
enableHangup: React.PropTypes.bool,
hangup: React.PropTypes.func.isRequired,
hangupButtonLabel: React.PropTypes.string,
onEditClick: React.PropTypes.func,
publishStream: React.PropTypes.func.isRequired,
screenShare: React.PropTypes.object,
video: React.PropTypes.object.isRequired
@ -211,6 +219,12 @@ loop.shared.views = (function(_, l10n) {
this.props.publishStream("audio", !this.props.audio.enabled);
},
handleToggleEdit: function() {
if (this.props.onEditClick) {
this.props.onEditClick(!this.props.edit.enabled);
}
},
_getHangupButtonLabel: function() {
return this.props.hangupButtonLabel || l10n.get("hangup_button_caption2");
},
@ -238,10 +252,19 @@ loop.shared.views = (function(_, l10n) {
scope: "local", type: "audio",
visible: this.props.audio.visible})
),
React.createElement("li", {className: "conversation-toolbar-btn-box btn-screen-share-entry"},
React.createElement("li", {className: "conversation-toolbar-btn-box"},
React.createElement(ScreenShareControlButton, {dispatcher: this.props.dispatcher,
state: this.props.screenShare.state,
visible: this.props.screenShare.visible})
),
React.createElement("li", {className: "conversation-toolbar-btn-box btn-edit-entry"},
React.createElement(MediaControlButton, {action: this.handleToggleEdit,
enabled: this.props.edit.enabled,
scope: "local",
title: l10n.get(this.props.edit.enabled ?
"context_edit_tooltip" : "context_hide_tooltip"),
type: "edit",
visible: this.props.edit.visible})
)
)
);
@ -300,7 +323,7 @@ loop.shared.views = (function(_, l10n) {
};
}
this.listenTo(this.props.sdk, "exception", this._handleSdkException.bind(this));
this.listenTo(this.props.sdk, "exception", this._handleSdkException);
this.listenTo(this.props.model, "session:connected",
this._onSessionConnected);
@ -402,14 +425,14 @@ loop.shared.views = (function(_, l10n) {
audio: {enabled: ev.stream.hasAudio},
video: {enabled: ev.stream.hasVideo}
});
}.bind(this));
});
this.listenTo(this.publisher, "streamDestroyed", function() {
this.setState({
audio: {enabled: false},
video: {enabled: false}
});
}.bind(this));
});
this.props.model.publish(this.publisher);
},
@ -519,7 +542,7 @@ loop.shared.views = (function(_, l10n) {
componentDidMount: function() {
this.listenTo(this.props.notifications, "reset add remove", function() {
this.forceUpdate();
}.bind(this));
});
},
componentWillUnmount: function() {

View File

@ -26,6 +26,7 @@ loop.shared.views = (function(_, l10n) {
action: React.PropTypes.func.isRequired,
enabled: React.PropTypes.bool.isRequired,
scope: React.PropTypes.string.isRequired,
title: React.PropTypes.string,
type: React.PropTypes.string.isRequired,
visible: React.PropTypes.bool.isRequired
},
@ -54,6 +55,10 @@ loop.shared.views = (function(_, l10n) {
},
_getTitle: function(enabled) {
if (this.props.title) {
return this.props.title;
}
var prefix = this.props.enabled ? "mute" : "unmute";
var suffix = "button_title";
var msgId = [prefix, this.props.scope, this.props.type, suffix].join("_");
@ -183,6 +188,7 @@ loop.shared.views = (function(_, l10n) {
return {
video: {enabled: true, visible: true},
audio: {enabled: true, visible: true},
edit: {enabled: false, visible: false},
screenShare: {state: SCREEN_SHARE_STATES.INACTIVE, visible: false},
enableHangup: true
};
@ -191,9 +197,11 @@ loop.shared.views = (function(_, l10n) {
propTypes: {
audio: React.PropTypes.object.isRequired,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
edit: React.PropTypes.object.isRequired,
enableHangup: React.PropTypes.bool,
hangup: React.PropTypes.func.isRequired,
hangupButtonLabel: React.PropTypes.string,
onEditClick: React.PropTypes.func,
publishStream: React.PropTypes.func.isRequired,
screenShare: React.PropTypes.object,
video: React.PropTypes.object.isRequired
@ -211,6 +219,12 @@ loop.shared.views = (function(_, l10n) {
this.props.publishStream("audio", !this.props.audio.enabled);
},
handleToggleEdit: function() {
if (this.props.onEditClick) {
this.props.onEditClick(!this.props.edit.enabled);
}
},
_getHangupButtonLabel: function() {
return this.props.hangupButtonLabel || l10n.get("hangup_button_caption2");
},
@ -238,11 +252,20 @@ loop.shared.views = (function(_, l10n) {
scope="local" type="audio"
visible={this.props.audio.visible} />
</li>
<li className="conversation-toolbar-btn-box btn-screen-share-entry">
<li className="conversation-toolbar-btn-box">
<ScreenShareControlButton dispatcher={this.props.dispatcher}
state={this.props.screenShare.state}
visible={this.props.screenShare.visible} />
</li>
<li className="conversation-toolbar-btn-box btn-edit-entry">
<MediaControlButton action={this.handleToggleEdit}
enabled={this.props.edit.enabled}
scope="local"
title={l10n.get(this.props.edit.enabled ?
"context_edit_tooltip" : "context_hide_tooltip")}
type="edit"
visible={this.props.edit.visible} />
</li>
</ul>
);
}
@ -300,7 +323,7 @@ loop.shared.views = (function(_, l10n) {
};
}
this.listenTo(this.props.sdk, "exception", this._handleSdkException.bind(this));
this.listenTo(this.props.sdk, "exception", this._handleSdkException);
this.listenTo(this.props.model, "session:connected",
this._onSessionConnected);
@ -402,14 +425,14 @@ loop.shared.views = (function(_, l10n) {
audio: {enabled: ev.stream.hasAudio},
video: {enabled: ev.stream.hasVideo}
});
}.bind(this));
});
this.listenTo(this.publisher, "streamDestroyed", function() {
this.setState({
audio: {enabled: false},
video: {enabled: false}
});
}.bind(this));
});
this.props.model.publish(this.publisher);
},
@ -519,7 +542,7 @@ loop.shared.views = (function(_, l10n) {
componentDidMount: function() {
this.listenTo(this.props.notifications, "reset add remove", function() {
this.forceUpdate();
}.bind(this));
});
},
componentWillUnmount: function() {

View File

@ -15,7 +15,7 @@ const LOOP_SESSION_TYPE = {
FXA: 2
};
/***
/**
* Values that we segment 2-way media connection length telemetry probes
* into.
*
@ -614,7 +614,8 @@ let MozLoopServiceInternal = {
return this.hawkRequestInternal(sessionType, path, method, payloadObj, false);
},
() => {
return handle401Error(error); //Process the original error that triggered the retry.
// Process the original error that triggered the retry.
return handle401Error(error);
}
);
}
@ -889,7 +890,10 @@ let MozLoopServiceInternal = {
// When the chat box or messages are shown, resize the panel or window
// to be slightly higher to accomodate them.
let customSize = kSizeMap[ev.type];
if (customSize) {
let currSize = chatbox.getAttribute("customSize");
// If the size is already at the requested one or at the maximum size
// already, don't do anything. Especially don't make it shrink.
if (customSize && currSize != customSize && currSize != "loopChatMessageAppended") {
chatbox.setAttribute("customSize", customSize);
chatbox.parentNode.setAttribute("customSize", customSize);
}
@ -916,10 +920,10 @@ let MozLoopServiceInternal = {
// Not ideal but insert our data amidst existing data like this:
// - 000 (id=00 url=http)
// + 000 (session=000 call=000 id=00 url=http)
var pair = pc.id.split("("); //)
var pair = pc.id.split("(");
if (pair.length == 2) {
pc.id = pair[0] + "(session=" + context.sessionId +
(context.callId ? " call=" + context.callId : "") + " " + pair[1]; //)
(context.callId ? " call=" + context.callId : "") + " " + pair[1];
}
}

View File

@ -394,9 +394,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
* @private
*/
_shouldRenderRemoteLoading: function() {
return this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
!this.state.remoteSrcVideoObject &&
!this.state.mediaConnected;
return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
!this.state.remoteSrcVideoObject &&
!this.state.mediaConnected);
},
/**
@ -464,7 +464,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
React.createElement(sharedViews.chat.TextChatView, {
dispatcher: this.props.dispatcher,
showAlways: true,
showRoomName: true}),
showRoomName: true,
useDesktopPaths: false}),
React.createElement("div", {className: "local"},
React.createElement(sharedViews.MediaView, {displayAvatar: this.state.videoMuted,
isLoading: this._shouldRenderLocalLoading(),
@ -477,6 +478,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
audio: {enabled: !this.state.audioMuted,
visible: this._roomIsActive()},
dispatcher: this.props.dispatcher,
edit: { visible: false, enabled: false},
enableHangup: this._roomIsActive(),
hangup: this.leaveRoom,
hangupButtonLabel: mozL10n.get("rooms_leave_button_label"),

View File

@ -394,9 +394,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
* @private
*/
_shouldRenderRemoteLoading: function() {
return this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
!this.state.remoteSrcVideoObject &&
!this.state.mediaConnected;
return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
!this.state.remoteSrcVideoObject &&
!this.state.mediaConnected);
},
/**
@ -464,7 +464,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
<sharedViews.chat.TextChatView
dispatcher={this.props.dispatcher}
showAlways={true}
showRoomName={true} />
showRoomName={true}
useDesktopPaths={false} />
<div className="local">
<sharedViews.MediaView displayAvatar={this.state.videoMuted}
isLoading={this._shouldRenderLocalLoading()}
@ -477,6 +478,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
audio={{enabled: !this.state.audioMuted,
visible: this._roomIsActive()}}
dispatcher={this.props.dispatcher}
edit={{ visible: false, enabled: false }}
enableHangup={this._roomIsActive()}
hangup={this.leaveRoom}
hangupButtonLabel={mozL10n.get("rooms_leave_button_label")}

View File

@ -223,7 +223,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
var urlCreationDateClasses = cx({
"light-color-font": true,
"call-url-date": true, /* Used as a handler in the tests */
/*hidden until date is available*/
// Hidden until date is available.
"hide": !this.props.urlCreationDateString.length
});

View File

@ -223,7 +223,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
var urlCreationDateClasses = cx({
"light-color-font": true,
"call-url-date": true, /* Used as a handler in the tests */
/*hidden until date is available*/
// Hidden until date is available.
"hide": !this.props.urlCreationDateString.length
});

View File

@ -12,8 +12,8 @@
},
"dependencies": {},
"devDependencies": {
"eslint": "0.21.x",
"eslint-plugin-react": "2.3.x",
"eslint": "0.24.x",
"eslint-plugin-react": "2.6.x",
"express": "4.x"
},
"scripts": {

View File

@ -146,7 +146,8 @@ describe("loop.contacts", function() {
it("should show the gravatars promo box", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
notifications: notifications
notifications: notifications,
startForm: function() {}
}));
var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
@ -166,7 +167,8 @@ describe("loop.contacts", function() {
};
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
notifications: notifications
notifications: notifications,
startForm: function() {}
}));
var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
@ -178,7 +180,8 @@ describe("loop.contacts", function() {
it("should hide the gravatars promo box when the 'use' button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
notifications: notifications
notifications: notifications,
startForm: function() {}
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
@ -193,7 +196,8 @@ describe("loop.contacts", function() {
it("should should set the prefs correctly when the 'use' button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
notifications: notifications
notifications: notifications,
startForm: function() {}
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
@ -207,7 +211,8 @@ describe("loop.contacts", function() {
it("should hide the gravatars promo box when the 'close' button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
notifications: notifications
notifications: notifications,
startForm: function() {}
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
@ -220,7 +225,8 @@ describe("loop.contacts", function() {
it("should set prefs correctly when the 'close' button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
notifications: notifications
notifications: notifications,
startForm: function() {}
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
@ -242,7 +248,8 @@ describe("loop.contacts", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
notifications: notifications
notifications: notifications,
startForm: function() {}
}));
});
@ -299,8 +306,10 @@ describe("loop.contacts", function() {
beforeEach(function() {
view = TestUtils.renderIntoDocument(
React.createElement(
loop.contacts.ContactDetailsForm, {mode: "add"}));
React.createElement(loop.contacts.ContactDetailsForm, {
mode: "add",
selectTab: function() {}
}));
});
it("should render 'add' header", function() {
@ -409,8 +418,10 @@ describe("loop.contacts", function() {
beforeEach(function() {
view = TestUtils.renderIntoDocument(
React.createElement(
loop.contacts.ContactDetailsForm, {mode: "edit"}));
React.createElement(loop.contacts.ContactDetailsForm, {
mode: "edit",
selectTab: function() {}
}));
});
it("should render 'edit' header", function() {

View File

@ -18,19 +18,6 @@ describe("loop.conversationViews", function () {
var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
// XXX refactor to Just Work with "sandbox.stubComponent" or else
// just pass in the sandbox and put somewhere generally usable
function stubComponent(obj, component, mockTagName){
var reactClass = React.createClass({
render: function() {
var tagName = mockTagName || "div";
return React.DOM[tagName](null, this.props.children);
}
});
return sandbox.stub(obj, component, reactClass);
}
beforeEach(function() {
sandbox = sinon.sandbox.create();
sandbox.useFakeTimers();

View File

@ -46,7 +46,10 @@ describe("loop.conversation", function() {
},
getAudioBlob: sinon.spy(function(name, callback) {
callback(null, new Blob([new ArrayBuffer(10)], {type: "audio/ogg"}));
})
}),
getSelectedTabMetadata: function(callback) {
callback({});
}
};
fakeWindow = {

View File

@ -97,7 +97,7 @@
describe("Unexpected Warnings Check", function() {
it("should long only the warnings we expect", function() {
chai.expect(caughtWarnings.length).to.eql(128);
chai.expect(caughtWarnings.length).to.eql(30);
});
});

View File

@ -778,7 +778,7 @@ describe("loop.panel", function() {
"conversation button",
function() {
navigator.mozLoop.userProfile = {email: fakeEmail};
var view = createTestComponent();
var view = createTestComponent(false);
TestUtils.Simulate.click(view.getDOMNode().querySelector(".new-room-button"));
@ -801,7 +801,7 @@ describe("loop.panel", function() {
});
};
var view = createTestComponent();
var view = createTestComponent(false);
// Simulate being visible
view.onDocumentVisible();
@ -841,7 +841,7 @@ describe("loop.panel", function() {
});
};
var view = createTestComponent();
var view = createTestComponent(false);
// Simulate being visible
view.onDocumentVisible();
@ -859,7 +859,7 @@ describe("loop.panel", function() {
});
};
var view = createTestComponent();
var view = createTestComponent(false);
view.setState({ checked: true });
@ -878,7 +878,7 @@ describe("loop.panel", function() {
});
};
var view = createTestComponent();
var view = createTestComponent(false);
// Simulate being visible
view.onDocumentVisible();
@ -896,7 +896,7 @@ describe("loop.panel", function() {
});
};
var view = createTestComponent();
var view = createTestComponent(false);
view.onDocumentVisible();
@ -913,7 +913,7 @@ describe("loop.panel", function() {
});
};
var view = createTestComponent();
var view = createTestComponent(false);
// Simulate being visible
view.onDocumentVisible();
@ -933,7 +933,7 @@ describe("loop.panel", function() {
});
};
var view = createTestComponent();
var view = createTestComponent(false);
// Simulate being visible.
view.onDocumentVisible();

View File

@ -141,8 +141,9 @@ describe("loop.roomViews", function () {
dispatcher: dispatcher,
mozLoop: fakeMozLoop,
roomData: {},
savingContext: false,
show: true,
showContext: false
showEditContext: false
}, props);
return TestUtils.renderIntoDocument(
React.createElement(loop.roomViews.DesktopRoomInvitationView, props));
@ -248,76 +249,37 @@ describe("loop.roomViews", function () {
});
});
describe("Context", function() {
it("should not render the context data when told not to", function() {
describe("Edit Context", function() {
it("should show the 'Add some context' link", function() {
view = mountTestComponent();
expect(view.getDOMNode().querySelector(".room-context")).to.eql(null);
expect(view.getDOMNode().querySelector(".room-invitation-addcontext")).
to.not.eql(null);
});
it("should render context when data is available", function() {
it("should call a callback when the link is clicked", function() {
var onAddContextClick = sinon.stub();
view = mountTestComponent({
showContext: true,
roomData: {
roomContextUrls: [fakeContextURL]
}
onAddContextClick: onAddContextClick
});
var node = view.getDOMNode();
expect(node.querySelector(".room-context")).to.eql(null);
var addLink = node.querySelector(".room-invitation-addcontext");
React.addons.TestUtils.Simulate.click(addLink);
sinon.assert.calledOnce(onAddContextClick);
});
it("should show the edit context view", function() {
view = mountTestComponent({
showEditContext: true
});
expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null);
});
it("should render the context in editMode when the pencil is clicked", function() {
view = mountTestComponent({
showContext: true,
roomData: {
roomContextUrls: [fakeContextURL]
}
});
var pencil = view.getDOMNode().querySelector(".room-context-btn-edit");
expect(pencil).to.not.eql(null);
React.addons.TestUtils.Simulate.click(pencil);
expect(view.state.editMode).to.eql(true);
var node = view.getDOMNode();
expect(node.querySelector("form")).to.not.eql(null);
// No text paragraphs should be visible in editMode.
var visiblePs = Array.slice(node.querySelector("p")).filter(function(p) {
return p.classList.contains("hide") || p.classList.contains("error");
});
expect(visiblePs.length).to.eql(0);
});
it("should format the context url for display", function() {
sandbox.stub(sharedUtils, "formatURL").returns({
location: "location",
hostname: "hostname"
});
view = mountTestComponent({
showContext: true,
roomData: {
roomContextUrls: [fakeContextURL]
}
});
expect(view.getDOMNode().querySelector(".room-context-url").textContent)
.eql("hostname");
});
it("should show a default favicon when none is available", function() {
fakeContextURL.thumbnail = null;
view = mountTestComponent({
showContext: true,
roomData: {
roomContextUrls: [fakeContextURL]
}
});
expect(view.getDOMNode().querySelector(".room-context-thumbnail").src)
.to.match(/loop\/shared\/img\/icons-16x16.svg#globe$/);
});
});
});
@ -331,6 +293,12 @@ describe("loop.roomViews", function () {
})
});
sandbox.stub(dispatcher, "dispatch");
fakeMozLoop.getLoopPref = function(prefName) {
if (prefName == "contextInConversations.enabled") {
return true;
}
return "test";
};
});
function mountTestComponent() {
@ -643,6 +611,32 @@ describe("loop.roomViews", function () {
.not.eql(null);
});
});
describe("Edit Context", function() {
it("should show the form when the edit button is clicked", function() {
view = mountTestComponent();
var node = view.getDOMNode();
expect(node.querySelector(".room-context")).to.eql(null);
var editButton = node.querySelector(".btn-mute-edit");
React.addons.TestUtils.Simulate.click(editButton);
expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null);
});
it("should hide the form when the edit button is clicked again", function() {
view = mountTestComponent();
var editButton = view.getDOMNode().querySelector(".btn-mute-edit");
React.addons.TestUtils.Simulate.click(editButton);
// Click again.
React.addons.TestUtils.Simulate.click(editButton);
expect(view.getDOMNode().querySelector(".room-context")).to.eql(null);
});
});
});
describe("SocialShareDropdown", function() {
@ -741,7 +735,7 @@ describe("loop.roomViews", function () {
});
});
describe("DesktopRoomContextView", function() {
describe("DesktopRoomEditContextView", function() {
var view;
afterEach(function() {
@ -759,35 +753,10 @@ describe("loop.roomViews", function () {
}
}, props);
return TestUtils.renderIntoDocument(
React.createElement(loop.roomViews.DesktopRoomContextView, props));
React.createElement(loop.roomViews.DesktopRoomEditContextView, props));
}
describe("#render", function() {
it("should show the context information properly when available", function() {
view = mountTestComponent({
roomData: {
roomDescription: "Hello, is it me you're looking for?",
roomContextUrls: [fakeContextURL]
}
});
var node = view.getDOMNode();
expect(node).to.not.eql(null);
expect(node.querySelector(".room-context-thumbnail").src).to.
eql(fakeContextURL.thumbnail);
expect(node.querySelector(".room-context-description").firstChild.textContent).
to.eql(fakeContextURL.description);
});
it("should not render optional data", function() {
view = mountTestComponent({
roomData: { roomContextUrls: [fakeContextURL] }
});
expect(view.getDOMNode().querySelector(".room-context-comment")).to.
eql(null);
});
it("should not render the component when 'show' is false", function() {
view = mountTestComponent({
show: false
@ -806,10 +775,9 @@ describe("loop.roomViews", function () {
expect(view.getDOMNode()).to.eql(null);
});
it("should render the view in editMode when appropriate", function() {
it("should render the view correctly", function() {
var roomName = "Hello, is it me you're looking for?";
view = mountTestComponent({
editMode: true,
roomData: {
roomName: roomName,
roomContextUrls: [fakeContextURL]
@ -826,7 +794,6 @@ describe("loop.roomViews", function () {
it("should show the checkbox as disabled when context is already set", function() {
view = mountTestComponent({
editMode: true,
roomData: {
roomToken: "fakeToken",
roomName: "fakeName",
@ -838,34 +805,7 @@ describe("loop.roomViews", function () {
expect(checkbox.classList.contains("disabled")).to.eql(true);
});
it("should render the editMode view when the edit button is clicked", function(done) {
var roomName = "Hello, is it me you're looking for?";
view = mountTestComponent({
roomData: {
roomToken: "fakeToken",
roomName: roomName,
roomContextUrls: [fakeContextURL]
}
});
// Switch to editMode via setting the prop, since we can control that
// better.
view.setProps({ editMode: true }, function() {
// First check if availableContext is set correctly.
expect(view.state.availableContext).to.not.eql(null);
expect(view.state.availableContext.previewImage).to.eql(favicon);
var node = view.getDOMNode();
expect(node.querySelector(".checkbox-wrapper").classList.contains("disabled")).to.eql(true);
expect(node.querySelector(".room-context-name").value).to.eql(roomName);
expect(node.querySelector(".room-context-url").value).to.eql(fakeContextURL.location);
expect(node.querySelector(".room-context-comments").value).to.eql(fakeContextURL.description);
done();
});
});
it("should hide the checkbox when no context data is stored or available", function(done) {
it("should hide the checkbox when no context data is stored or available", function() {
view = mountTestComponent({
roomData: {
roomToken: "fakeToken",
@ -873,18 +813,12 @@ describe("loop.roomViews", function () {
}
});
// Switch to editMode via setting the prop, since we can control that
// better.
view.setProps({ editMode: true }, function() {
// First check if availableContext is set correctly.
expect(view.state.availableContext).to.not.eql(null);
expect(view.state.availableContext.previewImage).to.eql(favicon);
// First check if availableContext is set correctly.
expect(view.state.availableContext).to.not.eql(null);
expect(view.state.availableContext.previewImage).to.eql(favicon);
var node = view.getDOMNode();
expect(node.querySelector(".checkbox-wrapper").classList.contains("hide")).to.eql(true);
done();
});
var node = view.getDOMNode();
expect(node.querySelector(".checkbox-wrapper").classList.contains("hide")).to.eql(true);
});
});
@ -952,8 +886,8 @@ describe("loop.roomViews", function () {
// Now simulate a successful save.
view.setProps({ savingContext: false }, function() {
// The editMode flag should be updated.
expect(view.state.editMode).to.eql(false);
// The 'show flag should be updated.
expect(view.state.show).to.eql(false);
done();
});
});
@ -964,13 +898,12 @@ describe("loop.roomViews", function () {
var node, checkbox;
beforeEach(function() {
fakeMozLoop.getSelectedTabMetadata = sinon.stub().callsArgWith(0, {
favicon: fakeContextURL.thumbnail,
title: fakeContextURL.description,
url: fakeContextURL.location
});
view = mountTestComponent({
availableContext: {
description: fakeContextURL.description,
previewImage: fakeContextURL.thumbnail,
url: fakeContextURL.location
},
editMode: true,
roomData: {
roomToken: "fakeToken",
roomName: "fakeName"

View File

@ -96,7 +96,7 @@
describe("Unexpected Warnings Check", function() {
it("should long only the warnings we expect", function() {
chai.expect(caughtWarnings.length).to.eql(180);
chai.expect(caughtWarnings.length).to.eql(73);
});
});

View File

@ -204,6 +204,52 @@ describe("loop.store.TextChatStore", function () {
]);
});
it("should not add more than one context message", function() {
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomOwner: "Mark",
roomUrl: "fake",
urls: [{
description: "A wonderful event",
location: "http://wonderful.invalid",
thumbnail: "fake"
}]
}));
expect(store.getStoreState("messageList")).eql([{
type: CHAT_MESSAGE_TYPES.SPECIAL,
contentType: CHAT_CONTENT_TYPES.CONTEXT,
message: "A wonderful event",
sentTimestamp: undefined,
receivedTimestamp: undefined,
extraData: {
location: "http://wonderful.invalid",
thumbnail: "fake"
}
}]);
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomOwner: "Mark",
roomUrl: "fake",
urls: [{
description: "A wonderful event2",
location: "http://wonderful.invalid2",
thumbnail: "fake2"
}]
}));
expect(store.getStoreState("messageList")).eql([{
type: CHAT_MESSAGE_TYPES.SPECIAL,
contentType: CHAT_CONTENT_TYPES.CONTEXT,
message: "A wonderful event2",
sentTimestamp: undefined,
receivedTimestamp: undefined,
extraData: {
location: "http://wonderful.invalid2",
thumbnail: "fake2"
}
}]);
});
it("should not dispatch a LoopChatMessageAppended event", function() {
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "Let's share!",

View File

@ -43,7 +43,8 @@ describe("loop.shared.views.TextChatView", function () {
function mountTestComponent(extraProps) {
var basicProps = {
dispatcher: dispatcher,
messageList: []
messageList: [],
useDesktopPaths: false
};
return TestUtils.renderIntoDocument(
@ -56,11 +57,13 @@ describe("loop.shared.views.TextChatView", function () {
messageList: [{
type: CHAT_MESSAGE_TYPES.RECEIVED,
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!"
message: "Hello!",
receivedTimestamp: "2015-06-25T17:53:55.357Z"
}, {
type: CHAT_MESSAGE_TYPES.SENT,
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Is it me you're looking for?"
message: "Is it me you're looking for?",
sentTimestamp: "2015-06-25T17:53:55.357Z"
}]
});
@ -81,7 +84,8 @@ describe("loop.shared.views.TextChatView", function () {
messageList: [{
type: CHAT_MESSAGE_TYPES.RECEIVED,
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!"
message: "Hello!",
receivedTimestamp: "2015-06-25T17:53:55.357Z"
}]
});
@ -97,7 +101,8 @@ describe("loop.shared.views.TextChatView", function () {
messageList: [{
type: CHAT_MESSAGE_TYPES.SPECIAL,
contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
message: "Hello!"
message: "Hello!",
receivedTimestamp: "2015-06-25T17:53:55.357Z"
}]
});
@ -112,7 +117,8 @@ describe("loop.shared.views.TextChatView", function () {
messageList: [{
type: CHAT_MESSAGE_TYPES.SENT,
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!"
message: "Hello!",
sentTimestamp: "2015-06-25T17:53:55.357Z"
}]
});
@ -125,7 +131,11 @@ describe("loop.shared.views.TextChatView", function () {
function mountTestComponent(extraProps) {
var props = _.extend({
dispatcher: dispatcher
contentType: CHAT_CONTENT_TYPES.TEXT,
dispatcher: dispatcher,
message: "test",
type: CHAT_MESSAGE_TYPES.RECEIVED,
timestamp: "2015-06-23T22:48:39.738Z"
}, extraProps);
return TestUtils.renderIntoDocument(
React.createElement(loop.shared.views.chat.TextChatEntry, props));
@ -133,8 +143,7 @@ describe("loop.shared.views.TextChatView", function () {
it("should not render a timestamp", function() {
view = mountTestComponent({
showTimestamp: false,
timestamp: "2015-06-23T22:48:39.738Z"
showTimestamp: false
});
var node = view.getDOMNode();
@ -143,8 +152,7 @@ describe("loop.shared.views.TextChatView", function () {
it("should render a timestamp", function() {
view = mountTestComponent({
showTimestamp: true,
timestamp: "2015-06-23T22:48:39.738Z"
showTimestamp: true
});
var node = view.getDOMNode();
@ -157,7 +165,9 @@ describe("loop.shared.views.TextChatView", function () {
function mountTestComponent(extraProps) {
var props = _.extend({
dispatcher: dispatcher
dispatcher: dispatcher,
messageList: [],
useDesktopPaths: false
}, extraProps);
return TestUtils.renderIntoDocument(
React.createElement(loop.shared.views.chat.TextChatEntriesView, props));
@ -172,11 +182,13 @@ describe("loop.shared.views.TextChatView", function () {
messageList: [{
type: CHAT_MESSAGE_TYPES.RECEIVED,
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!"
message: "Hello!",
receivedTimestamp: "2015-06-25T17:53:55.357Z"
}, {
type: CHAT_MESSAGE_TYPES.SENT,
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Is it me you're looking for?"
message: "Is it me you're looking for?",
sentTimestamp: "2015-06-25T17:53:55.357Z"
}]
});
node = view.getDOMNode();
@ -230,11 +242,13 @@ describe("loop.shared.views.TextChatView", function () {
messageList: [{
type: CHAT_MESSAGE_TYPES.RECEIVED,
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!"
message: "Hello!",
receivedTimestamp: "2015-06-25T17:53:55.357Z"
}, {
type: CHAT_MESSAGE_TYPES.RECEIVED,
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Is it me you're looking for?"
message: "Is it me you're looking for?",
sentTimestamp: "2015-06-25T17:53:55.357Z"
}]
});
node = view.getDOMNode();
@ -249,7 +263,9 @@ describe("loop.shared.views.TextChatView", function () {
function mountTestComponent(extraProps) {
var props = _.extend({
dispatcher: dispatcher
dispatcher: dispatcher,
showRoomName: false,
useDesktopPaths: false
}, extraProps);
return TestUtils.renderIntoDocument(
React.createElement(loop.shared.views.chat.TextChatView, props));
@ -288,30 +304,16 @@ describe("loop.shared.views.TextChatView", function () {
.to.eql(2);
});
it("should not display anything if no messages and text chat not enabled and showAlways is false", function() {
it("should not display the view if no messages and text chat not enabled", function() {
store.setStoreState({ textChatEnabled: false });
view = mountTestComponent({
showAlways: false
});
view = mountTestComponent();
expect(view.getDOMNode()).eql(null);
});
it("should display the view if no messages and text chat not enabled and showAlways is true", function() {
store.setStoreState({ textChatEnabled: false });
view = mountTestComponent({
showAlways: true
});
expect(view.getDOMNode()).not.eql(null);
});
it("should display the view if text chat is enabled", function() {
view = mountTestComponent({
showAlways: true
});
it("should display the view if no messages and text chat is enabled", function() {
view = mountTestComponent();
expect(view.getDOMNode()).not.eql(null);
});
@ -330,11 +332,15 @@ describe("loop.shared.views.TextChatView", function () {
store.receivedTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!"
message: "Hello!",
sentTimestamp: "1970-01-01T00:03:00.000Z",
receivedTimestamp: "1970-01-01T00:03:00.000Z"
});
store.sendTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Is it me you're looking for?"
message: "Is it me you're looking for?",
sentTimestamp: "1970-01-01T00:03:00.000Z",
receivedTimestamp: "1970-01-01T00:03:00.000Z"
});
var node = view.getDOMNode();
@ -359,17 +365,18 @@ describe("loop.shared.views.TextChatView", function () {
});
it("should add `received` CSS class selector to msg of type RECEIVED",
function() {
var node = mountTestComponent().getDOMNode();
function() {
var node = mountTestComponent().getDOMNode();
store.receivedTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Foo",
timestamp: 0
});
store.receivedTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Foo",
sentTimestamp: "1970-01-01T00:03:00.000Z",
receivedTimestamp: "1970-01-01T00:03:00.000Z"
});
expect(node.querySelector(".received")).to.not.eql(null);
});
expect(node.querySelector(".received")).to.not.eql(null);
});
it("should render a room name special entry", function() {
view = mountTestComponent({
@ -392,9 +399,7 @@ describe("loop.shared.views.TextChatView", function () {
});
it("should render a special entry for the context url", function() {
view = mountTestComponent({
showRoomName: true
});
view = mountTestComponent();
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "A Very Long Conversation Name",

View File

@ -256,6 +256,9 @@ describe("loop.shared.views", function() {
var hangup, publishStream;
function mountTestComponent(props) {
props = _.extend({
dispatcher: dispatcher
}, props || {});
return TestUtils.renderIntoDocument(
React.createElement(sharedViews.ConversationToolbar, props));
}
@ -362,6 +365,9 @@ describe("loop.shared.views", function() {
var fakeSDK, fakeSessionData, fakeSession, fakePublisher, model, fakeAudio;
function mountTestComponent(props) {
props = _.extend({
dispatcher: dispatcher
}, props || {});
return TestUtils.renderIntoDocument(
React.createElement(sharedViews.ConversationView, props));
}
@ -676,6 +682,9 @@ describe("loop.shared.views", function() {
var coll, view, testNotif;
function mountTestComponent(props) {
props = _.extend({
key: 0
}, props || {});
return TestUtils.renderIntoDocument(
React.createElement(sharedViews.NotificationListView, props));
}
@ -831,7 +840,11 @@ describe("loop.shared.views", function() {
function mountTestComponent(extraProps) {
var props = _.extend({
dispatcher: dispatcher
allowClick: false,
description: "test",
dispatcher: dispatcher,
showContextTitle: false,
useDesktopPaths: false
}, extraProps);
return TestUtils.renderIntoDocument(
React.createElement(sharedViews.ContextUrlView, props));
@ -913,6 +926,9 @@ describe("loop.shared.views", function() {
var view;
function mountTestComponent(props) {
props = _.extend({
isLoading: false
}, props || {});
return TestUtils.renderIntoDocument(
React.createElement(sharedViews.MediaView, props));
}

View File

@ -88,7 +88,7 @@
describe("Unexpected Warnings Check", function() {
it("should long only the warnings we expect", function() {
chai.expect(caughtWarnings.length).to.eql(37);
chai.expect(caughtWarnings.length).to.eql(36);
});
});

View File

@ -93,7 +93,8 @@ add_test(function test_reconnect_websocket() {
// The uaID is cleared to force re-regsitration of all notification channels.
add_test(function test_reopen_websocket() {
MozLoopPushHandler.uaID = undefined;
MozLoopPushHandler.registeredChannels = {}; //Do this to force a new registration callback.
// Do this to force a new registration callback.
MozLoopPushHandler.registeredChannels = {};
mockWebSocket.serverClose();
// Previously registered onRegistration callbacks will fire and be checked (see above).
});

View File

@ -25,6 +25,10 @@ window.queuedFrames = [];
*/
window.Frame = React.createClass({
propTypes: {
children: React.PropTypes.oneOfType([
React.PropTypes.element,
React.PropTypes.arrayOf(React.PropTypes.element)
]).isRequired,
className: React.PropTypes.string,
/* By default, <link rel="stylesheet> nodes from the containing frame's
head will be cloned into this iframe. However, if the link also has

View File

@ -1243,8 +1243,8 @@
width: 298},
React.createElement("div", {className: "fx-embedded"},
React.createElement(TextChatView, {dispatcher: dispatcher,
showAlways: false,
showRoomName: false})
showRoomName: false,
useDesktopPaths: false})
)
),
@ -1257,8 +1257,8 @@
React.createElement("div", {className: "media-wrapper"},
React.createElement(TextChatView, {
dispatcher: dispatcher,
showAlways: true,
showRoomName: true})
showRoomName: true,
useDesktopPaths: false})
)
)
)
@ -1313,7 +1313,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 = 53;
var expectedWarningsCount = 29;
var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
if (uncaughtError || warningsMismatch) {
$("#results").append("<div class='failures'><em>" +

View File

@ -1243,8 +1243,8 @@
width={298}>
<div className="fx-embedded">
<TextChatView dispatcher={dispatcher}
showAlways={false}
showRoomName={false} />
showRoomName={false}
useDesktopPaths={false} />
</div>
</FramedExample>
@ -1257,8 +1257,8 @@
<div className="media-wrapper">
<TextChatView
dispatcher={dispatcher}
showAlways={true}
showRoomName={true} />
showRoomName={true}
useDesktopPaths={false} />
</div>
</div>
</FramedExample>
@ -1313,7 +1313,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 = 53;
var expectedWarningsCount = 29;
var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
if (uncaughtError || warningsMismatch) {
$("#results").append("<div class='failures'><em>" +

View File

@ -37,10 +37,13 @@ XPIDL_MODULE = 'browsercompsbase'
EXTRA_PP_COMPONENTS += [
'BrowserComponents.manifest',
'nsBrowserContentHandler.js',
'nsBrowserGlue.js',
]
EXTRA_COMPONENTS += [
'nsBrowserContentHandler.js',
]
EXTRA_JS_MODULES += [
'distribution.js',
]

View File

@ -1,14 +1,17 @@
# 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/.
/* 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/. */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
"@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
const nsISupports = Components.interfaces.nsISupports;
@ -40,9 +43,6 @@ const NS_BINDING_ABORTED = Components.results.NS_BINDING_ABORTED;
const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
const NS_ERROR_ABORT = Components.results.NS_ERROR_ABORT;
const URI_INHERITS_SECURITY_CONTEXT = Components.interfaces.nsIHttpProtocolHandler
.URI_INHERITS_SECURITY_CONTEXT;
function shouldLoadURI(aURI) {
if (aURI && !aURI.schemeIs("chrome"))
return true;
@ -390,14 +390,22 @@ nsBrowserContentHandler.prototype = {
openPreferences();
cmdLine.preventDefault = true;
} else try {
// only load URIs which do not inherit chrome privs
var features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
var uri = resolveURIInternal(cmdLine, chromeParam);
var netutil = Components.classes["@mozilla.org/network/util;1"]
.getService(nsINetUtil);
if (!netutil.URIChainHasFlags(uri, URI_INHERITS_SECURITY_CONTEXT)) {
let isLocal = (uri) => {
let localSchemes = new Set(["chrome", "file", "resource"]);
if (uri instanceof Components.interfaces.nsINestedURI) {
uri = uri.QueryInterface(Components.interfaces.nsINestedURI).innerMostURI;
}
return localSchemes.has(uri.scheme);
};
if (isLocal(uri)) {
// If the URI is local, we are sure it won't wrongly inherit chrome privs
var features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
openWindow(null, uri.spec, "_blank", features);
cmdLine.preventDefault = true;
} else {
dump("*** Preventing load of web URI as chrome\n");
dump(" If you're trying to load a webpage, do not pass --chrome.\n");
}
}
catch (e) {
@ -452,31 +460,35 @@ nsBrowserContentHandler.prototype = {
cmdLine.preventDefault = true;
}
#ifdef XP_WIN
// Handle "? searchterm" for Windows Vista start menu integration
for (var i = cmdLine.length - 1; i >= 0; --i) {
var param = cmdLine.getArgument(i);
if (param.match(/^\? /)) {
cmdLine.removeArguments(i, i);
cmdLine.preventDefault = true;
if (AppConstants.platform == "win") {
// Handle "? searchterm" for Windows Vista start menu integration
for (var i = cmdLine.length - 1; i >= 0; --i) {
var param = cmdLine.getArgument(i);
if (param.match(/^\? /)) {
cmdLine.removeArguments(i, i);
cmdLine.preventDefault = true;
searchParam = param.substr(2);
doSearch(searchParam, cmdLine);
searchParam = param.substr(2);
doSearch(searchParam, cmdLine);
}
}
}
#endif
},
helpInfo : " --browser Open a browser window.\n" +
" --new-window <url> Open <url> in a new window.\n" +
" --new-tab <url> Open <url> in a new tab.\n" +
" --private-window <url> Open <url> in a new private window.\n" +
#ifdef XP_WIN
" --preferences Open Options dialog.\n" +
#else
" --preferences Open Preferences dialog.\n" +
#endif
" --search <term> Search <term> with your default search engine.\n",
get helpInfo() {
let info =
" --browser Open a browser window.\n" +
" --new-window <url> Open <url> in a new window.\n" +
" --new-tab <url> Open <url> in a new tab.\n" +
" --private-window <url> Open <url> in a new private window.\n";
if (AppConstants.platform == "win") {
info += " --preferences Open Options dialog.\n";
} else {
info += " --preferences Open Preferences dialog.\n";
}
info += " --search <term> Search <term> with your default search engine.\n";
return info;
},
/* nsIBrowserHandler */
@ -687,35 +699,33 @@ nsDefaultCommandLineHandler.prototype = {
return this;
},
#ifdef XP_WIN
_haveProfile: false,
#endif
/* nsICommandLineHandler */
handle : function dch_handle(cmdLine) {
var urilist = [];
#ifdef XP_WIN
// If we don't have a profile selected yet (e.g. the Profile Manager is
// displayed) we will crash if we open an url and then select a profile. To
// prevent this handle all url command line flags and set the command line's
// preventDefault to true to prevent the display of the ui. The initial
// command line will be retained when nsAppRunner calls LaunchChild though
// urls launched after the initial launch will be lost.
if (!this._haveProfile) {
try {
// This will throw when a profile has not been selected.
var fl = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties);
var dir = fl.get("ProfD", Components.interfaces.nsILocalFile);
this._haveProfile = true;
}
catch (e) {
while ((ar = cmdLine.handleFlagWithParam("url", false))) { }
cmdLine.preventDefault = true;
if (AppConstants.platform == "win") {
// If we don't have a profile selected yet (e.g. the Profile Manager is
// displayed) we will crash if we open an url and then select a profile. To
// prevent this handle all url command line flags and set the command line's
// preventDefault to true to prevent the display of the ui. The initial
// command line will be retained when nsAppRunner calls LaunchChild though
// urls launched after the initial launch will be lost.
if (!this._haveProfile) {
try {
// This will throw when a profile has not been selected.
var fl = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties);
var dir = fl.get("ProfD", Components.interfaces.nsILocalFile);
this._haveProfile = true;
}
catch (e) {
while ((ar = cmdLine.handleFlagWithParam("url", false))) { }
cmdLine.preventDefault = true;
}
}
}
#endif
try {
var ar;
@ -767,6 +777,16 @@ nsDefaultCommandLineHandler.prototype = {
}
else if (!cmdLine.preventDefault) {
if (AppConstants.isPlatformAndVersionAtLeast("win", "10") &&
cmdLine.state != nsICommandLine.STATE_INITIAL_LAUNCH &&
WindowsUIUtils.inTabletMode) {
// In windows 10 tablet mode, do not create a new window, but reuse the existing one.
let win = RecentWindow.getMostRecentBrowserWindow();
if (win) {
win.focus();
return;
}
}
// Passing defaultArgs, so use NO_EXTERNAL_URIS
openWindow(null, gBrowserContentHandler.chromeURL, "_blank",
"chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine),

View File

@ -127,11 +127,20 @@ Factory.prototype = {
var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(proto.classID, proto.classDescription,
proto.contractID, factory);
if (proto.classID2) {
this._classID2 = proto.classID2;
registrar.registerFactory(proto.classID2, proto.classDescription,
proto.contractID2, factory);
}
},
unregister: function unregister() {
var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
registrar.unregisterFactory(this._classID, this._factory);
if (this._classID2) {
registrar.unregisterFactory(this._classID2, this._factory);
}
this._factory = null;
}
};
@ -320,13 +329,6 @@ let PdfJs = {
Cu.import('resource://pdf.js/PdfStreamConverter.jsm');
this._pdfStreamConverterFactory.register(PdfStreamConverter);
this._pdfRedirectorFactory = new Factory();
Cu.import('resource://pdf.js/PdfRedirector.jsm');
this._pdfRedirectorFactory.register(PdfRedirector);
Svc.pluginHost.registerPlayPreviewMimeType(PDF_CONTENT_TYPE, true,
'data:application/x-moz-playpreview-pdfjs;,');
this._registered = true;
},
@ -338,12 +340,6 @@ let PdfJs = {
Cu.unload('resource://pdf.js/PdfStreamConverter.jsm');
delete this._pdfStreamConverterFactory;
this._pdfRedirectorFactory.unregister();
Cu.unload('resource://pdf.js/PdfRedirector.jsm');
delete this._pdfRedirectorFactory;
Svc.pluginHost.unregisterPlayPreviewMimeType(PDF_CONTENT_TYPE);
this._registered = false;
}
};

View File

@ -813,6 +813,9 @@ PdfStreamConverter.prototype = {
classDescription: 'pdf.js Component',
contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*',
classID2: Components.ID('{d0c5195d-e798-49d4-b1d3-9324328b2292}'),
contractID2: '@mozilla.org/streamconv;1?from=application/pdf&to=text/html',
QueryInterface: XPCOMUtils.generateQI([
Ci.nsISupports,
Ci.nsIStreamConverter,

View File

@ -260,16 +260,6 @@ if test -n "$MOZ_NATIVE_DEVICES" ; then
AC_SUBST(ANDROID_APPCOMPAT_LIB)
AC_SUBST(ANDROID_APPCOMPAT_RES)
ANDROID_RECYCLERVIEW_LIB="$ANDROID_COMPAT_DIR_BASE/v7/recyclerview/libs/android-support-v7-recyclerview.jar"
ANDROID_RECYCLERVIEW_RES="$ANDROID_COMPAT_DIR_BASE/v7/recyclerview/res"
AC_MSG_CHECKING([for v7 recyclerview library])
if ! test -e $ANDROID_RECYCLERVIEW_LIB ; then
AC_MSG_ERROR([You must download the v7 recyclerview Android support library. Run the Android SDK tool and install Android Support Library under Extras. See https://developer.android.com/tools/extras/support-library.html for more info. (looked for $ANDROID_RECYCLERVIEW_LIB)])
fi
AC_MSG_RESULT([$ANDROID_RECYCLERVIEW_LIB])
AC_SUBST(ANDROID_RECYCLERVIEW_LIB)
AC_SUBST(ANDROID_RECYCLERVIEW_RES)
ANDROID_MEDIAROUTER_LIB="$ANDROID_COMPAT_DIR_BASE/v7/mediarouter/libs/android-support-v7-mediarouter.jar"
ANDROID_MEDIAROUTER_RES="$ANDROID_COMPAT_DIR_BASE/v7/mediarouter/res"
AC_MSG_CHECKING([for v7 mediarouter library])
@ -390,6 +380,16 @@ case "$target" in
fi
AC_MSG_RESULT([$ANDROID_COMPAT_LIB])
ANDROID_RECYCLERVIEW_LIB="$ANDROID_COMPAT_DIR_BASE/v7/recyclerview/libs/android-support-v7-recyclerview.jar"
ANDROID_RECYCLERVIEW_RES="$ANDROID_COMPAT_DIR_BASE/v7/recyclerview/res"
AC_MSG_CHECKING([for v7 recyclerview library])
if ! test -e $ANDROID_RECYCLERVIEW_LIB ; then
AC_MSG_ERROR([You must download the v7 recyclerview Android support library. Run the Android SDK tool and install Android Support Library under Extras. See https://developer.android.com/tools/extras/support-library.html for more info. (looked for $ANDROID_RECYCLERVIEW_LIB)])
fi
AC_MSG_RESULT([$ANDROID_RECYCLERVIEW_LIB])
AC_SUBST(ANDROID_RECYCLERVIEW_LIB)
AC_SUBST(ANDROID_RECYCLERVIEW_RES)
dnl Google has a history of moving the Android tools around. We don't
dnl care where they are, so let's try to find them anywhere we can.
ALL_ANDROID_TOOLS_PATHS="$ANDROID_TOOLS$all_android_build_tools:$ANDROID_PLATFORM_TOOLS"

View File

@ -44,6 +44,13 @@ nsWebNavigationInfo::IsTypeSupported(const nsACString& aType,
// to say for itself.
*aIsTypeSupported = nsIWebNavigationInfo::UNSUPPORTED;
// We want to claim that the type for PDF documents is unsupported,
// so that the internal PDF viewer's stream converted will get used.
if (aType.LowerCaseEqualsLiteral("application/pdf") &&
nsContentUtils::IsPDFJSEnabled()) {
return NS_OK;
}
const nsCString& flatType = PromiseFlatCString(aType);
nsresult rv = IsTypeSupportedInternal(flatType, aIsTypeSupported);
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -152,6 +152,7 @@
#include "nsIScriptGlobalObject.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIScriptSecurityManager.h"
#include "nsIStreamConverterService.h"
#include "nsIStringBundle.h"
#include "nsIURI.h"
#include "nsIURL.h"
@ -6500,6 +6501,19 @@ nsContentUtils::AllowXULXBLForPrincipal(nsIPrincipal* aPrincipal)
IsSitePermAllow(aPrincipal, "allowXULXBL"));
}
bool
nsContentUtils::IsPDFJSEnabled()
{
nsCOMPtr<nsIStreamConverterService> convServ =
do_GetService("@mozilla.org/streamConverters;1");
nsresult rv = NS_ERROR_FAILURE;
bool canConvert = false;
if (convServ) {
rv = convServ->CanConvert("application/pdf", "text/html", &canConvert);
}
return NS_SUCCEEDED(rv) && canConvert;
}
already_AddRefed<nsIDocumentLoaderFactory>
nsContentUtils::FindInternalContentViewer(const nsACString& aType,
ContentViewerType* aLoaderType)
@ -7922,4 +7936,4 @@ nsContentUtils::SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
net::ReferrerPolicy referrerPolicy = aDoc->GetReferrerPolicy();
return aChannel->SetReferrerWithPolicy(referrerURI, referrerPolicy);
}
}

View File

@ -2070,6 +2070,11 @@ public:
*/
static void XPCOMShutdown();
/**
* Checks if internal PDF viewer is enabled.
*/
static bool IsPDFJSEnabled();
enum ContentViewerType
{
TYPE_UNSUPPORTED,

View File

@ -552,6 +552,11 @@ IsPluginEnabledByExtension(nsIURI* uri, nsCString& mimeType)
return false;
}
// Disables any native PDF plugins, when internal PDF viewer is enabled.
if (ext.EqualsIgnoreCase("pdf") && nsContentUtils::IsPDFJSEnabled()) {
return false;
}
nsRefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
if (!pluginHost) {
@ -2672,6 +2677,13 @@ nsObjectLoadingContent::GetTypeOfContent(const nsCString& aMIMEType)
return eType_Image;
}
// Faking support of the PDF content as a document for EMBED tags
// when internal PDF viewer is enabled.
if (aMIMEType.LowerCaseEqualsLiteral("application/pdf") &&
nsContentUtils::IsPDFJSEnabled()) {
return eType_Document;
}
// SVGs load as documents, but are their own capability
bool isSVG = aMIMEType.LowerCaseEqualsLiteral("image/svg+xml");
Capabilities supportType = isSVG ? eSupportSVG : eSupportDocuments;

View File

@ -68,8 +68,7 @@ class StartDiscoveryTask final : public BluetoothReplyRunnable
{
public:
StartDiscoveryTask(BluetoothAdapter* aAdapter, Promise* aPromise)
: BluetoothReplyRunnable(nullptr, aPromise,
NS_LITERAL_STRING("StartDiscovery"))
: BluetoothReplyRunnable(nullptr, aPromise)
, mAdapter(aAdapter)
{
MOZ_ASSERT(aPromise);
@ -117,8 +116,7 @@ class StartLeScanTask final : public BluetoothReplyRunnable
public:
StartLeScanTask(BluetoothAdapter* aAdapter, Promise* aPromise,
const nsTArray<nsString>& aServiceUuids)
: BluetoothReplyRunnable(nullptr, aPromise,
NS_LITERAL_STRING("StartLeScan"))
: BluetoothReplyRunnable(nullptr, aPromise)
, mAdapter(aAdapter)
, mServiceUuids(aServiceUuids)
{
@ -175,8 +173,7 @@ public:
StopLeScanTask(BluetoothAdapter* aAdapter,
Promise* aPromise,
const nsAString& aScanUuid)
: BluetoothReplyRunnable(nullptr, aPromise,
NS_LITERAL_STRING("StopLeScan"))
: BluetoothReplyRunnable(nullptr, aPromise)
, mAdapter(aAdapter)
, mScanUuid(aScanUuid)
{
@ -574,9 +571,7 @@ BluetoothAdapter::StartDiscovery(ErrorResult& aRv)
}
// Return BluetoothDiscoveryHandle in StartDiscoveryTask
nsRefPtr<BluetoothReplyRunnable> result =
new StartDiscoveryTask(this, promise);
bs->StartDiscoveryInternal(result);
bs->StartDiscoveryInternal(new StartDiscoveryTask(this, promise));
return promise.forget();
}
@ -606,11 +601,7 @@ BluetoothAdapter::StopDiscovery(ErrorResult& aRv)
BluetoothService* bs = BluetoothService::Get();
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("StopDiscovery"));
bs->StopDiscoveryInternal(result);
bs->StopDiscoveryInternal(new BluetoothVoidReplyRunnable(nullptr, promise));
return promise.forget();
}
@ -707,13 +698,10 @@ BluetoothAdapter::SetName(const nsAString& aName, ErrorResult& aRv)
nsString name(aName);
BluetoothNamedValue property(NS_LITERAL_STRING("Name"),
BluetoothValue(name));
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("SetName"));
BT_ENSURE_TRUE_REJECT(
NS_SUCCEEDED(bs->SetProperty(BluetoothObjectType::TYPE_ADAPTER,
property, result)),
NS_SUCCEEDED(
bs->SetProperty(BluetoothObjectType::TYPE_ADAPTER, property,
new BluetoothVoidReplyRunnable(nullptr, promise))),
promise,
NS_ERROR_DOM_OPERATION_ERR);
@ -750,13 +738,10 @@ BluetoothAdapter::SetDiscoverable(bool aDiscoverable, ErrorResult& aRv)
// Wrap property to set and runnable to handle result
BluetoothNamedValue property(NS_LITERAL_STRING("Discoverable"),
BluetoothValue(aDiscoverable));
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("SetDiscoverable"));
BT_ENSURE_TRUE_REJECT(
NS_SUCCEEDED(bs->SetProperty(BluetoothObjectType::TYPE_ADAPTER,
property, result)),
NS_SUCCEEDED(
bs->SetProperty(BluetoothObjectType::TYPE_ADAPTER, property,
new BluetoothVoidReplyRunnable(nullptr, promise))),
promise,
NS_ERROR_DOM_OPERATION_ERR);
@ -832,19 +817,12 @@ BluetoothAdapter::PairUnpair(bool aPair, const nsAString& aDeviceAddress,
nsresult rv;
if (aPair) {
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("Pair"));
rv = bs->CreatePairedDeviceInternal(aDeviceAddress,
kCreatePairedDeviceTimeout,
result);
rv = bs->CreatePairedDeviceInternal(
aDeviceAddress, kCreatePairedDeviceTimeout,
new BluetoothVoidReplyRunnable(nullptr, promise));
} else {
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("Unpair"));
rv = bs->RemoveDeviceInternal(aDeviceAddress, result);
rv = bs->RemoveDeviceInternal(aDeviceAddress,
new BluetoothVoidReplyRunnable(nullptr, promise));
}
BT_ENSURE_TRUE_REJECT(NS_SUCCEEDED(rv), promise, NS_ERROR_DOM_OPERATION_ERR);
@ -891,9 +869,7 @@ BluetoothAdapter::Enable(ErrorResult& aRv)
// Wrap runnable to handle result
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr, /* DOMRequest */
promise,
NS_LITERAL_STRING("Enable"));
new BluetoothVoidReplyRunnable(nullptr, promise);
if (NS_FAILED(bs->EnableDisable(true, result))) {
// Restore adapter state and reject promise
@ -932,9 +908,7 @@ BluetoothAdapter::Disable(ErrorResult& aRv)
// Wrap runnable to handle result
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr, /* DOMRequest */
promise,
NS_LITERAL_STRING("Disable"));
new BluetoothVoidReplyRunnable(nullptr, promise);
if (NS_FAILED(bs->EnableDisable(false, result))) {
// Restore adapter state and reject promise

View File

@ -54,9 +54,8 @@ class FetchUuidsTask final : public BluetoothReplyRunnable
{
public:
FetchUuidsTask(Promise* aPromise,
const nsAString& aName,
BluetoothDevice* aDevice)
: BluetoothReplyRunnable(nullptr /* DOMRequest */, aPromise, aName)
: BluetoothReplyRunnable(nullptr, aPromise)
, mDevice(aDevice)
{
MOZ_ASSERT(aPromise);
@ -185,16 +184,14 @@ BluetoothDevice::FetchUuids(ErrorResult& aRv)
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
// Ensure BluetoothService is available
BluetoothService* bs = BluetoothService::Get();
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
nsRefPtr<BluetoothReplyRunnable> result =
new FetchUuidsTask(promise,
NS_LITERAL_STRING("FetchUuids"),
this);
nsresult rv = bs->FetchUuidsInternal(mAddress, result);
BT_ENSURE_TRUE_REJECT(NS_SUCCEEDED(rv), promise, NS_ERROR_DOM_OPERATION_ERR);
BT_ENSURE_TRUE_REJECT(
NS_SUCCEEDED(
bs->FetchUuidsInternal(mAddress, new FetchUuidsTask(promise, this))),
promise, NS_ERROR_DOM_OPERATION_ERR);
return promise.forget();
}
@ -382,7 +379,7 @@ BluetoothDevice::UpdatePropertiesFromAdvData(const nsTArray<uint8_t>& aAdvData)
dataLength -= 2;
}
char uuidStr[36];
char uuidStr[37]; // one more char to be null-terminated
if (type == GAP_INCOMPLETE_UUID16 || type == GAP_COMPLETE_UUID16) {
// Convert 16-bits UUID into string.
snprintf(uuidStr, sizeof(uuidStr),

View File

@ -121,13 +121,8 @@ BluetoothGatt::Connect(ErrorResult& aRv)
}
UpdateConnectionState(BluetoothConnectionState::Connecting);
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("ConnectGattClient"));
bs->ConnectGattClientInternal(mAppUuid,
mDeviceAddr,
result);
bs->ConnectGattClientInternal(
mAppUuid, mDeviceAddr, new BluetoothVoidReplyRunnable(nullptr, promise));
return promise.forget();
}
@ -153,11 +148,8 @@ BluetoothGatt::Disconnect(ErrorResult& aRv)
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
UpdateConnectionState(BluetoothConnectionState::Disconnecting);
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("DisconnectGattClient"));
bs->DisconnectGattClientInternal(mAppUuid, mDeviceAddr, result);
bs->DisconnectGattClientInternal(
mAppUuid, mDeviceAddr, new BluetoothVoidReplyRunnable(nullptr, promise));
return promise.forget();
}
@ -166,8 +158,7 @@ class ReadRemoteRssiTask final : public BluetoothReplyRunnable
{
public:
ReadRemoteRssiTask(Promise* aPromise)
: BluetoothReplyRunnable(nullptr, aPromise,
NS_LITERAL_STRING("GattClientReadRemoteRssi"))
: BluetoothReplyRunnable(nullptr, aPromise)
{
MOZ_ASSERT(aPromise);
}
@ -205,9 +196,8 @@ BluetoothGatt::ReadRemoteRssi(ErrorResult& aRv)
BluetoothService* bs = BluetoothService::Get();
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
nsRefPtr<BluetoothReplyRunnable> result =
new ReadRemoteRssiTask(promise);
bs->GattClientReadRemoteRssiInternal(mClientIf, mDeviceAddr, result);
bs->GattClientReadRemoteRssiInternal(
mClientIf, mDeviceAddr, new ReadRemoteRssiTask(promise));
return promise.forget();
}
@ -236,11 +226,9 @@ BluetoothGatt::DiscoverServices(ErrorResult& aRv)
mDiscoveringServices = true;
mServices.Clear();
BluetoothGattBinding::ClearCachedServicesValue(this);
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("DiscoverGattServices"));
bs->DiscoverGattServicesInternal(mAppUuid, result);
bs->DiscoverGattServicesInternal(
mAppUuid, new BluetoothVoidReplyRunnable(nullptr, promise));
return promise.forget();
}

View File

@ -99,15 +99,9 @@ BluetoothGattCharacteristic::StartNotifications(ErrorResult& aRv)
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
BT_ENSURE_TRUE_REJECT(mService, promise, NS_ERROR_NOT_AVAILABLE);
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(
nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("GattClientStartNotifications"));
bs->GattClientStartNotificationsInternal(mService->GetAppUuid(),
mService->GetServiceId(),
mCharId,
result);
bs->GattClientStartNotificationsInternal(
mService->GetAppUuid(), mService->GetServiceId(), mCharId,
new BluetoothVoidReplyRunnable(nullptr, promise));
return promise.forget();
}
@ -128,15 +122,9 @@ BluetoothGattCharacteristic::StopNotifications(ErrorResult& aRv)
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
BT_ENSURE_TRUE_REJECT(mService, promise, NS_ERROR_NOT_AVAILABLE);
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(
nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("GattClientStopNotifications"));
bs->GattClientStopNotificationsInternal(mService->GetAppUuid(),
mService->GetServiceId(),
mCharId,
result);
bs->GattClientStopNotificationsInternal(
mService->GetAppUuid(), mService->GetServiceId(), mCharId,
new BluetoothVoidReplyRunnable(nullptr, promise));
return promise.forget();
}
@ -212,9 +200,7 @@ class ReadValueTask final : public BluetoothReplyRunnable
{
public:
ReadValueTask(BluetoothGattCharacteristic* aCharacteristic, Promise* aPromise)
: BluetoothReplyRunnable(
nullptr, aPromise,
NS_LITERAL_STRING("GattClientReadCharacteristicValue"))
: BluetoothReplyRunnable(nullptr, aPromise)
, mCharacteristic(aCharacteristic)
{
MOZ_ASSERT(aCharacteristic);
@ -271,11 +257,9 @@ BluetoothGattCharacteristic::ReadValue(ErrorResult& aRv)
BluetoothService* bs = BluetoothService::Get();
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
nsRefPtr<BluetoothReplyRunnable> result = new ReadValueTask(this, promise);
bs->GattClientReadCharacteristicValueInternal(mService->GetAppUuid(),
mService->GetServiceId(),
mCharId,
result);
bs->GattClientReadCharacteristicValueInternal(
mService->GetAppUuid(), mService->GetServiceId(), mCharId,
new ReadValueTask(this, promise));
return promise.forget();
}
@ -308,14 +292,10 @@ BluetoothGattCharacteristic::WriteValue(const ArrayBuffer& aValue,
BluetoothService* bs = BluetoothService::Get();
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
nsRefPtr<BluetoothReplyRunnable> result = new BluetoothVoidReplyRunnable(
nullptr, promise, NS_LITERAL_STRING("GattClientWriteCharacteristicValue"));
bs->GattClientWriteCharacteristicValueInternal(mService->GetAppUuid(),
mService->GetServiceId(),
mCharId,
mWriteType,
value,
result);
bs->GattClientWriteCharacteristicValueInternal(
mService->GetAppUuid(), mService->GetServiceId(),
mCharId, mWriteType, value,
new BluetoothVoidReplyRunnable(nullptr, promise));
return promise.forget();
}

View File

@ -122,9 +122,7 @@ class ReadValueTask final : public BluetoothReplyRunnable
{
public:
ReadValueTask(BluetoothGattDescriptor* aDescriptor, Promise* aPromise)
: BluetoothReplyRunnable(
nullptr, aPromise,
NS_LITERAL_STRING("GattClientReadDescriptorValue"))
: BluetoothReplyRunnable(nullptr, aPromise)
, mDescriptor(aDescriptor)
{
MOZ_ASSERT(aDescriptor);
@ -177,13 +175,12 @@ BluetoothGattDescriptor::ReadValue(ErrorResult& aRv)
BluetoothService* bs = BluetoothService::Get();
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
nsRefPtr<BluetoothReplyRunnable> result = new ReadValueTask(this, promise);
bs->GattClientReadDescriptorValueInternal(
mCharacteristic->Service()->GetAppUuid(),
mCharacteristic->Service()->GetServiceId(),
mCharacteristic->GetCharacteristicId(),
mDescriptorId,
result);
new ReadValueTask(this, promise));
return promise.forget();
}
@ -209,15 +206,13 @@ BluetoothGattDescriptor::WriteValue(
BluetoothService* bs = BluetoothService::Get();
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
nsRefPtr<BluetoothReplyRunnable> result = new BluetoothVoidReplyRunnable(
nullptr, promise, NS_LITERAL_STRING("GattClientWriteDescriptorValue"));
bs->GattClientWriteDescriptorValueInternal(
mCharacteristic->Service()->GetAppUuid(),
mCharacteristic->Service()->GetServiceId(),
mCharacteristic->GetCharacteristicId(),
mDescriptorId,
value,
result);
new BluetoothVoidReplyRunnable(nullptr, promise));
return promise.forget();
}

View File

@ -82,11 +82,8 @@ BluetoothPairingHandle::SetPinCode(const nsAString& aPinCode, ErrorResult& aRv)
BluetoothService* bs = BluetoothService::Get();
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("SetPinCode"));
bs->PinReplyInternal(mDeviceAddress, true /* accept */, aPinCode, result);
bs->PinReplyInternal(mDeviceAddress, true /* accept */, aPinCode,
new BluetoothVoidReplyRunnable(nullptr, promise));
return promise.forget();
}
@ -116,12 +113,8 @@ BluetoothPairingHandle::Accept(ErrorResult& aRv)
promise,
NS_ERROR_DOM_OPERATION_ERR);
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("Accept"));
bs->SspReplyInternal(
mDeviceAddress, variant, true /* aAccept */, result);
bs->SspReplyInternal(mDeviceAddress, variant, true /* aAccept */,
new BluetoothVoidReplyRunnable(nullptr, promise));
return promise.forget();
}
@ -141,22 +134,17 @@ BluetoothPairingHandle::Reject(ErrorResult& aRv)
BluetoothService* bs = BluetoothService::Get();
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("Reject"));
if (mType.EqualsLiteral(PAIRING_REQ_TYPE_ENTERPINCODE)) { // Pin request
bs->PinReplyInternal(
mDeviceAddress, false /* aAccept */, EmptyString(), result);
bs->PinReplyInternal(mDeviceAddress, false /* aAccept */, EmptyString(),
new BluetoothVoidReplyRunnable(nullptr, promise));
} else { // Ssp request
BluetoothSspVariant variant;
BT_ENSURE_TRUE_REJECT(GetSspVariant(variant),
promise,
NS_ERROR_DOM_OPERATION_ERR);
bs->SspReplyInternal(
mDeviceAddress, variant, false /* aAccept */, result);
bs->SspReplyInternal(mDeviceAddress, variant, false /* aAccept */,
new BluetoothVoidReplyRunnable(nullptr, promise));
}
return promise.forget();

View File

@ -17,12 +17,10 @@ using namespace mozilla::dom;
USING_BLUETOOTH_NAMESPACE
BluetoothReplyRunnable::BluetoothReplyRunnable(nsIDOMDOMRequest* aReq,
Promise* aPromise,
const nsAString& aName)
Promise* aPromise)
: mDOMRequest(aReq)
, mPromise(aPromise)
, mErrorStatus(STATUS_FAIL)
, mName(aName)
{}
void
@ -121,9 +119,8 @@ BluetoothReplyRunnable::Run()
}
BluetoothVoidReplyRunnable::BluetoothVoidReplyRunnable(nsIDOMDOMRequest* aReq,
Promise* aPromise,
const nsAString& aName)
: BluetoothReplyRunnable(aReq, aPromise, aName)
Promise* aPromise)
: BluetoothReplyRunnable(aReq, aPromise)
{}
BluetoothVoidReplyRunnable::~BluetoothVoidReplyRunnable()

View File

@ -30,8 +30,7 @@ public:
NS_DECL_NSIRUNNABLE
BluetoothReplyRunnable(nsIDOMDOMRequest* aReq,
Promise* aPromise = nullptr,
const nsAString& aName = EmptyString());
Promise* aPromise = nullptr);
void SetReply(BluetoothReply* aReply);
@ -71,15 +70,13 @@ private:
BluetoothStatus mErrorStatus;
nsString mErrorString;
nsString mName; // for debugging
};
class BluetoothVoidReplyRunnable : public BluetoothReplyRunnable
{
public:
BluetoothVoidReplyRunnable(nsIDOMDOMRequest* aReq,
Promise* aPromise = nullptr,
const nsAString& aName = EmptyString());
Promise* aPromise = nullptr);
~BluetoothVoidReplyRunnable();
protected:

View File

@ -173,7 +173,7 @@ HasTouchListener(nsIContent* aContent)
}
static bool
IsElementClickable(nsIFrame* aFrame, nsIAtom* stopAt = nullptr)
IsElementClickable(nsIFrame* aFrame, nsIAtom* stopAt = nullptr, nsAutoString* aLabelTargetId = nullptr)
{
// Input events propagate up the content tree so we'll follow the content
// ancestors to look for elements accepting the click.
@ -188,8 +188,13 @@ IsElementClickable(nsIFrame* aFrame, nsIAtom* stopAt = nullptr)
if (content->IsAnyOfHTMLElements(nsGkAtoms::button,
nsGkAtoms::input,
nsGkAtoms::select,
nsGkAtoms::textarea,
nsGkAtoms::label)) {
nsGkAtoms::textarea)) {
return true;
}
if (content->IsHTMLElement(nsGkAtoms::label)) {
if (aLabelTargetId) {
content->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, *aLabelTargetId);
}
return true;
}
@ -320,6 +325,22 @@ SubtractFromExposedRegion(nsRegion* aExposedRegion, const nsRegion& aRegion)
}
}
// Search in the list of frames aCandidates if the element with the id "aLabelTargetId"
// is present.
static bool IsElementPresent(nsTArray<nsIFrame*>& aCandidates, const nsAutoString& aLabelTargetId)
{
for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
nsIFrame* f = aCandidates[i];
nsIContent* aContent = f->GetContent();
if (aContent && aContent->IsElement()) {
if (aContent->GetID() && aLabelTargetId == nsAtomString(aContent->GetID())) {
return true;
}
}
}
return false;
}
static nsIFrame*
GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame,
const nsRect& aTargetRect, const EventRadiusPrefs* aPrefs,
@ -351,7 +372,8 @@ GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame,
SubtractFromExposedRegion(&exposedRegion, region);
}
if (!IsElementClickable(f, nsGkAtoms::body)) {
nsAutoString labelTargetId;
if (!IsElementClickable(f, nsGkAtoms::body, &labelTargetId)) {
PET_LOG(" candidate %p was not clickable\n", f);
continue;
}
@ -366,7 +388,13 @@ GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame,
continue;
}
(*aElementsInCluster)++;
// If the first clickable ancestor of f is a label element
// and "for" attribute is present in label element, search the frame list for the "for" element
// If this element is present in the current list, do not count the frame in
// the cluster elements counter
if (labelTargetId.IsEmpty() || !IsElementPresent(aCandidates, labelTargetId)) {
(*aElementsInCluster)++;
}
// distance is in appunits
float distance = ComputeDistanceFromRegion(aPointRelativeToRootFrame, region);

View File

@ -1503,7 +1503,7 @@ skip-if(B2G||Mulet) fuzzy-if(Android&&AndroidVersion>=15,12,300) == 551463-1.htm
random != 553571-1.html 553571-1-notref.html # expect dotted circle in test, not in ref: "fails" under harfbuzz, which doesn't consider the sequence invalid
fuzzy-if(!contentSameGfxBackendAsCanvas,128,91) random-if(d2d) skip-if(azureSkiaGL) == 555388-1.html 555388-1-ref.html
== 556661-1.html 556661-1-ref.html
skip-if(B2G||Mulet) fails-if(Android) == 557087-1.html 557087-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
skip-if(B2G||Mulet) == 557087-1.html 557087-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
skip-if(B2G||Mulet) fails-if(Android) == 557087-2.html 557087-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
== 557736-1.html 557736-1-ref.html
skip-if((B2G&&browserIsRemote)||Mulet) != 558011-1.xul 558011-1-ref.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop

View File

@ -1,9 +1,9 @@
== button-fieldset-1.html button-fieldset-ref.html
fails-if(Android||B2G||Mulet) == button-fieldset-2.html button-fieldset-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(Android||B2G||Mulet) == button-fieldset-3.html button-fieldset-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(B2G||Mulet) == button-fieldset-2.html button-fieldset-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(B2G||Mulet) == button-fieldset-3.html button-fieldset-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
== button-fieldset-4.html button-fieldset-ref.html
== button-fieldset-legend-1.html button-fieldset-legend-ref-1.html
fails-if(Android||B2G||Mulet) == button-fieldset-legend-2.html button-fieldset-legend-ref-2.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(Android||B2G||Mulet) == button-fieldset-legend-3.html button-fieldset-legend-ref-3.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(B2G||Mulet) == button-fieldset-legend-2.html button-fieldset-legend-ref-2.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(B2G||Mulet) == button-fieldset-legend-3.html button-fieldset-legend-ref-3.html # Initial mulet triage: parity with B2G/B2G Desktop
== button-fieldset-legend-4.html button-fieldset-legend-ref-4.html
== button-fieldset-legend-5.html button-fieldset-legend-ref-5.html

View File

@ -1,7 +1,7 @@
== select-fieldset-1.html select-fieldset-ref.html
fails-if(Android||B2G||Mulet) == select-fieldset-2.html select-fieldset-ref-disabled.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(Android||B2G||Mulet) == select-fieldset-3.html select-fieldset-ref-disabled.html # Initial mulet triage: parity with B2G/B2G Desktop
== select-fieldset-4.html select-fieldset-ref.html
fails-if(Android) == select-fieldset-4.html select-fieldset-ref.html
== select-fieldset-legend-1.html select-fieldset-legend-ref-1.html
fails-if(Android||B2G||Mulet) == select-fieldset-legend-2.html select-fieldset-legend-ref-2.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(Android||B2G||Mulet) == select-fieldset-legend-3.html select-fieldset-legend-ref-3.html # Initial mulet triage: parity with B2G/B2G Desktop

View File

@ -1,9 +1,9 @@
== button-fieldset-1.html button-fieldset-ref.html
fails-if(Android||B2G||Mulet) == button-fieldset-2.html button-fieldset-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(Android||B2G||Mulet) == button-fieldset-3.html button-fieldset-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(B2G||Mulet) == button-fieldset-2.html button-fieldset-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(B2G||Mulet) == button-fieldset-3.html button-fieldset-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
== button-fieldset-4.html button-fieldset-ref.html
== button-fieldset-legend-1.html button-fieldset-legend-ref-1.html
fails-if(Android||B2G||Mulet) == button-fieldset-legend-2.html button-fieldset-legend-ref-2.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(Android||B2G||Mulet) == button-fieldset-legend-3.html button-fieldset-legend-ref-3.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(B2G||Mulet) == button-fieldset-legend-2.html button-fieldset-legend-ref-2.html # Initial mulet triage: parity with B2G/B2G Desktop
fails-if(B2G||Mulet) == button-fieldset-legend-3.html button-fieldset-legend-ref-3.html # Initial mulet triage: parity with B2G/B2G Desktop
== button-fieldset-legend-4.html button-fieldset-legend-ref-4.html
== button-fieldset-legend-5.html button-fieldset-legend-ref-5.html

View File

@ -275,6 +275,9 @@ pref("browser.search.order.US.3", "chrome://browser/locale/region.properties");
// disable updating
pref("browser.search.update", false);
// enable tracking protection for private browsing
pref("privacy.trackingprotection.pbmode.enabled", true);
// disable search suggestions by default
pref("browser.search.suggest.enabled", false);
pref("browser.search.suggest.prompted", false);

View File

@ -2101,6 +2101,9 @@ public class BrowserApp extends GeckoApp
mDoorHangerPopup.disable();
}
mTabsPanel.show(panel);
// Hide potentially visible "find in page" bar (Bug 1177338)
mFindInPageBar.hide();
}
}

View File

@ -8,6 +8,7 @@ import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.Locales.LocaleAwareFragmentActivity;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.sync.Utils;
@ -180,6 +181,16 @@ public class FxAccountStatusActivity extends LocaleAwareFragmentActivity {
return true;
}
if (itemId == R.id.enable_debug_mode) {
FxAccountUtils.LOG_PERSONAL_INFORMATION = !FxAccountUtils.LOG_PERSONAL_INFORMATION;
Toast.makeText(this, (FxAccountUtils.LOG_PERSONAL_INFORMATION ? "Enabled" : "Disabled") +
" Firefox Account personal information!", Toast.LENGTH_LONG).show();
item.setChecked(!item.isChecked());
// Display or hide debug options.
statusFragment.hardRefresh();
return true;
}
return super.onOptionsItemSelected(item);
}
@ -187,6 +198,11 @@ public class FxAccountStatusActivity extends LocaleAwareFragmentActivity {
public boolean onCreateOptionsMenu(Menu menu) {
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.fxaccount_status_menu, menu);
// !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) || defined(MOZ_DEBUG)
boolean enabled = !AppConstants.MOZILLA_OFFICIAL || AppConstants.NIGHTLY_BUILD || AppConstants.DEBUG_BUILD;
if (!enabled) {
menu.removeItem(R.id.enable_debug_mode);
}
return super.onCreateOptionsMenu(menu);
};
}

View File

@ -48,7 +48,6 @@ import android.preference.PreferenceScreen;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.widget.Toast;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
@ -93,15 +92,6 @@ public class FxAccountStatusFragment
// configured to use a custom Sync server. In debug mode, this is set.
private static boolean ALWAYS_SHOW_SYNC_SERVER = false;
// If the user clicks the email field this many times, the debug / personal
// information logging setting will toggle. The setting is not permanent: it
// lasts until this process is killed. We don't want to dump PII to the log
// for a long time!
private final int NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG =
// !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) || defined(MOZ_DEBUG)
(!AppConstants.MOZILLA_OFFICIAL || AppConstants.NIGHTLY_BUILD || AppConstants.DEBUG_BUILD) ? 5 : -1 /* infinite */;
private int debugClickCount = 0;
protected PreferenceCategory accountCategory;
protected Preference profilePreference;
protected Preference emailPreference;
@ -245,18 +235,6 @@ public class FxAccountStatusFragment
@Override
public boolean onPreferenceClick(Preference preference) {
final Preference personalInformationPreference = AppConstants.MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES ? profilePreference : emailPreference;
if (preference == personalInformationPreference) {
debugClickCount += 1;
if (NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG > 0 && debugClickCount >= NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG) {
debugClickCount = 0;
FxAccountUtils.LOG_PERSONAL_INFORMATION = !FxAccountUtils.LOG_PERSONAL_INFORMATION;
Toast.makeText(getActivity(), "Toggled logging Firefox Account personal information!", Toast.LENGTH_LONG).show();
hardRefresh(); // Display or hide debug options.
}
return true;
}
if (preference == needsPasswordPreference) {
Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
final Bundle extras = getExtrasForAccount();

View File

@ -192,7 +192,7 @@
<!ENTITY pref_cookies_disabled "Disabled">
<!ENTITY pref_tracking_protection_title "Tracking protection">
<!ENTITY pref_tracking_protection_summary "&brandShortName; will prevent sites from tracking you">
<!ENTITY pref_tracking_protection_summary2 "Actively block tracking elements in Private Browsing">
<!ENTITY pref_donottrack_title "Do not track">
<!ENTITY pref_donottrack_summary "&brandShortName; will tell sites that you do not want to be tracked">

View File

@ -230,6 +230,7 @@
<!ENTITY fxaccount_remove_account_toast 'Firefox Account &formatS; removed.'>
<!ENTITY fxaccount_remove_account_menu_item 'Remove Account'>
<!ENTITY fxaccount_enable_debug_mode 'Enable Debug Mode'>
<!-- Localization note: this is the name shown by the Android system
itself for a Firefox Account. Don't localize this. -->

View File

@ -124,8 +124,6 @@ OnSharedPreferenceChangeListener
private static final String PREFS_DISPLAY_REFLOW_ON_ZOOM = "browser.zoom.reflowOnZoom";
private static final String PREFS_DISPLAY_TITLEBAR_MODE = "browser.chrome.titlebarMode";
private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync";
private static final String PREFS_TRACKING_PROTECTION = "privacy.trackingprotection.enabled";
private static final String PREFS_TRACKING_PROTECTION_LEARN_MORE = NON_PREF_PREFIX + "trackingprotection.learn_more";
public static final String PREFS_OPEN_URLS_IN_PRIVATE = NON_PREF_PREFIX + "openExternalURLsPrivately";
public static final String PREFS_VOICE_INPUT_ENABLED = NON_PREF_PREFIX + "voice_input_enabled";
public static final String PREFS_QRCODE_ENABLED = NON_PREF_PREFIX + "qrcode_enabled";
@ -723,14 +721,6 @@ OnSharedPreferenceChangeListener
i--;
continue;
}
} else if (PREFS_TRACKING_PROTECTION.equals(key) ||
PREFS_TRACKING_PROTECTION_LEARN_MORE.equals(key)) {
// Remove UI for tracking protection preference on non-Nightly builds.
if (!AppConstants.NIGHTLY_BUILD) {
preferences.removePreference(pref);
i--;
continue;
}
} else if (PREFS_TELEMETRY_ENABLED.equals(key)) {
if (!AppConstants.MOZ_TELEMETRY_REPORTING) {
preferences.removePreference(pref);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -3,4 +3,9 @@
<item
android:id="@+id/remove_account"
android:title="@string/fxaccount_remove_account_menu_item" />
<item
android:id="@+id/enable_debug_mode"
android:checkable="true"
android:checked="false"
android:title="@string/fxaccount_enable_debug_mode" />
</menu>

View File

@ -8,7 +8,7 @@
android:title="@string/pref_category_privacy_short"
android:enabled="false">
<CheckBoxPreference android:key="privacy.trackingprotection.enabled"
<CheckBoxPreference android:key="privacy.trackingprotection.pbmode.enabled"
android:title="@string/pref_tracking_protection_title"
android:summary="@string/pref_tracking_protection_summary"
android:persistent="false" />

View File

@ -190,7 +190,7 @@
<string name="pref_cookies_disabled">&pref_cookies_disabled;</string>
<string name="pref_tracking_protection_title">&pref_tracking_protection_title;</string>
<string name="pref_tracking_protection_summary">&pref_tracking_protection_summary;</string>
<string name="pref_tracking_protection_summary">&pref_tracking_protection_summary2;</string>
<string name="pref_donottrack_title">&pref_donottrack_title;</string>
<string name="pref_donottrack_summary">&pref_donottrack_summary;</string>

View File

@ -54,7 +54,7 @@ public class ExtendedJSONObject {
return getJSONParser().parse(in);
} catch (Error e) {
// Don't be stupid, org.json.simple. Bug 1042929.
throw new ParseException(ParseException.ERROR_UNEXPECTED_EXCEPTION);
throw new ParseException(ParseException.ERROR_UNEXPECTED_EXCEPTION, e);
}
}
@ -72,7 +72,7 @@ public class ExtendedJSONObject {
return getJSONParser().parse(input);
} catch (Error e) {
// Don't be stupid, org.json.simple. Bug 1042929.
throw new ParseException(ParseException.ERROR_UNEXPECTED_EXCEPTION);
throw new ParseException(ParseException.ERROR_UNEXPECTED_EXCEPTION, e);
}
}
@ -314,6 +314,7 @@ public class ExtendedJSONObject {
return this.object.toJSONString();
}
@Override
public String toString() {
return this.object.toString();
}

View File

@ -124,6 +124,9 @@ class TabsGridLayout extends GridView
for (int x = 1, i = (removedPosition - firstPosition) + 1; i < childCount; i++, x++) {
final View child = getChildAt(i);
if (child != null) {
// Reset the transformations here in case the user is swiping tabs away fast and they swipe a tab
// before the last animation has finished (bug 1179195).
resetTransforms(child);
mTabLocations.append(x, new PointF(child.getX(), child.getY()));
}
}
@ -316,7 +319,7 @@ class TabsGridLayout extends GridView
TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
Tab tab = Tabs.getInstance().getTab(itemView.getTabId());
Tabs.getInstance().closeTab(tab);
Tabs.getInstance().closeTab(tab, true);
}
private void animateRemoveTab(final Tab removedTab) {

View File

@ -251,7 +251,7 @@ function removeSnippet(snippetId) {
function writeStat(snippetId, timestamp) {
let data = gEncoder.encode(snippetId + "," + timestamp + ";");
Task.spawn(function* () {
Task.spawn(function() {
try {
let file = yield OS.File.open(gStatsPath, { append: true, write: true });
try {

View File

@ -213,7 +213,7 @@ var gDatabaseEnsured = false;
* Creates the database schema.
*/
function createDatabase(db) {
return Task.spawn(function* create_database_task() {
return Task.spawn(function create_database_task() {
yield db.execute(SQL.createItemsTable);
});
}
@ -222,7 +222,7 @@ function createDatabase(db) {
* Migrates the database schema to a new version.
*/
function upgradeDatabase(db, oldVersion, newVersion) {
return Task.spawn(function* upgrade_database_task() {
return Task.spawn(function upgrade_database_task() {
switch (oldVersion) {
case 1:
// Migration from v1 to latest:
@ -251,7 +251,7 @@ function upgradeDatabase(db, oldVersion, newVersion) {
* @resolves Handle on an opened SQLite database.
*/
function getDatabaseConnection() {
return Task.spawn(function* get_database_connection_task() {
return Task.spawn(function get_database_connection_task() {
let db = yield Sqlite.openConnection({ path: DB_PATH });
if (gDatabaseEnsured) {
throw new Task.Result(db);
@ -350,10 +350,10 @@ HomeStorage.prototype = {
": you cannot save more than " + MAX_SAVE_COUNT + " items at once";
}
return Task.spawn(function* save_task() {
return Task.spawn(function save_task() {
let db = yield getDatabaseConnection();
try {
yield db.executeTransaction(function* save_transaction() {
yield db.executeTransaction(function save_transaction() {
if (options && options.replace) {
yield db.executeCached(SQL.deleteFromDataset, { dataset_id: this.datasetId });
}
@ -392,7 +392,7 @@ HomeStorage.prototype = {
* @resolves When the operation has completed.
*/
deleteAll: function() {
return Task.spawn(function* delete_all_task() {
return Task.spawn(function delete_all_task() {
let db = yield getDatabaseConnection();
try {
let params = { dataset_id: this.datasetId };

View File

@ -238,3 +238,6 @@
<string name="fxaccount_sync_finish_migrating_notification_title">&fxaccount_sync_finish_migrating_notification_title;</string>
<string name="fxaccount_sync_finish_migrating_notification_text">&fxaccount_sync_finish_migrating_notification_text;</string>
<!-- Log Personal information -->
<string name="fxaccount_enable_debug_mode">&fxaccount_enable_debug_mode;</string>

View File

@ -149,6 +149,7 @@ skip-if = android_version == "18"
# disabled on Android 2.3 due to video playback issues, bug 1088038; on 4.3, bug 1098532
skip-if = android_version == "10" || android_version == "18"
[testVideoDiscovery.java]
[testWebChannel.java]
# Used for Talos, please don't use in mochitest
#[testCheck2.java]

View File

@ -32,7 +32,6 @@ public class testSettingsMenuItems extends PixelTest {
// Privacy menu items.
String[] PATH_PRIVACY;
String[] TRACKING_PROTECTION_LABEL_ARR;
String[] MANAGE_LOGINS_ARR;
String[][] OPTIONS_PRIVACY;
@ -96,10 +95,9 @@ public class testSettingsMenuItems extends PixelTest {
};
PATH_PRIVACY = new String[] { mStringHelper.PRIVACY_SECTION_LABEL };
TRACKING_PROTECTION_LABEL_ARR = new String[] { mStringHelper.TRACKING_PROTECTION_LABEL };
MANAGE_LOGINS_ARR = new String[] { mStringHelper.MANAGE_LOGINS_LABEL };
OPTIONS_PRIVACY = new String[][] {
TRACKING_PROTECTION_LABEL_ARR,
{ mStringHelper.TRACKING_PROTECTION_LABEL },
{ mStringHelper.DNT_LABEL },
{ mStringHelper.COOKIES_LABEL, "Enabled", "Enabled, excluding 3rd party", "Disabled" },
{ mStringHelper.REMEMBER_LOGINS_LABEL },
@ -196,7 +194,6 @@ public class testSettingsMenuItems extends PixelTest {
if (!AppConstants.NIGHTLY_BUILD) {
final List<String[]> privacy = settingsMap.get(PATH_PRIVACY);
privacy.remove(TRACKING_PROTECTION_LABEL_ARR);
privacy.remove(MANAGE_LOGINS_ARR);
}

View File

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>web_channel_test</title>
</head>
<body>
<script>
window.onload = function() {
var testName = window.location.search.replace(/^\?/, "");
switch(testName) {
case "generic":
test_generic();
break;
case "twoway":
test_twoWay();
break;
case "multichannel":
test_multichannel();
break;
}
};
function test_generic() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "generic",
message: {
something: {
nested: "hello",
},
}
}
});
window.dispatchEvent(event);
}
function test_twoWay() {
var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "twoway",
message: {
command: "one",
},
}
});
window.addEventListener("WebChannelMessageToContent", function(e) {
var secondMessage = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "twoway",
message: {
command: "two",
detail: e.detail.message,
},
},
});
if (!e.detail.message.error) {
window.dispatchEvent(secondMessage);
}
}, true);
window.dispatchEvent(firstMessage);
}
function test_multichannel() {
var event1 = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "wrongchannel",
message: {},
}
});
var event2 = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "multichannel",
message: {},
}
});
window.dispatchEvent(event1);
window.dispatchEvent(event2);
}
</script>
</body>
</html>

View File

@ -0,0 +1,13 @@
/* 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.tests;
public class testWebChannel extends JavascriptTest {
public testWebChannel() {
super("testWebChannel.js");
}
}

View File

@ -0,0 +1,107 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* 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/. */
const { classes: Cc, interfaces: Ci, utils: Cu } = Components; /*global Components */
Cu.import("resource://gre/modules/Promise.jsm"); /*global Promise */
Cu.import("resource://gre/modules/Services.jsm"); /*global Services */
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global XPCOMUtils */
XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
"resource://gre/modules/WebChannel.jsm"); /*global WebChannel */
const HTTP_PATH = "http://mochi.test:8888";
const HTTP_ENDPOINT = "/tests/robocop/testWebChannel.html";
const gChromeWin = Services.wm.getMostRecentWindow("navigator:browser");
let BrowserApp = gChromeWin.BrowserApp;
/**
* Robocop test helpers.
*/
function ok(passed, text) {
do_report_result(passed, text, Components.stack.caller, false);
}
function is(lhs, rhs, text) {
do_report_result(lhs === rhs, "[ " + lhs + " === " + rhs + " ] " + text,
Components.stack.caller, false);
}
// Keep this synced with /browser/base/content/test/general/browser_web_channel.js
// as much as possible. (We only have this since we can't run browser chrome
// tests on Android. Yet?)
let gTests = [
{
desc: "WebChannel generic message",
run: function* () {
return new Promise(function(resolve, reject) {
let tab;
let channel = new WebChannel("generic", Services.io.newURI(HTTP_PATH, null, null));
channel.listen(function (id, message, target) {
is(id, "generic");
is(message.something.nested, "hello");
channel.stopListening();
BrowserApp.closeTab(tab);
resolve();
});
tab = BrowserApp.addTab(HTTP_PATH + HTTP_ENDPOINT + "?generic");
});
}
},
{
desc: "WebChannel two way communication",
run: function* () {
return new Promise(function(resolve, reject) {
let tab;
let channel = new WebChannel("twoway", Services.io.newURI(HTTP_PATH, null, null));
channel.listen(function (id, message, sender) {
is(id, "twoway");
ok(message.command);
if (message.command === "one") {
channel.send({ data: { nested: true } }, sender);
}
if (message.command === "two") {
is(message.detail.data.nested, true);
channel.stopListening();
BrowserApp.closeTab(tab);
resolve();
}
});
tab = BrowserApp.addTab(HTTP_PATH + HTTP_ENDPOINT + "?twoway");
});
}
},
{
desc: "WebChannel multichannel",
run: function* () {
return new Promise(function(resolve, reject) {
let tab;
let channel = new WebChannel("multichannel", Services.io.newURI(HTTP_PATH, null, null));
channel.listen(function (id, message, sender) {
is(id, "multichannel");
BrowserApp.closeTab(tab);
resolve();
});
tab = BrowserApp.addTab(HTTP_PATH + HTTP_ENDPOINT + "?multichannel");
});
}
}
]; // gTests
add_task(function test() {
for (let test of gTests) {
do_print("Running: " + test.desc);
yield test.run();
}
});
run_next_test();

View File

@ -241,17 +241,17 @@ input[type="radio"]:focus {
}
/* we need to be specific for selects because the above rules are specific too */
textarea[disabled],
select[size][disabled],
select[multiple][disabled],
select[size][multiple][disabled],
select:not([size]):not([multiple])[disabled],
select[size="0"][disabled],
select[size="1"][disabled],
button[disabled],
button[disabled]:active,
* > input:not([type="image"])[disabled],
* > input:not([type="image"])[disabled]:active {
textarea:disabled,
select[size]:disabled,
select[multiple]:disabled,
select[size][multiple]:disabled,
select:not([size]):not([multiple]):disabled,
select[size="0"]:disabled,
select[size="1"]:disabled,
button:disabled,
button:disabled:active,
* > input:not([type="image"]):disabled,
* > input:not([type="image"]):disabled:active {
color: @form_text_disabled@;
border-color: @form_border@;
border-style: solid;
@ -259,20 +259,20 @@ button[disabled]:active,
background: @form_background_disabled@;
}
select:not([size]):not([multiple])[disabled],
select[size="0"][disabled],
select[size="1"][disabled] {
select:not([size]):not([multiple]):disabled,
select[size="0"]:disabled,
select[size="1"]:disabled {
background: @form_background_disabled@;
}
input[type="button"][disabled],
input[type="button"][disabled]:active,
input[type="submit"][disabled],
input[type="submit"][disabled]:active,
input[type="reset"][disabled],
input[type="reset"][disabled]:active,
button[disabled],
button[disabled]:active {
input[type="button"]:disabled,
input[type="button"]:disabled:active,
input[type="submit"]:disabled,
input[type="submit"]:disabled:active,
input[type="reset"]:disabled,
input[type="reset"]:disabled:active,
button:disabled,
button:disabled:active {
-moz-padding-start: 7px;
-moz-padding-end: 7px;
padding-block-start: 0;
@ -280,18 +280,18 @@ button[disabled]:active {
background: @form_background_disabled@;
}
input[type="radio"][disabled],
input[type="radio"][disabled]:active,
input[type="radio"][disabled]:hover,
input[type="radio"][disabled]:hover:active,
input[type="checkbox"][disabled],
input[type="checkbox"][disabled]:active,
input[type="checkbox"][disabled]:hover,
input[type="checkbox"][disabled]:hover:active {
input[type="radio"]:disabled,
input[type="radio"]:disabled:active,
input[type="radio"]:disabled:hover,
input[type="radio"]:disabled:hover:active,
input[type="checkbox"]:disabled,
input[type="checkbox"]:disabled:active,
input[type="checkbox"]:disabled:hover,
input[type="checkbox"]:disabled:hover:active {
border:1px solid @form_border@ !important;
}
select[disabled] > button {
select:disabled > button {
opacity: 0.6;
-moz-padding-start: 7px;
-moz-padding-end: 7px;
@ -306,10 +306,10 @@ select[disabled] > button {
*:-moz-any-link:active,
*[role=button]:active,
button:not([disabled]):active,
input:not(:focus):not([disabled]):active,
select:not([disabled]):active,
textarea:not(:focus):not([disabled]):active,
button:not(:disabled):active,
input:not(:focus):not(:disabled):active,
select:not(:disabled):active,
textarea:not(:focus):not(:disabled):active,
option:active,
label:active,
xul|menulist:active {

View File

@ -2,13 +2,13 @@ package org.json.simple.parser;
/**
* ParseException explains why and where the error occurs in source JSON text.
*
*
* @author FangYidong<fangyidong@yahoo.com.cn>
*
*/
public class ParseException extends Exception {
private static final long serialVersionUID = -7880698968187728548L;
public static final int ERROR_UNEXPECTED_CHAR = 0;
public static final int ERROR_UNEXPECTED_TOKEN = 1;
public static final int ERROR_UNEXPECTED_EXCEPTION = 2;
@ -16,45 +16,50 @@ public class ParseException extends Exception {
private int errorType;
private Object unexpectedObject;
private int position;
public ParseException(int errorType){
this(-1, errorType, null);
public ParseException(int errorType, Throwable throwable) {
this(-1, errorType, null, throwable);
}
public ParseException(int errorType, Object unexpectedObject){
public ParseException(int errorType, Object unexpectedObject) {
this(-1, errorType, unexpectedObject);
}
public ParseException(int position, int errorType, Object unexpectedObject){
this.position = position;
this.errorType = errorType;
this.unexpectedObject = unexpectedObject;
public ParseException(int position, int errorType, Object unexpectedObject) {
this(-1, errorType, unexpectedObject, null);
}
public ParseException(int position, int errorType, Object unexpectedObject, Throwable throwable) {
super(throwable);
this.position = position;
this.errorType = errorType;
this.unexpectedObject = unexpectedObject;
}
public int getErrorType() {
return errorType;
}
public void setErrorType(int errorType) {
this.errorType = errorType;
}
/**
* @see org.json.simple.parser.JSONParser#getPosition()
*
*
* @return The character position (starting with 0) of the input where the error occurs.
*/
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
/**
* @see org.json.simple.parser.Yytoken
*
*
* @return One of the following base on the value of errorType:
* ERROR_UNEXPECTED_CHAR java.lang.Character
* ERROR_UNEXPECTED_TOKEN org.json.simple.parser.Yytoken
@ -63,14 +68,15 @@ public class ParseException extends Exception {
public Object getUnexpectedObject() {
return unexpectedObject;
}
public void setUnexpectedObject(Object unexpectedObject) {
this.unexpectedObject = unexpectedObject;
}
public String toString(){
@Override
public String toString(){
StringBuffer sb = new StringBuffer();
switch(errorType){
case ERROR_UNEXPECTED_CHAR:
sb.append("Unexpected character (").append(unexpectedObject).append(") at position ").append(position).append(".");

View File

@ -4,10 +4,40 @@
#include "nsAutoCompleteSimpleResult.h"
#define CHECK_MATCH_INDEX(_index, _insert) \
if (_index < 0 || \
static_cast<MatchesArray::size_type>(_index) > mMatches.Length() || \
(!_insert && static_cast<MatchesArray::size_type>(_index) == mMatches.Length())) { \
MOZ_ASSERT(false, "Trying to use an invalid index on mMatches"); \
return NS_ERROR_ILLEGAL_VALUE; \
} \
NS_IMPL_ISUPPORTS(nsAutoCompleteSimpleResult,
nsIAutoCompleteResult,
nsIAutoCompleteSimpleResult)
struct AutoCompleteSimpleResultMatch
{
AutoCompleteSimpleResultMatch(const nsAString& aValue,
const nsAString& aComment,
const nsAString& aImage,
const nsAString& aStyle,
const nsAString& aFinalCompleteValue)
: mValue(aValue)
, mComment(aComment)
, mImage(aImage)
, mStyle(aStyle)
, mFinalCompleteValue(aFinalCompleteValue)
{
}
nsString mValue;
nsString mComment;
nsString mImage;
nsString mStyle;
nsString mFinalCompleteValue;
};
nsAutoCompleteSimpleResult::nsAutoCompleteSimpleResult() :
mDefaultIndex(-1),
mSearchResult(RESULT_NOMATCH),
@ -86,6 +116,25 @@ nsAutoCompleteSimpleResult::SetTypeAheadResult(bool aTypeAheadResult)
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteSimpleResult::InsertMatchAt(int32_t aIndex,
const nsAString& aValue,
const nsAString& aComment,
const nsAString& aImage,
const nsAString& aStyle,
const nsAString& aFinalCompleteValue)
{
CHECK_MATCH_INDEX(aIndex, true);
AutoCompleteSimpleResultMatch match(aValue, aComment, aImage, aStyle, aFinalCompleteValue);
if (!mMatches.InsertElementAt(aIndex, match)) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteSimpleResult::AppendMatch(const nsAString& aValue,
const nsAString& aComment,
@ -93,52 +142,22 @@ nsAutoCompleteSimpleResult::AppendMatch(const nsAString& aValue,
const nsAString& aStyle,
const nsAString& aFinalCompleteValue)
{
CheckInvariants();
if (! mValues.AppendElement(aValue))
return NS_ERROR_OUT_OF_MEMORY;
if (! mComments.AppendElement(aComment)) {
mValues.RemoveElementAt(mValues.Length() - 1);
return NS_ERROR_OUT_OF_MEMORY;
}
if (! mImages.AppendElement(aImage)) {
mValues.RemoveElementAt(mValues.Length() - 1);
mComments.RemoveElementAt(mComments.Length() - 1);
return NS_ERROR_OUT_OF_MEMORY;
}
if (! mStyles.AppendElement(aStyle)) {
mValues.RemoveElementAt(mValues.Length() - 1);
mComments.RemoveElementAt(mComments.Length() - 1);
mImages.RemoveElementAt(mImages.Length() - 1);
return NS_ERROR_OUT_OF_MEMORY;
}
if (!mFinalCompleteValues.AppendElement(aFinalCompleteValue)) {
mValues.RemoveElementAt(mValues.Length() - 1);
mComments.RemoveElementAt(mComments.Length() - 1);
mImages.RemoveElementAt(mImages.Length() - 1);
mStyles.RemoveElementAt(mStyles.Length() - 1);
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
return InsertMatchAt(mMatches.Length(), aValue, aComment, aImage, aStyle,
aFinalCompleteValue);
}
NS_IMETHODIMP
nsAutoCompleteSimpleResult::GetMatchCount(uint32_t *aMatchCount)
{
CheckInvariants();
*aMatchCount = mValues.Length();
*aMatchCount = mMatches.Length();
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteSimpleResult::GetValueAt(int32_t aIndex, nsAString& _retval)
{
NS_ENSURE_TRUE(aIndex >= 0 && aIndex < int32_t(mValues.Length()),
NS_ERROR_ILLEGAL_VALUE);
CheckInvariants();
_retval = mValues[aIndex];
CHECK_MATCH_INDEX(aIndex, false);
_retval = mMatches[aIndex].mValue;
return NS_OK;
}
@ -151,30 +170,24 @@ nsAutoCompleteSimpleResult::GetLabelAt(int32_t aIndex, nsAString& _retval)
NS_IMETHODIMP
nsAutoCompleteSimpleResult::GetCommentAt(int32_t aIndex, nsAString& _retval)
{
NS_ENSURE_TRUE(aIndex >= 0 && aIndex < int32_t(mComments.Length()),
NS_ERROR_ILLEGAL_VALUE);
CheckInvariants();
_retval = mComments[aIndex];
CHECK_MATCH_INDEX(aIndex, false);
_retval = mMatches[aIndex].mComment;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteSimpleResult::GetImageAt(int32_t aIndex, nsAString& _retval)
{
NS_ENSURE_TRUE(aIndex >= 0 && aIndex < int32_t(mImages.Length()),
NS_ERROR_ILLEGAL_VALUE);
CheckInvariants();
_retval = mImages[aIndex];
CHECK_MATCH_INDEX(aIndex, false);
_retval = mMatches[aIndex].mImage;
return NS_OK;
}
NS_IMETHODIMP
nsAutoCompleteSimpleResult::GetStyleAt(int32_t aIndex, nsAString& _retval)
{
NS_ENSURE_TRUE(aIndex >= 0 && aIndex < int32_t(mStyles.Length()),
NS_ERROR_ILLEGAL_VALUE);
CheckInvariants();
_retval = mStyles[aIndex];
CHECK_MATCH_INDEX(aIndex, false);
_retval = mMatches[aIndex].mStyle;
return NS_OK;
}
@ -182,12 +195,10 @@ NS_IMETHODIMP
nsAutoCompleteSimpleResult::GetFinalCompleteValueAt(int32_t aIndex,
nsAString& _retval)
{
NS_ENSURE_TRUE(aIndex >= 0 && aIndex < int32_t(mFinalCompleteValues.Length()),
NS_ERROR_ILLEGAL_VALUE);
CheckInvariants();
_retval = mFinalCompleteValues[aIndex];
CHECK_MATCH_INDEX(aIndex, false);
_retval = mMatches[aIndex].mFinalCompleteValue;
if (_retval.Length() == 0)
_retval = mValues[aIndex];
_retval = mMatches[aIndex].mValue;
return NS_OK;
}
@ -202,18 +213,13 @@ NS_IMETHODIMP
nsAutoCompleteSimpleResult::RemoveValueAt(int32_t aRowIndex,
bool aRemoveFromDb)
{
NS_ENSURE_TRUE(aRowIndex >= 0 && aRowIndex < int32_t(mValues.Length()),
NS_ERROR_ILLEGAL_VALUE);
CHECK_MATCH_INDEX(aRowIndex, false);
nsAutoString removedValue(mValues[aRowIndex]);
mValues.RemoveElementAt(aRowIndex);
mComments.RemoveElementAt(aRowIndex);
mImages.RemoveElementAt(aRowIndex);
mStyles.RemoveElementAt(aRowIndex);
mFinalCompleteValues.RemoveElementAt(aRowIndex);
nsString value = mMatches[aRowIndex].mValue;
mMatches.RemoveElementAt(aRowIndex);
if (mListener)
mListener->OnValueRemoved(this, removedValue, aRemoveFromDb);
mListener->OnValueRemoved(this, value, aRemoveFromDb);
return NS_OK;
}

View File

@ -13,16 +13,12 @@
#include "nsTArray.h"
#include "mozilla/Attributes.h"
struct AutoCompleteSimpleResultMatch;
class nsAutoCompleteSimpleResult final : public nsIAutoCompleteSimpleResult
{
public:
nsAutoCompleteSimpleResult();
inline void CheckInvariants() {
NS_ASSERTION(mValues.Length() == mComments.Length(), "Arrays out of sync");
NS_ASSERTION(mValues.Length() == mImages.Length(), "Arrays out of sync");
NS_ASSERTION(mValues.Length() == mStyles.Length(), "Arrays out of sync");
NS_ASSERTION(mValues.Length() == mFinalCompleteValues.Length(), "Arrays out of sync");
}
NS_DECL_ISUPPORTS
NS_DECL_NSIAUTOCOMPLETERESULT
@ -32,15 +28,8 @@ private:
~nsAutoCompleteSimpleResult() {}
protected:
// What we really want is an array of structs with value/comment/image/style contents.
// But then we'd either have to use COM or manage object lifetimes ourselves.
// Having four arrays of string simplifies this, but is stupid.
nsTArray<nsString> mValues;
nsTArray<nsString> mComments;
nsTArray<nsString> mImages;
nsTArray<nsString> mStyles;
nsTArray<nsString> mFinalCompleteValues;
typedef nsTArray<AutoCompleteSimpleResultMatch> MatchesArray;
MatchesArray mMatches;
nsString mSearchString;
nsString mErrorDescription;

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