Merge m-c to b-i

This commit is contained in:
Phil Ringnalda 2015-07-03 19:17:50 -07:00
commit a6af0a454c
597 changed files with 56898 additions and 3365 deletions

View File

@ -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");

View File

@ -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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -100,12 +100,29 @@ loop.store.TextChatStore = (function() {
sentTimestamp: messageData.sentTimestamp,
receivedTimestamp: messageData.receivedTimestamp
};
var newList = this._storeState.messageList.concat(message);
var newList = [].concat(this._storeState.messageList);
var isContext = message.contentType === CHAT_CONTENT_TYPES.CONTEXT;
if (isContext) {
var contextUpdated = false;
for (var i = 0, l = newList.length; i < l; ++i) {
// Replace the current context message with the provided update.
if (newList[i].contentType === CHAT_CONTENT_TYPES.CONTEXT) {
newList[i] = message;
contextUpdated = true;
break;
}
}
if (!contextUpdated) {
newList.push(message);
}
} else {
newList.push(message);
}
this.setStoreState({ messageList: newList });
// Notify MozLoopService if appropriate that a message has been appended
// and it should therefore check if we need a different sized window or not.
if (type != CHAT_MESSAGE_TYPES.SPECIAL) {
if (message.contentType != CHAT_CONTENT_TYPES.ROOM_NAME) {
window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
}
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1243,8 +1243,8 @@
width: 298},
React.createElement("div", {className: "fx-embedded"},
React.createElement(TextChatView, {dispatcher: dispatcher,
showAlways: false,
showRoomName: false})
showRoomName: false,
useDesktopPaths: false})
)
),
@ -1257,8 +1257,8 @@
React.createElement("div", {className: "media-wrapper"},
React.createElement(TextChatView, {
dispatcher: dispatcher,
showAlways: true,
showRoomName: true})
showRoomName: true,
useDesktopPaths: false})
)
)
)
@ -1313,7 +1313,7 @@
// This simulates the mocha layout for errors which means we can run
// this alongside our other unit tests but use the same harness.
var expectedWarningsCount = 53;
var expectedWarningsCount = 29;
var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
if (uncaughtError || warningsMismatch) {
$("#results").append("<div class='failures'><em>" +

View File

@ -1243,8 +1243,8 @@
width={298}>
<div className="fx-embedded">
<TextChatView dispatcher={dispatcher}
showAlways={false}
showRoomName={false} />
showRoomName={false}
useDesktopPaths={false} />
</div>
</FramedExample>
@ -1257,8 +1257,8 @@
<div className="media-wrapper">
<TextChatView
dispatcher={dispatcher}
showAlways={true}
showRoomName={true} />
showRoomName={true}
useDesktopPaths={false} />
</div>
</div>
</FramedExample>
@ -1313,7 +1313,7 @@
// This simulates the mocha layout for errors which means we can run
// this alongside our other unit tests but use the same harness.
var expectedWarningsCount = 53;
var expectedWarningsCount = 29;
var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
if (uncaughtError || warningsMismatch) {
$("#results").append("<div class='failures'><em>" +

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {

View File

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

View File

@ -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)

View File

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

View File

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

View File

@ -143,6 +143,7 @@ public:
const nsString& aName,
const nsString& aFeatures,
const nsString& aBaseURI,
nsresult* aResult,
bool* aWindowIsNew,
InfallibleTArray<FrameScriptInfo>* aFrameScripts,
nsCString* aURLToLoad) override;

View File

@ -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"

View File

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

View File

@ -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() {

View File

@ -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*

View File

@ -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");

View File

@ -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.

View File

@ -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');

View File

@ -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"]
]

View File

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

View File

@ -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] ]

View File

@ -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) {}

View File

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

View File

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

View File

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

View File

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

View File

@ -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();

View File

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

View File

@ -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.

View File

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

View File

@ -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,

View File

@ -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) {

View File

@ -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(),

View File

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

View File

@ -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_ */

View File

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

View File

@ -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();

View File

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

View File

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

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

View File

@ -289,6 +289,7 @@ WEBIDL_FILES = [
'MediaStreamError.webidl',
'MediaStreamTrack.webidl',
'MediaTrackConstraintSet.webidl',
'MediaTrackSupportedConstraints.webidl',
'MenuBoxObject.webidl',
'MessageChannel.webidl',
'MessageEvent.webidl',

View File

@ -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) {

View File

@ -6,6 +6,7 @@
#include "ServiceWorker.h"
#include "nsIDocument.h"
#include "nsPIDOMWindow.h"
#include "ServiceWorkerClient.h"
#include "ServiceWorkerManager.h"

View File

@ -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"

View File

@ -10,6 +10,7 @@
#include "nsIServiceWorkerManager.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "nsCycleCollectionParticipant.h"

View File

@ -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"

View File

@ -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 {

View File

@ -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"

View File

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

View File

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