mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge m-c to b-i
This commit is contained in:
commit
a6af0a454c
@ -940,10 +940,6 @@ pref("consoleservice.buffered", false);
|
||||
pref("toolkit.storage.pageSize", 2048);
|
||||
#endif
|
||||
|
||||
// Enable captive portal detection.
|
||||
pref("captivedetect.canonicalURL", "http://detectportal.firefox.com/success.txt");
|
||||
pref("captivedetect.canonicalContent", "success\n");
|
||||
|
||||
// The url of the manifest we use for ADU pings.
|
||||
pref("ping.manifestURL", "https://marketplace.firefox.com/packaged.webapp");
|
||||
|
||||
|
@ -66,11 +66,9 @@ XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
|
||||
});
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_CAPTIVEDETECT
|
||||
XPCOMUtils.defineLazyServiceGetter(Services, 'captivePortalDetector',
|
||||
'@mozilla.org/toolkit/captive-detector;1',
|
||||
'nsICaptivePortalDetector');
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_SAFE_BROWSING
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
|
||||
|
@ -20,7 +20,6 @@ MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
|
||||
MOZ_SAFE_BROWSING=1
|
||||
MOZ_SERVICES_COMMON=1
|
||||
MOZ_SERVICES_METRICS=1
|
||||
MOZ_CAPTIVEDETECT=1
|
||||
|
||||
MOZ_WEBSMS_BACKEND=1
|
||||
MOZ_NO_SMART_CARDS=1
|
||||
|
@ -317,9 +317,7 @@
|
||||
@RESPATH@/components/services-crypto.xpt
|
||||
#endif
|
||||
@RESPATH@/components/services-crypto-component.xpt
|
||||
#ifdef MOZ_CAPTIVEDETECT
|
||||
@RESPATH@/components/captivedetect.xpt
|
||||
#endif
|
||||
@RESPATH@/components/shellservice.xpt
|
||||
@RESPATH@/components/shistory.xpt
|
||||
@RESPATH@/components/spellchecker.xpt
|
||||
@ -633,10 +631,8 @@
|
||||
@RESPATH@/components/HealthReportComponents.manifest
|
||||
@RESPATH@/components/HealthReportService.js
|
||||
#endif
|
||||
#ifdef MOZ_CAPTIVEDETECT
|
||||
@RESPATH@/components/CaptivePortalDetectComponents.manifest
|
||||
@RESPATH@/components/captivedetect.js
|
||||
#endif
|
||||
@RESPATH@/components/TelemetryStartup.js
|
||||
@RESPATH@/components/TelemetryStartup.manifest
|
||||
@RESPATH@/components/XULStore.js
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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",
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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})
|
||||
|
@ -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} />
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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 |
@ -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"));
|
||||
}
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"),
|
||||
|
@ -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")}
|
||||
|
@ -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
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
});
|
||||
|
||||
|
@ -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": {
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
|
@ -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 = {
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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!",
|
||||
|
@ -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",
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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).
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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>" +
|
||||
|
@ -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>" +
|
||||
|
@ -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',
|
||||
]
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -314,9 +314,7 @@
|
||||
@RESPATH@/components/saxparser.xpt
|
||||
@RESPATH@/browser/components/sessionstore.xpt
|
||||
@RESPATH@/components/services-crypto-component.xpt
|
||||
#ifdef MOZ_CAPTIVEDETECT
|
||||
@RESPATH@/components/captivedetect.xpt
|
||||
#endif
|
||||
@RESPATH@/browser/components/shellservice.xpt
|
||||
@RESPATH@/components/shistory.xpt
|
||||
@RESPATH@/components/spellchecker.xpt
|
||||
@ -521,10 +519,8 @@
|
||||
@RESPATH@/components/SyncComponents.manifest
|
||||
@RESPATH@/components/Weave.js
|
||||
#endif
|
||||
#ifdef MOZ_CAPTIVEDETECT
|
||||
@RESPATH@/components/CaptivePortalDetectComponents.manifest
|
||||
@RESPATH@/components/captivedetect.js
|
||||
#endif
|
||||
@RESPATH@/components/servicesComponents.manifest
|
||||
@RESPATH@/components/cryptoComponents.manifest
|
||||
@RESPATH@/components/TelemetryStartup.js
|
||||
|
@ -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"
|
||||
|
@ -8489,12 +8489,6 @@ if test -n "$MOZ_SERVICES_CLOUDSYNC"; then
|
||||
AC_DEFINE(MOZ_SERVICES_CLOUDSYNC)
|
||||
fi
|
||||
|
||||
dnl Build Captive Portal Detector if required
|
||||
AC_SUBST(MOZ_CAPTIVEDETECT)
|
||||
if test -n "$MOZ_CAPTIVEDETECT"; then
|
||||
AC_DEFINE(MOZ_CAPTIVEDETECT)
|
||||
fi
|
||||
|
||||
dnl Build second screen and casting features for external devices if required
|
||||
AC_SUBST(MOZ_DEVICES)
|
||||
if test -n "$MOZ_DEVICES"; then
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -2070,6 +2070,11 @@ public:
|
||||
*/
|
||||
static void XPCOMShutdown();
|
||||
|
||||
/**
|
||||
* Checks if internal PDF viewer is enabled.
|
||||
*/
|
||||
static bool IsPDFJSEnabled();
|
||||
|
||||
enum ContentViewerType
|
||||
{
|
||||
TYPE_UNSUPPORTED,
|
||||
|
@ -3911,7 +3911,6 @@ nsGlobalWindow::GetRealTop(nsIDOMWindow** aTop)
|
||||
if (IsInnerWindow()) {
|
||||
outer = GetOuterWindowInternal();
|
||||
if (!outer) {
|
||||
NS_WARNING("No outer window available!");
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
} else {
|
||||
|
@ -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;
|
||||
|
@ -158,7 +158,10 @@ parent:
|
||||
nsString aName,
|
||||
nsString aFeatures,
|
||||
nsString aBaseURI)
|
||||
returns (bool windowOpened, FrameScriptInfo[] frameScripts, nsCString urlToLoad);
|
||||
returns (nsresult rv,
|
||||
bool windowOpened,
|
||||
FrameScriptInfo[] frameScripts,
|
||||
nsCString urlToLoad);
|
||||
|
||||
sync SyncMessage(nsString aMessage, ClonedMessageData aData,
|
||||
CpowEntry[] aCpows, Principal aPrincipal)
|
||||
|
@ -1534,16 +1534,23 @@ TabChild::ProvideWindowCommon(nsIDOMWindow* aOpener,
|
||||
// tab, then we want to enforce that the new window is also a remote tab.
|
||||
features.AppendLiteral(",remote");
|
||||
|
||||
nsresult rv;
|
||||
|
||||
if (!SendCreateWindow(newChild,
|
||||
aChromeFlags, aCalledFromJS, aPositionSpecified,
|
||||
aSizeSpecified, url,
|
||||
name, NS_ConvertUTF8toUTF16(features),
|
||||
NS_ConvertUTF8toUTF16(baseURIString),
|
||||
&rv,
|
||||
aWindowIsNew,
|
||||
&frameScripts,
|
||||
&urlToLoad)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
if (!*aWindowIsNew) {
|
||||
PBrowserChild::Send__delete__(newChild);
|
||||
|
@ -608,6 +608,7 @@ TabParent::RecvCreateWindow(PBrowserParent* aNewTab,
|
||||
const nsString& aName,
|
||||
const nsString& aFeatures,
|
||||
const nsString& aBaseURI,
|
||||
nsresult* aResult,
|
||||
bool* aWindowIsNew,
|
||||
InfallibleTArray<FrameScriptInfo>* aFrameScripts,
|
||||
nsCString* aURLToLoad)
|
||||
@ -615,14 +616,14 @@ TabParent::RecvCreateWindow(PBrowserParent* aNewTab,
|
||||
// We always expect to open a new window here. If we don't, it's an error.
|
||||
*aWindowIsNew = true;
|
||||
|
||||
if (IsBrowserOrApp()) {
|
||||
if (NS_WARN_IF(IsBrowserOrApp()))
|
||||
return false;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsPIWindowWatcher> pwwatch =
|
||||
do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
do_GetService(NS_WINDOWWATCHER_CONTRACTID, aResult);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(*aResult)))
|
||||
return true;
|
||||
|
||||
TabParent* newTab = TabParent::GetFrom(aNewTab);
|
||||
MOZ_ASSERT(newTab);
|
||||
@ -653,8 +654,9 @@ TabParent::RecvCreateWindow(PBrowserParent* aNewTab,
|
||||
// opened one.
|
||||
if (!parent) {
|
||||
parent = FindMostRecentOpenWindow();
|
||||
if (!parent) {
|
||||
return false;
|
||||
if (NS_WARN_IF(!parent)) {
|
||||
*aResult = NS_ERROR_FAILURE;
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMChromeWindow> rootChromeWin = do_QueryInterface(parent);
|
||||
@ -672,7 +674,10 @@ TabParent::RecvCreateWindow(PBrowserParent* aNewTab,
|
||||
|
||||
// Opening new tabs is the easy case...
|
||||
if (openLocation == nsIBrowserDOMWindow::OPEN_NEWTAB) {
|
||||
NS_ENSURE_TRUE(browserDOMWin, false);
|
||||
if (NS_WARN_IF(!browserDOMWin)) {
|
||||
*aResult = NS_ERROR_FAILURE;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isPrivate;
|
||||
nsCOMPtr<nsILoadContext> loadContext = GetLoadContext();
|
||||
@ -704,14 +709,19 @@ TabParent::RecvCreateWindow(PBrowserParent* aNewTab,
|
||||
// TabChild has sent us a baseURI with which we can ensure that
|
||||
// the URI we pass to WindowWatcher is valid.
|
||||
nsCOMPtr<nsIURI> baseURI;
|
||||
rv = NS_NewURI(getter_AddRefs(baseURI), aBaseURI);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
*aResult = NS_NewURI(getter_AddRefs(baseURI), aBaseURI);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(*aResult)))
|
||||
return true;
|
||||
|
||||
nsAutoCString finalURIString;
|
||||
if (!aURI.IsEmpty()) {
|
||||
nsCOMPtr<nsIURI> finalURI;
|
||||
rv = NS_NewURI(getter_AddRefs(finalURI), NS_ConvertUTF16toUTF8(aURI).get(), baseURI);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
*aResult = NS_NewURI(getter_AddRefs(finalURI), NS_ConvertUTF16toUTF8(aURI).get(), baseURI);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(*aResult)))
|
||||
return true;
|
||||
|
||||
finalURI->GetSpec(finalURIString);
|
||||
}
|
||||
|
||||
@ -719,31 +729,45 @@ TabParent::RecvCreateWindow(PBrowserParent* aNewTab,
|
||||
|
||||
AutoUseNewTab aunt(newTab, aWindowIsNew, aURLToLoad);
|
||||
|
||||
rv = pwwatch->OpenWindow2(parent, finalURIString.get(),
|
||||
NS_ConvertUTF16toUTF8(aName).get(),
|
||||
NS_ConvertUTF16toUTF8(aFeatures).get(), aCalledFromJS,
|
||||
false, false, this, nullptr, getter_AddRefs(window));
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
*aResult = pwwatch->OpenWindow2(parent, finalURIString.get(),
|
||||
NS_ConvertUTF16toUTF8(aName).get(),
|
||||
NS_ConvertUTF16toUTF8(aFeatures).get(), aCalledFromJS,
|
||||
false, false, this, nullptr, getter_AddRefs(window));
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(*aResult)))
|
||||
return true;
|
||||
|
||||
*aResult = NS_ERROR_FAILURE;
|
||||
|
||||
nsCOMPtr<nsPIDOMWindow> pwindow = do_QueryInterface(window);
|
||||
NS_ENSURE_TRUE(pwindow, false);
|
||||
if (NS_WARN_IF(!pwindow)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDocShell> windowDocShell = pwindow->GetDocShell();
|
||||
NS_ENSURE_TRUE(windowDocShell, false);
|
||||
if (NS_WARN_IF(!windowDocShell)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
|
||||
windowDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
|
||||
|
||||
nsCOMPtr<nsIXULWindow> xulWin = do_GetInterface(treeOwner);
|
||||
NS_ENSURE_TRUE(xulWin, false);
|
||||
if (NS_WARN_IF(!xulWin)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIXULBrowserWindow> xulBrowserWin;
|
||||
xulWin->GetXULBrowserWindow(getter_AddRefs(xulBrowserWin));
|
||||
NS_ENSURE_TRUE(xulBrowserWin, false);
|
||||
if (NS_WARN_IF(!xulBrowserWin)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsITabParent> newRemoteTab;
|
||||
rv = xulBrowserWin->ForceInitialBrowserRemote(getter_AddRefs(newRemoteTab));
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
*aResult = xulBrowserWin->ForceInitialBrowserRemote(getter_AddRefs(newRemoteTab));
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(*aResult)))
|
||||
return true;
|
||||
|
||||
MOZ_ASSERT(TabParent::GetFrom(newRemoteTab) == newTab);
|
||||
|
||||
|
@ -143,6 +143,7 @@ public:
|
||||
const nsString& aName,
|
||||
const nsString& aFeatures,
|
||||
const nsString& aBaseURI,
|
||||
nsresult* aResult,
|
||||
bool* aWindowIsNew,
|
||||
InfallibleTArray<FrameScriptInfo>* aFrameScripts,
|
||||
nsCString* aURLToLoad) override;
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "mozilla/dom/MediaDevicesBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/MediaManager.h"
|
||||
#include "MediaTrackConstraints.h"
|
||||
#include "nsIEventTarget.h"
|
||||
#include "nsIScriptGlobalObject.h"
|
||||
#include "nsIPermissionManager.h"
|
||||
|
@ -16,6 +16,7 @@ namespace dom {
|
||||
|
||||
class Promise;
|
||||
struct MediaStreamConstraints;
|
||||
struct MediaTrackSupportedConstraints;
|
||||
|
||||
#define MOZILLA_DOM_MEDIADEVICES_IMPLEMENTATION_IID \
|
||||
{ 0x2f784d8a, 0x7485, 0x4280, \
|
||||
@ -32,6 +33,9 @@ public:
|
||||
|
||||
JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
// No code needed, as MediaTrackSupportedConstraints members default to true.
|
||||
void GetSupportedConstraints(MediaTrackSupportedConstraints& aResult) {};
|
||||
|
||||
already_AddRefed<Promise>
|
||||
GetUserMedia(const MediaStreamConstraints& aConstraints, ErrorResult &aRv);
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -465,13 +465,25 @@ public:
|
||||
NS_DECL_NSIMEDIADEVICE
|
||||
|
||||
void SetId(const nsAString& aID);
|
||||
virtual uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
|
||||
protected:
|
||||
virtual ~MediaDevice() {}
|
||||
explicit MediaDevice(MediaEngineSource* aSource);
|
||||
explicit MediaDevice(MediaEngineSource* aSource, bool aIsVideo);
|
||||
static uint32_t FitnessDistance(nsString aN,
|
||||
const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint);
|
||||
private:
|
||||
static bool StringsContain(const dom::OwningStringOrStringSequence& aStrings,
|
||||
nsString aN);
|
||||
static uint32_t FitnessDistance(nsString aN,
|
||||
const dom::ConstrainDOMStringParameters& aParams);
|
||||
protected:
|
||||
nsString mName;
|
||||
nsString mID;
|
||||
dom::MediaSourceEnum mMediaSource;
|
||||
nsRefPtr<MediaEngineSource> mSource;
|
||||
public:
|
||||
bool mIsVideo;
|
||||
};
|
||||
|
||||
class VideoDevice : public MediaDevice
|
||||
@ -482,8 +494,8 @@ public:
|
||||
explicit VideoDevice(Source* aSource);
|
||||
NS_IMETHOD GetType(nsAString& aType);
|
||||
Source* GetSource();
|
||||
uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
|
||||
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs);
|
||||
};
|
||||
|
||||
class AudioDevice : public MediaDevice
|
||||
@ -494,8 +506,8 @@ public:
|
||||
explicit AudioDevice(Source* aSource);
|
||||
NS_IMETHOD GetType(nsAString& aType);
|
||||
Source* GetSource();
|
||||
uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
|
||||
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs);
|
||||
};
|
||||
|
||||
// we could add MediaManager if needed
|
||||
@ -571,21 +583,25 @@ public:
|
||||
|
||||
MediaEnginePrefs mPrefs;
|
||||
|
||||
private:
|
||||
typedef nsTArray<nsRefPtr<MediaDevice>> SourceSet;
|
||||
private:
|
||||
typedef media::Pledge<SourceSet*, dom::MediaStreamError> PledgeSourceSet;
|
||||
|
||||
static bool IsPrivileged();
|
||||
static bool IsLoop(nsIURI* aDocURI);
|
||||
static bool IsPrivateBrowsing(nsPIDOMWindow *window);
|
||||
static nsresult GenerateUUID(nsAString& aResult);
|
||||
static nsresult AnonymizeId(nsAString& aId, const nsACString& aOriginKey);
|
||||
public: // TODO: make private once we upgrade to GCC 4.8+ on linux.
|
||||
static void AnonymizeDevices(SourceSet& aDevices, const nsACString& aOriginKey);
|
||||
static already_AddRefed<nsIWritableVariant> ToJSArray(SourceSet& aDevices);
|
||||
private:
|
||||
already_AddRefed<PledgeSourceSet>
|
||||
EnumerateRawDevices(uint64_t aWindowId,
|
||||
const dom::MediaStreamConstraints& aConstraints);
|
||||
EnumerateRawDevices(uint64_t aWindowId, dom::MediaSourceEnum aSrcType,
|
||||
bool aFake, bool aFakeTracks);
|
||||
already_AddRefed<PledgeSourceSet>
|
||||
EnumerateDevicesImpl(uint64_t aWindowId,
|
||||
const dom::MediaStreamConstraints& aConstraints);
|
||||
EnumerateDevicesImpl(uint64_t aWindowId, dom::MediaSourceEnum aSrcType,
|
||||
bool aFake = false, bool aFakeTracks = false);
|
||||
|
||||
StreamListeners* AddWindowID(uint64_t aWindowId);
|
||||
WindowTable *GetActiveWindows() {
|
||||
|
@ -503,16 +503,12 @@ Parent<Super>::Parent(bool aSameProcess)
|
||||
if (!gMediaParentLog)
|
||||
gMediaParentLog = PR_NewLogModule("MediaParent");
|
||||
LOG(("media::Parent: %p", this));
|
||||
|
||||
MOZ_COUNT_CTOR(Parent);
|
||||
}
|
||||
|
||||
template<class Super>
|
||||
Parent<Super>::~Parent()
|
||||
{
|
||||
LOG(("~media::Parent: %p", this));
|
||||
|
||||
MOZ_COUNT_DTOR(Parent);
|
||||
}
|
||||
|
||||
PMediaParent*
|
||||
|
@ -7,24 +7,28 @@ function check_webm(v, enabled) {
|
||||
check("video/webm", "maybe");
|
||||
check("audio/webm", "maybe");
|
||||
|
||||
var video = ['vp8', 'vp8.0', 'vp9', 'vp9.0'];
|
||||
var audio = ['vorbis', 'opus'];
|
||||
// Check for FxOS case.
|
||||
// Since we want to use OMX webm HW acceleration to speed up vp8 decoding,
|
||||
// we enabled it after Android version 16(JB) as MOZ_OMX_WEBM_DECODER
|
||||
// defined in moz.build. More information is on Bug 986381.
|
||||
// Currently OMX (KK included) webm decoders can only support vp8 and vorbis,
|
||||
// so only vp8 and vorbis will be tested when OMX webm decoder is enabled.
|
||||
var androidVer = SpecialPowers.Cc['@mozilla.org/system-info;1']
|
||||
.getService(SpecialPowers.Ci.nsIPropertyBag2)
|
||||
.getProperty('version');
|
||||
info("android version:"+androidVer);
|
||||
//Check for FxOS case
|
||||
if (navigator.userAgent.indexOf("Mobile") != -1 &&
|
||||
navigator.userAgent.indexOf("Android") == -1 && androidVer > 15) {
|
||||
var video = ['vp8', 'vp8.0'];
|
||||
var audio = ['vorbis'];
|
||||
} else {
|
||||
var video = ['vp8', 'vp8.0', 'vp9', 'vp9.0'];
|
||||
var audio = ['vorbis', 'opus'];
|
||||
navigator.userAgent.indexOf("Android") == -1) {
|
||||
// See nsSystemInfo.cpp, the getProperty('version') and
|
||||
// getProperty('sdk_version') are different.
|
||||
var androidSDKVer = SpecialPowers.Cc['@mozilla.org/system-info;1']
|
||||
.getService(SpecialPowers.Ci.nsIPropertyBag2)
|
||||
.getProperty('sdk_version');
|
||||
info("android version:"+androidSDKVer);
|
||||
if (androidSDKVer > 15) {
|
||||
video = ['vp8', 'vp8.0'];
|
||||
audio = ['vorbis'];
|
||||
}
|
||||
}
|
||||
|
||||
audio.forEach(function(acodec) {
|
||||
check("audio/webm; codecs=" + acodec, "probably");
|
||||
check("video/webm; codecs=" + acodec, "probably");
|
||||
|
@ -491,14 +491,25 @@ var gUnseekableTests = [
|
||||
{ name:"bogus.duh", type:"bogus/duh"}
|
||||
];
|
||||
// Android supports fragmented MP4 playback from 4.3.
|
||||
var androidVersion = SpecialPowers.Cc['@mozilla.org/system-info;1']
|
||||
.getService(SpecialPowers.Ci.nsIPropertyBag2)
|
||||
.getProperty('version');
|
||||
// Fragmented MP4.
|
||||
if (manifestNavigator().userAgent.indexOf("Mobile") != -1 && androidVersion >= 18) {
|
||||
gUnseekableTests = gUnseekableTests.concat([
|
||||
{ name:"street.mp4", type:"video/mp4" }
|
||||
]);
|
||||
if (manifestNavigator().userAgent.indexOf("Mobile") != -1) {
|
||||
// See nsSystemInfo.cpp, the getProperty('version') returns different value
|
||||
// on each platforms, so we need to distinguish the android and B2G platform.
|
||||
var androidVersion;
|
||||
if (navigator.userAgent.indexOf("Android") != -1) {
|
||||
androidSDKVer = SpecialPowers.Cc['@mozilla.org/system-info;1']
|
||||
.getService(SpecialPowers.Ci.nsIPropertyBag2)
|
||||
.getProperty('version');
|
||||
} else if (navigator.userAgent.indexOf("Android") == -1) {
|
||||
androidSDKVer = SpecialPowers.Cc['@mozilla.org/system-info;1']
|
||||
.getService(SpecialPowers.Ci.nsIPropertyBag2)
|
||||
.getProperty('sdk_version');
|
||||
}
|
||||
if (androidVersion >= 18) {
|
||||
gUnseekableTests = gUnseekableTests.concat([
|
||||
{ name:"street.mp4", type:"video/mp4" }
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// These are files suitable for using with a "new Audio" constructor.
|
||||
|
@ -23,7 +23,14 @@ var getVideoImagePixelData = function(v) {
|
||||
"a" + imgData[3];
|
||||
}
|
||||
|
||||
navigator.mozGetUserMedia({video: true, fake: true}, function(stream) {
|
||||
// This test does not appear to work with the "Dummy video source" provided on
|
||||
// linux through the "media.video_loopback_dev" pref in the tree test environment.
|
||||
// To force the built-in fake streams to always be used instead, we specify
|
||||
// fakeTracks, a feature solely of the built-in fake streams (even though we
|
||||
// don't use the extra tracks).
|
||||
|
||||
navigator.mozGetUserMedia({video: true, fake: true, fakeTracks: true },
|
||||
function(stream) {
|
||||
var stream = stream;
|
||||
var video1 = document.getElementById('video1');
|
||||
var video2 = document.getElementById('video2');
|
||||
|
@ -99,17 +99,13 @@ function createMediaElement(type, label) {
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper function for mozGetUserMedia to allow a singular area of control
|
||||
* for determining whether we run this with fake devices or not.
|
||||
* Wrapper function for mediaDevices.getUserMedia used by some tests. Whether
|
||||
* to use fake devices or not is now determined in pref further below instead.
|
||||
*
|
||||
* @param {Dictionary} constraints
|
||||
* The constraints for this mozGetUserMedia callback
|
||||
*/
|
||||
function getUserMedia(constraints) {
|
||||
if (!("fake" in constraints) && FAKE_ENABLED) {
|
||||
constraints["fake"] = FAKE_ENABLED;
|
||||
}
|
||||
|
||||
info("Call getUserMedia for " + JSON.stringify(constraints));
|
||||
return navigator.mediaDevices.getUserMedia(constraints);
|
||||
}
|
||||
@ -138,6 +134,7 @@ function setupEnvironment() {
|
||||
['media.peerconnection.ice.stun_client_maximum_transmits', 14],
|
||||
['media.peerconnection.ice.trickle_grace_period', 30000],
|
||||
['media.navigator.permission.disabled', true],
|
||||
['media.navigator.streams.fake', FAKE_ENABLED],
|
||||
['media.getusermedia.screensharing.enabled', true],
|
||||
['media.getusermedia.screensharing.allowed_domains', "mochi.test"]
|
||||
]
|
||||
|
@ -8,10 +8,24 @@
|
||||
<script type="application/javascript">
|
||||
createHTML({ title: "Run enumerateDevices code", bug: "1046245" });
|
||||
/**
|
||||
Tests covering enumerateDevices API. Exercise code.
|
||||
Tests covering enumerateDevices API and deviceId constraint. Exercise code.
|
||||
*/
|
||||
|
||||
runTest(() => navigator.mediaDevices.enumerateDevices()
|
||||
function mustSucceed(msg, f) {
|
||||
return f().then(() => ok(true, msg + " must succeed"),
|
||||
e => is(e.name, null, msg + " must succeed: " + e.message));
|
||||
}
|
||||
|
||||
function mustFailWith(msg, reason, f) {
|
||||
return f().then(() => ok(false, msg + " must fail"),
|
||||
e => is(e.name, reason, msg + " must fail: " + e.message));
|
||||
}
|
||||
|
||||
var pushPrefs = dict => new Promise(res => SpecialPowers.pushPrefEnv(dict, res));
|
||||
|
||||
runTest(() =>
|
||||
pushPrefs({ set : [["media.navigator.streams.fake", true]] })
|
||||
.then(() => navigator.mediaDevices.enumerateDevices())
|
||||
.then(devices => {
|
||||
ok(devices.length > 0, "At least one device found");
|
||||
devices.forEach(d => {
|
||||
@ -20,7 +34,28 @@ runTest(() => navigator.mediaDevices.enumerateDevices()
|
||||
ok(d.label.length !== undefined, "Device label: " + d.label);
|
||||
is(d.groupId, "", "Don't support groupId yet");
|
||||
});
|
||||
}));
|
||||
})
|
||||
// Check deviceId failure paths for video.
|
||||
.then(() => mustSucceed("unknown plain deviceId on video",
|
||||
() => navigator.mediaDevices.getUserMedia({
|
||||
video: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
|
||||
fake: true,
|
||||
})))
|
||||
.then(() => mustSucceed("unknown plain deviceId on audio",
|
||||
() => navigator.mediaDevices.getUserMedia({
|
||||
audio: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
|
||||
fake: true,
|
||||
})))
|
||||
.then(() => mustFailWith("unknown exact deviceId on video", "NotFoundError",
|
||||
() => navigator.mediaDevices.getUserMedia({
|
||||
video: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
|
||||
fake: true,
|
||||
})))
|
||||
.then(() => mustFailWith("unknown exact deviceId on audio", "NotFoundError",
|
||||
() => navigator.mediaDevices.getUserMedia({
|
||||
audio: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
|
||||
fake: true,
|
||||
}))));
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
|
@ -65,12 +65,40 @@ var tests = [
|
||||
error: null },
|
||||
];
|
||||
|
||||
var mustSupport = [
|
||||
'width', 'height', 'frameRate', 'facingMode', 'deviceId',
|
||||
// Yet to add:
|
||||
// 'aspectRatio', 'frameRate', 'volume', 'sampleRate', 'sampleSize',
|
||||
// 'echoCancellation', 'latency', 'groupId'
|
||||
|
||||
// http://fluffy.github.io/w3c-screen-share/#screen-based-video-constraints
|
||||
// OBE by http://w3c.github.io/mediacapture-screen-share
|
||||
'mediaSource',
|
||||
|
||||
// Experimental https://bugzilla.mozilla.org/show_bug.cgi?id=1131568#c3
|
||||
'browserWindow', 'scrollWithPage',
|
||||
];
|
||||
|
||||
/**
|
||||
* Starts the test run by running through each constraint
|
||||
* test by verifying that the right resolution and rejection is fired.
|
||||
*/
|
||||
|
||||
runTest(function() {
|
||||
|
||||
// Check supported constraints first.
|
||||
var dict = navigator.mediaDevices.getSupportedConstraints();
|
||||
var supported = Object.keys(dict);
|
||||
|
||||
mustSupport.forEach(key => ok(supported.includes(key) && dict[key],
|
||||
"Supports " + key));
|
||||
|
||||
var unexpected = supported.filter(key => !mustSupport.includes(key));
|
||||
is(unexpected.length, 0,
|
||||
"Unexpected support (please update test): " + unexpected);
|
||||
|
||||
// Run constraint tests
|
||||
|
||||
var p = new Promise(resolve => SpecialPowers.pushPrefEnv({
|
||||
set : [ ['media.getusermedia.browser.enabled', false],
|
||||
['media.getusermedia.screensharing.enabled', false] ]
|
||||
|
@ -164,6 +164,15 @@ public:
|
||||
mHasFakeTracks = aHasFakeTracks;
|
||||
}
|
||||
|
||||
/* This call reserves but does not start the device. */
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId) = 0;
|
||||
|
||||
virtual uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId) = 0;
|
||||
|
||||
protected:
|
||||
// Only class' own members can be initialized in constructor initializer list.
|
||||
explicit MediaEngineSource(MediaEngineState aState)
|
||||
@ -224,13 +233,6 @@ class MediaEngineVideoSource : public MediaEngineSource
|
||||
public:
|
||||
virtual ~MediaEngineVideoSource() {}
|
||||
|
||||
/* This call reserves but does not start the device. */
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) = 0;
|
||||
|
||||
virtual uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) = 0;
|
||||
|
||||
protected:
|
||||
explicit MediaEngineVideoSource(MediaEngineState aState)
|
||||
: MediaEngineSource(aState) {}
|
||||
@ -246,10 +248,6 @@ class MediaEngineAudioSource : public MediaEngineSource
|
||||
public:
|
||||
virtual ~MediaEngineAudioSource() {}
|
||||
|
||||
/* This call reserves but does not start the device. */
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) = 0;
|
||||
|
||||
protected:
|
||||
explicit MediaEngineAudioSource(MediaEngineState aState)
|
||||
: MediaEngineSource(aState) {}
|
||||
|
@ -51,121 +51,17 @@ MediaEngineCameraVideoSource::GetCapability(size_t aIndex,
|
||||
aOut = mHardcodedCapabilities[aIndex];
|
||||
}
|
||||
|
||||
// The full algorithm for all cameras. Sources that don't list capabilities
|
||||
// need to fake it and hardcode some by populating mHardcodedCapabilities above.
|
||||
|
||||
// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
|
||||
|
||||
template<class ValueType, class ConstrainRange>
|
||||
/* static */ uint32_t
|
||||
MediaEngineCameraVideoSource::FitnessDistance(ValueType aN,
|
||||
const ConstrainRange& aRange)
|
||||
{
|
||||
if ((aRange.mExact.WasPassed() && aRange.mExact.Value() != aN) ||
|
||||
(aRange.mMin.WasPassed() && aRange.mMin.Value() > aN) ||
|
||||
(aRange.mMax.WasPassed() && aRange.mMax.Value() < aN)) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
if (!aRange.mIdeal.WasPassed() || aN == aRange.mIdeal.Value()) {
|
||||
return 0;
|
||||
}
|
||||
return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.Value()) * 1000) /
|
||||
std::max(std::abs(aN), std::abs(aRange.mIdeal.Value()))));
|
||||
}
|
||||
|
||||
// Binding code doesn't templatize well...
|
||||
|
||||
/*static*/ uint32_t
|
||||
MediaEngineCameraVideoSource::FitnessDistance(int32_t aN,
|
||||
const OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced)
|
||||
{
|
||||
if (aConstraint.IsLong()) {
|
||||
ConstrainLongRange range;
|
||||
(aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsLong());
|
||||
return FitnessDistance(aN, range);
|
||||
} else {
|
||||
return FitnessDistance(aN, aConstraint.GetAsConstrainLongRange());
|
||||
}
|
||||
}
|
||||
|
||||
/*static*/ uint32_t
|
||||
MediaEngineCameraVideoSource::FitnessDistance(double aN,
|
||||
const OwningDoubleOrConstrainDoubleRange& aConstraint,
|
||||
bool aAdvanced)
|
||||
{
|
||||
if (aConstraint.IsDouble()) {
|
||||
ConstrainDoubleRange range;
|
||||
(aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsDouble());
|
||||
return FitnessDistance(aN, range);
|
||||
} else {
|
||||
return FitnessDistance(aN, aConstraint.GetAsConstrainDoubleRange());
|
||||
}
|
||||
}
|
||||
|
||||
// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
|
||||
|
||||
/* static */ uint32_t
|
||||
MediaEngineCameraVideoSource::FitnessDistance(nsString aN,
|
||||
const ConstrainDOMStringParameters& aParams)
|
||||
{
|
||||
struct Func
|
||||
{
|
||||
static bool
|
||||
Contains(const OwningStringOrStringSequence& aStrings, nsString aN)
|
||||
{
|
||||
return aStrings.IsString() ? aStrings.GetAsString() == aN
|
||||
: aStrings.GetAsStringSequence().Contains(aN);
|
||||
}
|
||||
};
|
||||
|
||||
if (aParams.mExact.WasPassed() && !Func::Contains(aParams.mExact.Value(), aN)) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
if (aParams.mIdeal.WasPassed() && !Func::Contains(aParams.mIdeal.Value(), aN)) {
|
||||
return 1000;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* static */ uint32_t
|
||||
MediaEngineCameraVideoSource::FitnessDistance(nsString aN,
|
||||
const OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint,
|
||||
bool aAdvanced)
|
||||
{
|
||||
if (aConstraint.IsString()) {
|
||||
ConstrainDOMStringParameters params;
|
||||
if (aAdvanced) {
|
||||
params.mExact.Construct();
|
||||
params.mExact.Value().SetAsString() = aConstraint.GetAsString();
|
||||
} else {
|
||||
params.mIdeal.Construct();
|
||||
params.mIdeal.Value().SetAsString() = aConstraint.GetAsString();
|
||||
}
|
||||
return FitnessDistance(aN, params);
|
||||
} else if (aConstraint.IsStringSequence()) {
|
||||
ConstrainDOMStringParameters params;
|
||||
if (aAdvanced) {
|
||||
params.mExact.Construct();
|
||||
params.mExact.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
|
||||
} else {
|
||||
params.mIdeal.Construct();
|
||||
params.mIdeal.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
|
||||
}
|
||||
return FitnessDistance(aN, params);
|
||||
} else {
|
||||
return FitnessDistance(aN, aConstraint.GetAsConstrainDOMStringParameters());
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
MediaEngineCameraVideoSource::GetFitnessDistance(const webrtc::CaptureCapability& aCandidate,
|
||||
const MediaTrackConstraintSet &aConstraints,
|
||||
bool aAdvanced)
|
||||
bool aAdvanced,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
// Treat width|height|frameRate == 0 on capability as "can do any".
|
||||
// This allows for orthogonal capabilities that are not in discrete steps.
|
||||
|
||||
uint64_t distance =
|
||||
uint64_t(FitnessDistance(aDeviceId, aConstraints.mDeviceId, aAdvanced)) +
|
||||
uint64_t(FitnessDistance(mFacingMode, aConstraints.mFacingMode, aAdvanced)) +
|
||||
uint64_t(aCandidate.width? FitnessDistance(int32_t(aCandidate.width),
|
||||
aConstraints.mWidth,
|
||||
@ -209,7 +105,8 @@ MediaEngineCameraVideoSource::TrimLessFitCandidates(CapabilitySet& set) {
|
||||
|
||||
uint32_t
|
||||
MediaEngineCameraVideoSource::GetBestFitnessDistance(
|
||||
const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
|
||||
const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
size_t num = NumCapabilities();
|
||||
|
||||
@ -224,7 +121,7 @@ MediaEngineCameraVideoSource::GetBestFitnessDistance(
|
||||
auto& candidate = candidateSet[i];
|
||||
webrtc::CaptureCapability cap;
|
||||
GetCapability(candidate.mIndex, cap);
|
||||
uint32_t distance = GetFitnessDistance(cap, *cs, !first);
|
||||
uint32_t distance = GetFitnessDistance(cap, *cs, !first, aDeviceId);
|
||||
if (distance == UINT32_MAX) {
|
||||
candidateSet.RemoveElementAt(i);
|
||||
} else {
|
||||
@ -268,7 +165,8 @@ MediaEngineCameraVideoSource::LogConstraints(
|
||||
bool
|
||||
MediaEngineCameraVideoSource::ChooseCapability(
|
||||
const MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs)
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
if (MOZ_LOG_TEST(GetMediaManagerLog(), LogLevel::Debug)) {
|
||||
LOG(("ChooseCapability: prefs: %dx%d @%d-%dfps",
|
||||
@ -296,7 +194,7 @@ MediaEngineCameraVideoSource::ChooseCapability(
|
||||
auto& candidate = candidateSet[i];
|
||||
webrtc::CaptureCapability cap;
|
||||
GetCapability(candidate.mIndex, cap);
|
||||
candidate.mDistance = GetFitnessDistance(cap, aConstraints, false);
|
||||
candidate.mDistance = GetFitnessDistance(cap, aConstraints, false, aDeviceId);
|
||||
if (candidate.mDistance == UINT32_MAX) {
|
||||
candidateSet.RemoveElementAt(i);
|
||||
} else {
|
||||
@ -313,7 +211,7 @@ MediaEngineCameraVideoSource::ChooseCapability(
|
||||
auto& candidate = candidateSet[i];
|
||||
webrtc::CaptureCapability cap;
|
||||
GetCapability(candidate.mIndex, cap);
|
||||
if (GetFitnessDistance(cap, cs, true) == UINT32_MAX) {
|
||||
if (GetFitnessDistance(cap, cs, true, aDeviceId) == UINT32_MAX) {
|
||||
rejects.AppendElement(candidate);
|
||||
candidateSet.RemoveElementAt(i);
|
||||
} else {
|
||||
@ -345,7 +243,7 @@ MediaEngineCameraVideoSource::ChooseCapability(
|
||||
for (auto& candidate : candidateSet) {
|
||||
webrtc::CaptureCapability cap;
|
||||
GetCapability(candidate.mIndex, cap);
|
||||
candidate.mDistance = GetFitnessDistance(cap, prefs, false);
|
||||
candidate.mDistance = GetFitnessDistance(cap, prefs, false, aDeviceId);
|
||||
}
|
||||
TrimLessFitCandidates(candidateSet);
|
||||
}
|
||||
|
@ -16,7 +16,8 @@
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MediaEngineCameraVideoSource : public MediaEngineVideoSource
|
||||
class MediaEngineCameraVideoSource : public MediaEngineVideoSource,
|
||||
private MediaConstraintsHelper
|
||||
{
|
||||
public:
|
||||
explicit MediaEngineCameraVideoSource(int aIndex,
|
||||
@ -59,7 +60,8 @@ public:
|
||||
}
|
||||
|
||||
uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) override;
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId) override;
|
||||
|
||||
virtual void Shutdown() override {};
|
||||
|
||||
@ -80,28 +82,18 @@ protected:
|
||||
layers::Image* aImage,
|
||||
TrackID aID,
|
||||
StreamTime delta);
|
||||
template<class ValueType, class ConstrainRange>
|
||||
static uint32_t FitnessDistance(ValueType aN, const ConstrainRange& aRange);
|
||||
static uint32_t FitnessDistance(int32_t aN,
|
||||
const dom::OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced);
|
||||
static uint32_t FitnessDistance(double aN,
|
||||
const dom::OwningDoubleOrConstrainDoubleRange& aConstraint, bool aAdvanced);
|
||||
static uint32_t FitnessDistance(nsString aN,
|
||||
const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint,
|
||||
bool aAdvanced);
|
||||
static uint32_t FitnessDistance(nsString aN,
|
||||
const dom::ConstrainDOMStringParameters& aParams);
|
||||
|
||||
uint32_t GetFitnessDistance(const webrtc::CaptureCapability& aCandidate,
|
||||
const dom::MediaTrackConstraintSet &aConstraints,
|
||||
bool aAdvanced);
|
||||
bool aAdvanced,
|
||||
const nsString& aDeviceId);
|
||||
static void TrimLessFitCandidates(CapabilitySet& set);
|
||||
static void LogConstraints(const dom::MediaTrackConstraintSet& aConstraints,
|
||||
bool aAdvanced);
|
||||
virtual size_t NumCapabilities();
|
||||
virtual void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut);
|
||||
bool ChooseCapability(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs);
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId);
|
||||
void SetName(nsString aName);
|
||||
void SetUUID(const char* aUUID);
|
||||
const nsCString& GetUUID(); // protected access
|
||||
|
@ -68,9 +68,24 @@ MediaEngineDefaultVideoSource::GetUUID(nsACString& aUUID)
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
MediaEngineDefaultVideoSource::GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
uint32_t distance = 0;
|
||||
|
||||
for (const MediaTrackConstraintSet* cs : aConstraintSets) {
|
||||
distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
|
||||
break; // distance is read from first entry only
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineDefaultVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs)
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
if (mState != kReleased) {
|
||||
return NS_ERROR_FAILURE;
|
||||
@ -348,9 +363,24 @@ MediaEngineDefaultAudioSource::GetUUID(nsACString& aUUID)
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
MediaEngineDefaultAudioSource::GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
uint32_t distance = 0;
|
||||
|
||||
for (const MediaTrackConstraintSet* cs : aConstraintSets) {
|
||||
distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
|
||||
break; // distance is read from first entry only
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs)
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
if (mState != kReleased) {
|
||||
return NS_ERROR_FAILURE;
|
||||
|
@ -31,7 +31,8 @@ class MediaEngineDefault;
|
||||
* The default implementation of the MediaEngine interface.
|
||||
*/
|
||||
class MediaEngineDefaultVideoSource : public nsITimerCallback,
|
||||
public MediaEngineVideoSource
|
||||
public MediaEngineVideoSource,
|
||||
private MediaConstraintsHelper
|
||||
{
|
||||
public:
|
||||
MediaEngineDefaultVideoSource();
|
||||
@ -42,7 +43,8 @@ public:
|
||||
virtual void GetUUID(nsACString&) override;
|
||||
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) override;
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId) override;
|
||||
virtual nsresult Deallocate() override;
|
||||
virtual nsresult Start(SourceMediaStream*, TrackID) override;
|
||||
virtual nsresult Stop(SourceMediaStream*, TrackID) override;
|
||||
@ -56,10 +58,8 @@ public:
|
||||
TrackID aId,
|
||||
StreamTime aDesiredTime) override;
|
||||
virtual uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId) override;
|
||||
|
||||
virtual bool IsFake() override {
|
||||
return true;
|
||||
@ -101,7 +101,8 @@ protected:
|
||||
class SineWaveGenerator;
|
||||
|
||||
class MediaEngineDefaultAudioSource : public nsITimerCallback,
|
||||
public MediaEngineAudioSource
|
||||
public MediaEngineAudioSource,
|
||||
private MediaConstraintsHelper
|
||||
{
|
||||
public:
|
||||
MediaEngineDefaultAudioSource();
|
||||
@ -112,7 +113,8 @@ public:
|
||||
virtual void GetUUID(nsACString&) override;
|
||||
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) override;
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId) override;
|
||||
virtual nsresult Deallocate() override;
|
||||
virtual nsresult Start(SourceMediaStream*, TrackID) override;
|
||||
virtual nsresult Stop(SourceMediaStream*, TrackID) override;
|
||||
@ -139,6 +141,10 @@ public:
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
virtual uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId) override;
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSITIMERCALLBACK
|
||||
|
||||
|
@ -147,13 +147,14 @@ MediaEngineGonkVideoSource::NumCapabilities()
|
||||
|
||||
nsresult
|
||||
MediaEngineGonkVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs)
|
||||
const MediaEnginePrefs& aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
LOG((__FUNCTION__));
|
||||
|
||||
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
||||
if (mState == kReleased && mInitDone) {
|
||||
ChooseCapability(aConstraints, aPrefs);
|
||||
ChooseCapability(aConstraints, aPrefs, aDeviceId);
|
||||
NS_DispatchToMainThread(WrapRunnable(nsRefPtr<MediaEngineGonkVideoSource>(this),
|
||||
&MediaEngineGonkVideoSource::AllocImpl));
|
||||
mCallbackMonitor.Wait();
|
||||
|
@ -61,7 +61,8 @@ public:
|
||||
}
|
||||
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) override;
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId) override;
|
||||
virtual nsresult Deallocate() override;
|
||||
virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) override;
|
||||
virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;
|
||||
|
@ -120,7 +120,8 @@ MediaEngineTabVideoSource::GetUUID(nsACString_internal& aUuid)
|
||||
|
||||
nsresult
|
||||
MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs)
|
||||
const MediaEnginePrefs& aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
// windowId and scrollWithPage are not proper constraints, so just read them.
|
||||
// They have no well-defined behavior in advanced, so ignore them there.
|
||||
|
@ -22,7 +22,8 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
|
||||
virtual void GetName(nsAString_internal&) override;
|
||||
virtual void GetUUID(nsACString_internal&) override;
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &,
|
||||
const mozilla::MediaEnginePrefs&) override;
|
||||
const mozilla::MediaEnginePrefs&,
|
||||
const nsString& aDeviceId) override;
|
||||
virtual nsresult Deallocate() override;
|
||||
virtual nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID) override;
|
||||
virtual void SetDirectListeners(bool aHasDirectListeners) override {};
|
||||
@ -34,7 +35,8 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
|
||||
return dom::MediaSourceEnum::Browser;
|
||||
}
|
||||
virtual uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) override
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
@ -86,11 +86,13 @@ public:
|
||||
, mMediaSource(aMediaSource)
|
||||
{
|
||||
MOZ_ASSERT(aVideoEnginePtr);
|
||||
MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other);
|
||||
Init();
|
||||
}
|
||||
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs) override;
|
||||
const MediaEnginePrefs& aPrefs,
|
||||
const nsString& aDeviceId) override;
|
||||
virtual nsresult Deallocate() override;
|
||||
virtual nsresult Start(SourceMediaStream*, TrackID) override;
|
||||
virtual nsresult Stop(SourceMediaStream*, TrackID) override;
|
||||
@ -132,7 +134,8 @@ private:
|
||||
};
|
||||
|
||||
class MediaEngineWebRTCAudioSource : public MediaEngineAudioSource,
|
||||
public webrtc::VoEMediaProcess
|
||||
public webrtc::VoEMediaProcess,
|
||||
private MediaConstraintsHelper
|
||||
{
|
||||
public:
|
||||
MediaEngineWebRTCAudioSource(nsIThread* aThread, webrtc::VoiceEngine* aVoiceEnginePtr,
|
||||
@ -161,7 +164,8 @@ public:
|
||||
virtual void GetUUID(nsACString& aUUID) override;
|
||||
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs) override;
|
||||
const MediaEnginePrefs& aPrefs,
|
||||
const nsString& aDeviceId) override;
|
||||
virtual nsresult Deallocate() override;
|
||||
virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) override;
|
||||
virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;
|
||||
@ -189,6 +193,10 @@ public:
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
virtual uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId) override;
|
||||
|
||||
// VoEMediaProcess.
|
||||
void Process(int channel, webrtc::ProcessingTypes type,
|
||||
int16_t audio10ms[], int length,
|
||||
|
@ -259,9 +259,31 @@ MediaEngineWebRTCAudioSource::Config(bool aEchoOn, uint32_t aEcho,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// GetBestFitnessDistance returns the best distance the capture device can offer
|
||||
// as a whole, given an accumulated number of ConstraintSets.
|
||||
// Ideal values are considered in the first ConstraintSet only.
|
||||
// Plain values are treated as Ideal in the first ConstraintSet.
|
||||
// Plain values are treated as Exact in subsequent ConstraintSets.
|
||||
// Infinity = UINT32_MAX e.g. device cannot satisfy accumulated ConstraintSets.
|
||||
// A finite result may be used to calculate this device's ranking as a choice.
|
||||
|
||||
uint32_t MediaEngineWebRTCAudioSource::GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
uint32_t distance = 0;
|
||||
|
||||
for (const MediaTrackConstraintSet* cs : aConstraintSets) {
|
||||
distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
|
||||
break; // distance is read from first entry only
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineWebRTCAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs)
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
if (mState == kReleased) {
|
||||
if (mInitDone) {
|
||||
|
@ -208,14 +208,15 @@ MediaEngineWebRTCVideoSource::GetCapability(size_t aIndex,
|
||||
|
||||
nsresult
|
||||
MediaEngineWebRTCVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs)
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
LOG((__FUNCTION__));
|
||||
if (mState == kReleased && mInitDone) {
|
||||
// Note: if shared, we don't allow a later opener to affect the resolution.
|
||||
// (This may change depending on spec changes for Constraints/settings)
|
||||
|
||||
if (!ChooseCapability(aConstraints, aPrefs)) {
|
||||
if (!ChooseCapability(aConstraints, aPrefs, aDeviceId)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
if (mViECapture->AllocateCaptureDevice(GetUUID().get(),
|
||||
|
@ -81,4 +81,130 @@ FlattenedConstraints::FlattenedConstraints(const dom::MediaTrackConstraints& aOt
|
||||
}
|
||||
}
|
||||
|
||||
// MediaEngine helper
|
||||
//
|
||||
// The full algorithm for all devices. Sources that don't list capabilities
|
||||
// need to fake it and hardcode some by populating mHardcodedCapabilities above.
|
||||
//
|
||||
// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
|
||||
|
||||
// First, all devices have a minimum distance based on their deviceId.
|
||||
// If you have no other constraints, use this one. Reused by all device types.
|
||||
|
||||
uint32_t
|
||||
MediaConstraintsHelper::GetMinimumFitnessDistance(
|
||||
const dom::MediaTrackConstraintSet &aConstraints,
|
||||
bool aAdvanced,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
uint64_t distance =
|
||||
uint64_t(FitnessDistance(aDeviceId, aConstraints.mDeviceId, aAdvanced));
|
||||
|
||||
// This function is modeled on MediaEngineCameraVideoSource::GetFitnessDistance
|
||||
// and will make more sense once more audio constraints are added.
|
||||
|
||||
return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
|
||||
}
|
||||
|
||||
template<class ValueType, class ConstrainRange>
|
||||
/* static */ uint32_t
|
||||
MediaConstraintsHelper::FitnessDistance(ValueType aN,
|
||||
const ConstrainRange& aRange)
|
||||
{
|
||||
if ((aRange.mExact.WasPassed() && aRange.mExact.Value() != aN) ||
|
||||
(aRange.mMin.WasPassed() && aRange.mMin.Value() > aN) ||
|
||||
(aRange.mMax.WasPassed() && aRange.mMax.Value() < aN)) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
if (!aRange.mIdeal.WasPassed() || aN == aRange.mIdeal.Value()) {
|
||||
return 0;
|
||||
}
|
||||
return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.Value()) * 1000) /
|
||||
std::max(std::abs(aN), std::abs(aRange.mIdeal.Value()))));
|
||||
}
|
||||
|
||||
// Binding code doesn't templatize well...
|
||||
|
||||
/*static*/ uint32_t
|
||||
MediaConstraintsHelper::FitnessDistance(int32_t aN,
|
||||
const OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced)
|
||||
{
|
||||
if (aConstraint.IsLong()) {
|
||||
ConstrainLongRange range;
|
||||
(aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsLong());
|
||||
return FitnessDistance(aN, range);
|
||||
} else {
|
||||
return FitnessDistance(aN, aConstraint.GetAsConstrainLongRange());
|
||||
}
|
||||
}
|
||||
|
||||
/*static*/ uint32_t
|
||||
MediaConstraintsHelper::FitnessDistance(double aN,
|
||||
const OwningDoubleOrConstrainDoubleRange& aConstraint,
|
||||
bool aAdvanced)
|
||||
{
|
||||
if (aConstraint.IsDouble()) {
|
||||
ConstrainDoubleRange range;
|
||||
(aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsDouble());
|
||||
return FitnessDistance(aN, range);
|
||||
} else {
|
||||
return FitnessDistance(aN, aConstraint.GetAsConstrainDoubleRange());
|
||||
}
|
||||
}
|
||||
|
||||
// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
|
||||
|
||||
/* static */ uint32_t
|
||||
MediaConstraintsHelper::FitnessDistance(nsString aN,
|
||||
const ConstrainDOMStringParameters& aParams)
|
||||
{
|
||||
struct Func
|
||||
{
|
||||
static bool
|
||||
Contains(const OwningStringOrStringSequence& aStrings, nsString aN)
|
||||
{
|
||||
return aStrings.IsString() ? aStrings.GetAsString() == aN
|
||||
: aStrings.GetAsStringSequence().Contains(aN);
|
||||
}
|
||||
};
|
||||
|
||||
if (aParams.mExact.WasPassed() && !Func::Contains(aParams.mExact.Value(), aN)) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
if (aParams.mIdeal.WasPassed() && !Func::Contains(aParams.mIdeal.Value(), aN)) {
|
||||
return 1000;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* static */ uint32_t
|
||||
MediaConstraintsHelper::FitnessDistance(nsString aN,
|
||||
const OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint,
|
||||
bool aAdvanced)
|
||||
{
|
||||
if (aConstraint.IsString()) {
|
||||
ConstrainDOMStringParameters params;
|
||||
if (aAdvanced) {
|
||||
params.mExact.Construct();
|
||||
params.mExact.Value().SetAsString() = aConstraint.GetAsString();
|
||||
} else {
|
||||
params.mIdeal.Construct();
|
||||
params.mIdeal.Value().SetAsString() = aConstraint.GetAsString();
|
||||
}
|
||||
return FitnessDistance(aN, params);
|
||||
} else if (aConstraint.IsStringSequence()) {
|
||||
ConstrainDOMStringParameters params;
|
||||
if (aAdvanced) {
|
||||
params.mExact.Construct();
|
||||
params.mExact.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
|
||||
} else {
|
||||
params.mIdeal.Construct();
|
||||
params.mIdeal.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
|
||||
}
|
||||
return FitnessDistance(aN, params);
|
||||
} else {
|
||||
return FitnessDistance(aN, aConstraint.GetAsConstrainDOMStringParameters());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/dom/MediaStreamTrackBinding.h"
|
||||
#include "mozilla/dom/MediaTrackConstraintSetBinding.h"
|
||||
#include "mozilla/dom/MediaTrackSupportedConstraintsBinding.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -82,6 +83,29 @@ struct FlattenedConstraints : public NormalizedConstraintSet
|
||||
explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther);
|
||||
};
|
||||
|
||||
// A helper class for MediaEngines
|
||||
|
||||
class MediaConstraintsHelper
|
||||
{
|
||||
protected:
|
||||
template<class ValueType, class ConstrainRange>
|
||||
static uint32_t FitnessDistance(ValueType aN, const ConstrainRange& aRange);
|
||||
static uint32_t FitnessDistance(int32_t aN,
|
||||
const dom::OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced);
|
||||
static uint32_t FitnessDistance(double aN,
|
||||
const dom::OwningDoubleOrConstrainDoubleRange& aConstraint, bool aAdvanced);
|
||||
static uint32_t FitnessDistance(nsString aN,
|
||||
const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint,
|
||||
bool aAdvanced);
|
||||
static uint32_t FitnessDistance(nsString aN,
|
||||
const dom::ConstrainDOMStringParameters& aParams);
|
||||
|
||||
static uint32_t
|
||||
GetMinimumFitnessDistance(const dom::MediaTrackConstraintSet &aConstraints,
|
||||
bool aAdvanced,
|
||||
const nsString& aDeviceId);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* MEDIATRACKCONSTRAINTS_H_ */
|
||||
|
@ -351,6 +351,8 @@ typedef enum {
|
||||
/* In the NPDrawingModelCoreAnimation drawing model, the browser asks the plug-in for a Core Animation layer. */
|
||||
, NPPVpluginCoreAnimationLayer = 1003
|
||||
#endif
|
||||
/* Notification that the plugin just started or stopped playing audio */
|
||||
, NPPVpluginIsPlayingAudio = 4000
|
||||
|
||||
} NPPVariable;
|
||||
|
||||
@ -409,6 +411,7 @@ typedef enum {
|
||||
, NPNVsupportsCocoaBool = 3001 /* TRUE if the browser supports the Cocoa event model */
|
||||
, NPNVsupportsUpdatedCocoaTextInputBool = 3002 /* TRUE if the browser supports the updated
|
||||
Cocoa text input specification. */
|
||||
, NPNVmuteAudioBool = 4000 /* Request that the browser wants to mute or unmute the plugin */
|
||||
, NPNVsupportsCompositingCoreAnimationPluginsBool = 74656 /* TRUE if the browser supports
|
||||
CA model compositing */
|
||||
#endif
|
||||
|
@ -13,7 +13,7 @@
|
||||
[Func="Navigator::HasUserMediaSupport"]
|
||||
interface MediaDevices : EventTarget {
|
||||
// attribute EventHandler ondevicechange;
|
||||
// static Dictionary getSupportedConstraints (DOMString kind);
|
||||
MediaTrackSupportedConstraints getSupportedConstraints();
|
||||
|
||||
[Throws]
|
||||
Promise<sequence<MediaDeviceInfo>> enumerateDevices();
|
||||
|
@ -17,13 +17,13 @@ dictionary MediaStreamConstraints {
|
||||
(boolean or MediaTrackConstraints) audio = false;
|
||||
(boolean or MediaTrackConstraints) video = false;
|
||||
boolean picture = false; // Mozilla legacy
|
||||
boolean fake = false; // For testing purpose. Generates frames of solid
|
||||
// colors if video is enabled, and sound of 1Khz sine
|
||||
// wave if audio is enabled.
|
||||
boolean fakeTracks = false; // For testing purpose, works only if fake is
|
||||
// enabled. Enable fakeTracks returns a stream
|
||||
// with two extra empty video tracks and three
|
||||
// extra empty audio tracks.
|
||||
boolean fake; // For testing purpose. Generates frames of solid
|
||||
// colors if video is enabled, and sound of 1Khz sine
|
||||
// wave if audio is enabled.
|
||||
boolean fakeTracks; // For testing purpose, works only if fake is
|
||||
// enabled. Enable fakeTracks returns a stream
|
||||
// with two extra empty video tracks and three
|
||||
// extra empty audio tracks.
|
||||
DOMString? peerIdentity = null;
|
||||
};
|
||||
|
||||
|
@ -15,7 +15,8 @@ enum SupportedVideoConstraints {
|
||||
"frameRate",
|
||||
"mediaSource",
|
||||
"browserWindow",
|
||||
"scrollWithPage"
|
||||
"scrollWithPage",
|
||||
"deviceId"
|
||||
};
|
||||
|
||||
enum SupportedAudioConstraints {
|
||||
@ -30,6 +31,7 @@ dictionary MediaTrackConstraintSet {
|
||||
DOMString mediaSource = "camera";
|
||||
long long browserWindow;
|
||||
boolean scrollWithPage;
|
||||
ConstrainDOMString deviceId;
|
||||
};
|
||||
|
||||
typedef (long or ConstrainLongRange) ConstrainLong;
|
||||
|
35
dom/webidl/MediaTrackSupportedConstraints.webidl
Normal file
35
dom/webidl/MediaTrackSupportedConstraints.webidl
Normal file
@ -0,0 +1,35 @@
|
||||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/.
|
||||
*
|
||||
* The origin of this IDL file is
|
||||
* http://dev.w3.org/2011/webrtc/editor/getusermedia.html
|
||||
*/
|
||||
|
||||
dictionary MediaTrackSupportedConstraints {
|
||||
boolean width = true;
|
||||
boolean height = true;
|
||||
boolean aspectRatio; // to be supported
|
||||
boolean frameRate = true;
|
||||
boolean facingMode = true;
|
||||
boolean volume; // to be supported
|
||||
boolean sampleRate; // to be supported
|
||||
boolean sampleSize; // to be supported
|
||||
boolean echoCancellation; // to be supported
|
||||
boolean latency; // to be supported
|
||||
boolean deviceId = true;
|
||||
boolean groupId; // to be supported
|
||||
|
||||
// Mozilla-specific extensions:
|
||||
|
||||
// http://fluffy.github.io/w3c-screen-share/#screen-based-video-constraints
|
||||
// OBE by http://w3c.github.io/mediacapture-screen-share
|
||||
|
||||
boolean mediaSource = true;
|
||||
|
||||
// Experimental https://bugzilla.mozilla.org/show_bug.cgi?id=1131568#c3
|
||||
|
||||
boolean browserWindow = true;
|
||||
boolean scrollWithPage = true;
|
||||
};
|
@ -289,6 +289,7 @@ WEBIDL_FILES = [
|
||||
'MediaStreamError.webidl',
|
||||
'MediaStreamTrack.webidl',
|
||||
'MediaTrackConstraintSet.webidl',
|
||||
'MediaTrackSupportedConstraints.webidl',
|
||||
'MenuBoxObject.webidl',
|
||||
'MessageChannel.webidl',
|
||||
'MessageEvent.webidl',
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "nsIInputStreamPump.h"
|
||||
#include "nsIIOService.h"
|
||||
#include "nsIProtocolHandler.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
#include "nsIStreamLoader.h"
|
||||
#include "nsIStreamListenerTee.h"
|
||||
@ -42,14 +43,18 @@
|
||||
#include "mozilla/dom/cache/CacheTypes.h"
|
||||
#include "mozilla/dom/cache/Cache.h"
|
||||
#include "mozilla/dom/cache/CacheStorage.h"
|
||||
#include "mozilla/dom/ChannelInfo.h"
|
||||
#include "mozilla/dom/Exceptions.h"
|
||||
#include "mozilla/dom/InternalResponse.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/PromiseNativeHandler.h"
|
||||
#include "mozilla/dom/Response.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "Principal.h"
|
||||
#include "WorkerFeature.h"
|
||||
#include "WorkerPrivate.h"
|
||||
#include "WorkerRunnable.h"
|
||||
#include "WorkerScope.h"
|
||||
|
||||
#define MAX_CONCURRENT_SCRIPTS 1000
|
||||
|
||||
@ -60,6 +65,9 @@ using mozilla::dom::cache::CacheStorage;
|
||||
using mozilla::dom::Promise;
|
||||
using mozilla::dom::PromiseNativeHandler;
|
||||
using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult;
|
||||
using mozilla::ErrorResult;
|
||||
using mozilla::ipc::PrincipalInfo;
|
||||
using mozilla::UniquePtr;
|
||||
|
||||
namespace {
|
||||
|
||||
@ -434,7 +442,7 @@ private:
|
||||
bool mFailed;
|
||||
nsCOMPtr<nsIInputStreamPump> mPump;
|
||||
nsCOMPtr<nsIURI> mBaseURI;
|
||||
ChannelInfo mChannelInfo;
|
||||
mozilla::dom::ChannelInfo mChannelInfo;
|
||||
UniquePtr<PrincipalInfo> mPrincipalInfo;
|
||||
};
|
||||
|
||||
@ -598,8 +606,8 @@ private:
|
||||
MOZ_ASSERT(channel == loadInfo.mChannel);
|
||||
|
||||
// We synthesize the result code, but its never exposed to content.
|
||||
nsRefPtr<InternalResponse> ir =
|
||||
new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
|
||||
nsRefPtr<mozilla::dom::InternalResponse> ir =
|
||||
new mozilla::dom::InternalResponse(200, NS_LITERAL_CSTRING("OK"));
|
||||
ir->SetBody(mReader);
|
||||
|
||||
// Set the channel info of the channel on the response so that it's
|
||||
@ -627,9 +635,10 @@ private:
|
||||
|
||||
ir->SetPrincipalInfo(Move(principalInfo));
|
||||
|
||||
nsRefPtr<Response> response = new Response(mCacheCreator->Global(), ir);
|
||||
nsRefPtr<mozilla::dom::Response> response =
|
||||
new mozilla::dom::Response(mCacheCreator->Global(), ir);
|
||||
|
||||
RequestOrUSVString request;
|
||||
mozilla::dom::RequestOrUSVString request;
|
||||
|
||||
MOZ_ASSERT(!loadInfo.mFullURL.IsEmpty());
|
||||
request.SetAsUSVString().Rebind(loadInfo.mFullURL.Data(),
|
||||
@ -1077,7 +1086,7 @@ private:
|
||||
void
|
||||
DataReceivedFromCache(uint32_t aIndex, const uint8_t* aString,
|
||||
uint32_t aStringLen,
|
||||
const ChannelInfo& aChannelInfo,
|
||||
const mozilla::dom::ChannelInfo& aChannelInfo,
|
||||
UniquePtr<PrincipalInfo> aPrincipalInfo)
|
||||
{
|
||||
AssertIsOnMainThread();
|
||||
@ -1102,14 +1111,14 @@ private:
|
||||
mWorkerPrivate->SetBaseURI(finalURI);
|
||||
}
|
||||
|
||||
DebugOnly<nsIPrincipal*> principal = mWorkerPrivate->GetPrincipal();
|
||||
mozilla::DebugOnly<nsIPrincipal*> principal = mWorkerPrivate->GetPrincipal();
|
||||
MOZ_ASSERT(principal);
|
||||
nsILoadGroup* loadGroup = mWorkerPrivate->GetLoadGroup();
|
||||
MOZ_ASSERT(loadGroup);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> responsePrincipal =
|
||||
PrincipalInfoToPrincipal(*aPrincipalInfo);
|
||||
DebugOnly<bool> equal = false;
|
||||
mozilla::DebugOnly<bool> equal = false;
|
||||
MOZ_ASSERT(responsePrincipal && NS_SUCCEEDED(responsePrincipal->Equals(principal, &equal)));
|
||||
MOZ_ASSERT(equal);
|
||||
|
||||
@ -1245,7 +1254,7 @@ CacheCreator::CreateCacheStorage(nsIPrincipal* aPrincipal)
|
||||
nsIXPConnect* xpc = nsContentUtils::XPConnect();
|
||||
MOZ_ASSERT(xpc, "This should never be null!");
|
||||
|
||||
AutoSafeJSContext cx;
|
||||
mozilla::AutoSafeJSContext cx;
|
||||
nsCOMPtr<nsIXPConnectJSObjectHolder> sandbox;
|
||||
nsresult rv = xpc->CreateSandbox(cx, aPrincipal, getter_AddRefs(sandbox));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
@ -1269,7 +1278,7 @@ CacheCreator::CreateCacheStorage(nsIPrincipal* aPrincipal)
|
||||
// to this point.
|
||||
ErrorResult error;
|
||||
mCacheStorage =
|
||||
CacheStorage::CreateOnMainThread(cache::CHROME_ONLY_NAMESPACE,
|
||||
CacheStorage::CreateOnMainThread(mozilla::dom::cache::CHROME_ONLY_NAMESPACE,
|
||||
mSandboxGlobalObject,
|
||||
aPrincipal, mPrivateBrowsing,
|
||||
true /* force trusted origin */,
|
||||
@ -1422,11 +1431,11 @@ CacheScriptLoader::Load(Cache* aCache)
|
||||
MOZ_ASSERT(mLoadInfo.mFullURL.IsEmpty());
|
||||
CopyUTF8toUTF16(spec, mLoadInfo.mFullURL);
|
||||
|
||||
RequestOrUSVString request;
|
||||
mozilla::dom::RequestOrUSVString request;
|
||||
request.SetAsUSVString().Rebind(mLoadInfo.mFullURL.Data(),
|
||||
mLoadInfo.mFullURL.Length());
|
||||
|
||||
CacheQueryOptions params;
|
||||
mozilla::dom::CacheQueryOptions params;
|
||||
|
||||
ErrorResult error;
|
||||
nsRefPtr<Promise> promise = aCache->Match(request, params, error);
|
||||
@ -1468,7 +1477,7 @@ CacheScriptLoader::ResolvedCallback(JSContext* aCx,
|
||||
MOZ_ASSERT(aValue.isObject());
|
||||
|
||||
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
|
||||
Response* response = nullptr;
|
||||
mozilla::dom::Response* response = nullptr;
|
||||
nsresult rv = UNWRAP_OBJECT(Response, obj, response);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
Fail(rv);
|
||||
@ -1478,10 +1487,9 @@ CacheScriptLoader::ResolvedCallback(JSContext* aCx,
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
response->GetBody(getter_AddRefs(inputStream));
|
||||
mChannelInfo = response->GetChannelInfo();
|
||||
const UniquePtr<mozilla::ipc::PrincipalInfo>& pInfo =
|
||||
response->GetPrincipalInfo();
|
||||
const UniquePtr<PrincipalInfo>& pInfo = response->GetPrincipalInfo();
|
||||
if (pInfo) {
|
||||
mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(*pInfo);
|
||||
mPrincipalInfo = mozilla::MakeUnique<PrincipalInfo>(*pInfo);
|
||||
}
|
||||
|
||||
if (!inputStream) {
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "ServiceWorker.h"
|
||||
|
||||
#include "nsIDocument.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "ServiceWorkerClient.h"
|
||||
#include "ServiceWorkerManager.h"
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "ServiceWorkerContainer.h"
|
||||
|
||||
#include "mozilla/dom/MessageEvent.h"
|
||||
#include "mozilla/dom/Navigator.h"
|
||||
#include "nsGlobalWindow.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "WorkerPrivate.h"
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "nsIServiceWorkerManager.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Services.h"
|
||||
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
@ -63,6 +63,7 @@
|
||||
#include "ServiceWorkerRegistration.h"
|
||||
#include "ServiceWorkerScriptCache.h"
|
||||
#include "ServiceWorkerEvents.h"
|
||||
#include "SharedWorker.h"
|
||||
#include "WorkerInlines.h"
|
||||
#include "WorkerPrivate.h"
|
||||
#include "WorkerRunnable.h"
|
||||
|
@ -7,9 +7,12 @@
|
||||
#include "ServiceWorkerManagerParent.h"
|
||||
#include "ServiceWorkerManagerService.h"
|
||||
#include "mozilla/AppProcessChecker.h"
|
||||
#include "mozilla/dom/ContentParent.h"
|
||||
#include "mozilla/dom/ServiceWorkerRegistrar.h"
|
||||
#include "mozilla/ipc/BackgroundParent.h"
|
||||
#include "mozilla/ipc/BackgroundUtils.h"
|
||||
#include "mozilla/unused.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -9,18 +9,22 @@
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/PromiseWorkerProxy.h"
|
||||
#include "mozilla/dom/ServiceWorkerRegistrationBinding.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "ServiceWorker.h"
|
||||
#include "ServiceWorkerManager.h"
|
||||
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIServiceWorkerManager.h"
|
||||
#include "nsISupportsPrimitives.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
|
||||
#include "WorkerPrivate.h"
|
||||
#include "Workers.h"
|
||||
#include "WorkerScope.h"
|
||||
|
||||
#ifndef MOZ_SIMPLEPUSH
|
||||
#include "mozilla/dom/PushManagerBinding.h"
|
||||
|
@ -9,11 +9,17 @@
|
||||
#include "mozilla/dom/CacheBinding.h"
|
||||
#include "mozilla/dom/cache/CacheStorage.h"
|
||||
#include "mozilla/dom/cache/Cache.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/PromiseWorkerProxy.h"
|
||||
#include "mozilla/ipc/BackgroundUtils.h"
|
||||
#include "mozilla/ipc/PBackgroundSharedTypes.h"
|
||||
#include "nsIHttpChannelInternal.h"
|
||||
#include "nsIStreamLoader.h"
|
||||
#include "nsIThreadRetargetableRequest.h"
|
||||
|
||||
#include "nsIPrincipal.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsScriptLoader.h"
|
||||
#include "Workers.h"
|
||||
|
||||
using mozilla::dom::cache::Cache;
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "nsString.h"
|
||||
|
||||
class nsILoadGroup;
|
||||
class nsIPrincipal;
|
||||
|
||||
namespace mozilla {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user