Merge mozilla-central to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-09-21 14:06:24 +02:00
commit 64ebf55ec9
388 changed files with 10534 additions and 7504 deletions

View File

@ -2058,7 +2058,7 @@ DocAccessible::ValidateARIAOwned()
for (uint32_t idx = 0; idx < childEls->Length(); idx++) { for (uint32_t idx = 0; idx < childEls->Length(); idx++) {
nsIContent* childEl = childEls->ElementAt(idx); nsIContent* childEl = childEls->ElementAt(idx);
Accessible* child = GetAccessible(childEl); Accessible* child = GetAccessible(childEl);
if (child && !child->GetFrame()) { if (child && child->IsInDocument() && !child->GetFrame()) {
UpdateTreeOnRemoval(child->Parent(), childEl); UpdateTreeOnRemoval(child->Parent(), childEl);
} }
} }

View File

@ -62,7 +62,6 @@ pref("browser.cache.memory_limit", 2048); // 2 MB
/* image cache prefs */ /* image cache prefs */
pref("image.cache.size", 1048576); // bytes pref("image.cache.size", 1048576); // bytes
pref("image.high_quality_downscaling.enabled", false);
pref("canvas.image.cache.limit", 20971520); // 20 MB pref("canvas.image.cache.limit", 20971520); // 20 MB
/* offline cache prefs */ /* offline cache prefs */

View File

@ -679,11 +679,6 @@
@RESPATH@/components/ActivityWrapper.js @RESPATH@/components/ActivityWrapper.js
@RESPATH@/components/ActivityMessageConfigurator.js @RESPATH@/components/ActivityMessageConfigurator.js
@RESPATH@/components/TCPSocket.js
@RESPATH@/components/TCPServerSocket.js
@RESPATH@/components/TCPSocketParentIntermediary.js
@RESPATH@/components/TCPSocket.manifest
@RESPATH@/components/Payment.js @RESPATH@/components/Payment.js
@RESPATH@/components/PaymentFlowInfo.js @RESPATH@/components/PaymentFlowInfo.js
@RESPATH@/components/PaymentProvider.js @RESPATH@/components/PaymentProvider.js

View File

@ -1693,6 +1693,7 @@ pref("image.mem.max_decoded_image_kb", 256000);
pref("loop.enabled", true); pref("loop.enabled", true);
pref("loop.textChat.enabled", true); pref("loop.textChat.enabled", true);
pref("loop.server", "https://loop.services.mozilla.com/v0"); pref("loop.server", "https://loop.services.mozilla.com/v0");
pref("loop.linkClicker.url", "https://hello.firefox.com/");
pref("loop.gettingStarted.seen", false); pref("loop.gettingStarted.seen", false);
pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/"); pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
pref("loop.gettingStarted.resumeOnFirstJoin", false); pref("loop.gettingStarted.resumeOnFirstJoin", false);

View File

@ -214,10 +214,6 @@ var gFxAccounts = {
this.showDoorhanger("sync-start-panel"); this.showDoorhanger("sync-start-panel");
}, },
showSyncFailedDoorhanger: function () {
this.showDoorhanger("sync-error-panel");
},
updateUI: function () { updateUI: function () {
this.updateAppMenuItem(); this.updateAppMenuItem();
this.updateMigrationNotification(); this.updateMigrationNotification();

View File

@ -449,28 +449,6 @@
</hbox> </hbox>
</panel> </panel>
<!-- Sync Error Panel -->
<panel id="sync-error-panel" class="sync-panel" type="arrow" hidden="true"
noautofocus="true" onclick="this.hidePopup();"
flip="slide">
<hbox class="sync-panel-outer">
<image class="sync-panel-icon"/>
<vbox class="sync-panel-inner">
<description id="sync-error-panel-title"
value="&syncErrorPanel.heading;"/>
<description id="sync-error-panel-subtitle"
value="&syncErrorPanel.subTitle;"/>
<hbox class="sync-panel-button-box">
<spacer flex="1"/>
<button class="sync-panel-button"
label="&syncErrorPanel.signInButton.label;"
accesskey="&syncErrorPanel.signInButton.accesskey;"
onclick="gFxAccounts.openSignInAgainPage();"/>
</hbox>
</vbox>
</hbox>
</panel>
<!-- Bookmarks and history tooltip --> <!-- Bookmarks and history tooltip -->
<tooltip id="bhTooltip"/> <tooltip id="bhTooltip"/>

View File

@ -50,6 +50,7 @@
"SocialShare": false, "SocialShare": false,
"Task": false, "Task": false,
"UITour": false, "UITour": false,
"WebChannel": false,
"XPCOMUtils": false, "XPCOMUtils": false,
"uuidgen": true, "uuidgen": true,
// Test Related // Test Related

View File

@ -607,89 +607,6 @@ html[dir="rtl"] .room-entry-context-actions > .dropdown-menu {
width: 16px; width: 16px;
} }
/* Buttons */
.button-group {
display: flex;
flex-direction: row;
width: 100%;
padding-top: 6px;
}
.button-group > .button {
flex: 1;
margin: 0 5px;
min-height: 3rem;
font-size: 1.2rem;
line-height: 1rem;
font-weight: 300;
border-radius: 4px;
}
.button-group > .button:first-child {
-moz-margin-start: 0;
}
.button-group > .button:last-child {
-moz-margin-end: 0;
}
.button {
padding: 2px 5px;
background-color: #fbfbfb;
color: #333;
border-radius: 2px;
min-height: 26px;
font-size: 1.2rem;
line-height: 1.2rem;
border: none;
}
.button:hover {
background-color: #ebebeb;
}
.button:active {
background-color: #ccc;
color: #fff;
}
.button.button-accept {
background-color: #00a9dc;
color: #fff;
}
.button.button-accept:hover,
.button.button-accept:hover:active {
background-color: #5cccee;
color: #fff;
}
.button.button-cancel {
background-color: #ebebeb;
border: 0;
color: #000;
width: 105px; /* based on fixed width of Cancel button from mockup */
flex: 0 0 auto;
}
.button.button-cancel:hover,
.button.button-cancel:hover:active {
background-color: #dcd6d6;
color: #000;
}
.button.button-cancel:disabled {
background-color: #ebebeb;
color: #c3c3c3;
}
.button.button-accept:active {
background-color: #3aa689;
border-color: #3aa689;
color: #fff;
}
.button-close { .button-close {
background-color: transparent; background-color: transparent;
background-image: url(../shared/img/icons-10x10.svg#close); background-image: url(../shared/img/icons-10x10.svg#close);

View File

@ -719,6 +719,7 @@ loop.conversationViews = (function(mozL10n) {
mozLoop: this.props.mozLoop, mozLoop: this.props.mozLoop,
publishStream: this.publishStream, publishStream: this.publishStream,
settingsMenuItems: settingsMenuItems, settingsMenuItems: settingsMenuItems,
show: true,
video: this.props.video}) video: this.props.video})
) )
) )

View File

@ -719,6 +719,7 @@ loop.conversationViews = (function(mozL10n) {
mozLoop={this.props.mozLoop} mozLoop={this.props.mozLoop}
publishStream={this.publishStream} publishStream={this.publishStream}
settingsMenuItems={settingsMenuItems} settingsMenuItems={settingsMenuItems}
show={true}
video={this.props.video} /> video={this.props.video} />
</sharedViews.MediaLayoutView> </sharedViews.MediaLayoutView>
</div> </div>

View File

@ -975,7 +975,7 @@ loop.panel = (function(_, mozL10n) {
React.createElement("div", {className: "new-room-view"}, React.createElement("div", {className: "new-room-view"},
React.createElement("div", {className: contextClasses}, React.createElement("div", {className: contextClasses},
React.createElement(Checkbox, {checked: this.state.checked, React.createElement(Checkbox, {checked: this.state.checked,
label: mozL10n.get("context_inroom_label"), label: mozL10n.get("context_inroom_label2"),
onChange: this.onCheckboxChange}), onChange: this.onCheckboxChange}),
React.createElement(sharedViews.ContextUrlView, { React.createElement(sharedViews.ContextUrlView, {
allowClick: false, allowClick: false,

View File

@ -975,7 +975,7 @@ loop.panel = (function(_, mozL10n) {
<div className="new-room-view"> <div className="new-room-view">
<div className={contextClasses}> <div className={contextClasses}>
<Checkbox checked={this.state.checked} <Checkbox checked={this.state.checked}
label={mozL10n.get("context_inroom_label")} label={mozL10n.get("context_inroom_label2")}
onChange={this.onCheckboxChange} /> onChange={this.onCheckboxChange} />
<sharedViews.ContextUrlView <sharedViews.ContextUrlView
allowClick={false} allowClick={false}

View File

@ -449,25 +449,6 @@ loop.roomViews = (function(mozL10n) {
mozLoop.telemetryAddValue("LOOP_ROOM_CONTEXT_CLICK", 1); mozLoop.telemetryAddValue("LOOP_ROOM_CONTEXT_CLICK", 1);
}, },
handleCheckboxChange: function(state) {
if (state.checked) {
// The checkbox was checked, prefill the fields with the values available
// in `availableContext`.
var context = this.state.availableContext;
this.setState({
newRoomURL: context.url,
newRoomDescription: context.description,
newRoomThumbnail: context.previewImage
});
} else {
this.setState({
newRoomURL: "",
newRoomDescription: "",
newRoomThumbnail: ""
});
}
},
handleFormSubmit: function(event) { handleFormSubmit: function(event) {
event && event.preventDefault(); event && event.preventDefault();
@ -516,27 +497,13 @@ loop.roomViews = (function(mozL10n) {
var cx = React.addons.classSet; var cx = React.addons.classSet;
var availableContext = this.state.availableContext; 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 ( return (
React.createElement("div", {className: "room-context"}, React.createElement("div", {className: "room-context"},
React.createElement("p", {className: cx({"error": !!this.props.error, React.createElement("p", {className: cx({"error": !!this.props.error,
"error-display-area": true})}, "error-display-area": true})},
mozL10n.get("rooms_change_failed_label") mozL10n.get("rooms_change_failed_label")
), ),
React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")), React.createElement("h2", {className: "room-context-header"}, mozL10n.get("context_inroom_header")),
React.createElement(sharedViews.Checkbox, {
additionalClass: cx({ hide: !checkboxLabel }),
checked: checked,
disabled: checked,
label: checkboxLabel,
onChange: this.handleCheckboxChange,
useEllipsis: true,
value: location}),
React.createElement("form", {onSubmit: this.handleFormSubmit}, React.createElement("form", {onSubmit: this.handleFormSubmit},
React.createElement("input", {className: "room-context-name", React.createElement("input", {className: "room-context-name",
maxLength: this.maxRoomNameLength, maxLength: this.maxRoomNameLength,
@ -554,16 +521,17 @@ loop.roomViews = (function(mozL10n) {
onKeyDown: this.handleTextareaKeyDown, onKeyDown: this.handleTextareaKeyDown,
placeholder: mozL10n.get("context_edit_comments_placeholder"), placeholder: mozL10n.get("context_edit_comments_placeholder"),
rows: "2", type: "text", rows: "2", type: "text",
valueLink: this.linkState("newRoomDescription")}) valueLink: this.linkState("newRoomDescription")}),
), React.createElement(sharedViews.ButtonGroup, null,
React.createElement("button", {className: "btn btn-info", React.createElement(sharedViews.Button, {additionalClass: "button-cancel",
disabled: this.props.savingContext, caption: mozL10n.get("context_cancel_label"),
onClick: this.handleFormSubmit}, onClick: this.handleCloseClick}),
mozL10n.get("context_save_label2") React.createElement(sharedViews.Button, {additionalClass: "button-accept",
), caption: mozL10n.get("context_done_label"),
React.createElement("button", {className: "room-context-btn-close", disabled: this.props.savingContext,
onClick: this.handleCloseClick, onClick: this.handleFormSubmit})
title: mozL10n.get("cancel_button")}) )
)
) )
); );
} }
@ -819,6 +787,7 @@ loop.roomViews = (function(mozL10n) {
publishStream: this.publishStream, publishStream: this.publishStream,
screenShare: screenShareData, screenShare: screenShareData,
settingsMenuItems: settingsMenuItems, settingsMenuItems: settingsMenuItems,
show: !shouldRenderEditContextView,
video: {enabled: !this.state.videoMuted, visible: true}}), video: {enabled: !this.state.videoMuted, visible: true}}),
React.createElement(DesktopRoomInvitationView, { React.createElement(DesktopRoomInvitationView, {
dispatcher: this.props.dispatcher, dispatcher: this.props.dispatcher,

View File

@ -449,25 +449,6 @@ loop.roomViews = (function(mozL10n) {
mozLoop.telemetryAddValue("LOOP_ROOM_CONTEXT_CLICK", 1); mozLoop.telemetryAddValue("LOOP_ROOM_CONTEXT_CLICK", 1);
}, },
handleCheckboxChange: function(state) {
if (state.checked) {
// The checkbox was checked, prefill the fields with the values available
// in `availableContext`.
var context = this.state.availableContext;
this.setState({
newRoomURL: context.url,
newRoomDescription: context.description,
newRoomThumbnail: context.previewImage
});
} else {
this.setState({
newRoomURL: "",
newRoomDescription: "",
newRoomThumbnail: ""
});
}
},
handleFormSubmit: function(event) { handleFormSubmit: function(event) {
event && event.preventDefault(); event && event.preventDefault();
@ -516,27 +497,13 @@ loop.roomViews = (function(mozL10n) {
var cx = React.addons.classSet; var cx = React.addons.classSet;
var availableContext = this.state.availableContext; 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 ( return (
<div className="room-context"> <div className="room-context">
<p className={cx({"error": !!this.props.error, <p className={cx({"error": !!this.props.error,
"error-display-area": true})}> "error-display-area": true})}>
{mozL10n.get("rooms_change_failed_label")} {mozL10n.get("rooms_change_failed_label")}
</p> </p>
<div className="room-context-label">{mozL10n.get("context_inroom_label")}</div> <h2 className="room-context-header">{mozL10n.get("context_inroom_header")}</h2>
<sharedViews.Checkbox
additionalClass={cx({ hide: !checkboxLabel })}
checked={checked}
disabled={checked}
label={checkboxLabel}
onChange={this.handleCheckboxChange}
useEllipsis={true}
value={location} />
<form onSubmit={this.handleFormSubmit}> <form onSubmit={this.handleFormSubmit}>
<input className="room-context-name" <input className="room-context-name"
maxLength={this.maxRoomNameLength} maxLength={this.maxRoomNameLength}
@ -555,15 +522,16 @@ loop.roomViews = (function(mozL10n) {
placeholder={mozL10n.get("context_edit_comments_placeholder")} placeholder={mozL10n.get("context_edit_comments_placeholder")}
rows="2" type="text" rows="2" type="text"
valueLink={this.linkState("newRoomDescription")} /> valueLink={this.linkState("newRoomDescription")} />
<sharedViews.ButtonGroup>
<sharedViews.Button additionalClass="button-cancel"
caption={mozL10n.get("context_cancel_label")}
onClick={this.handleCloseClick} />
<sharedViews.Button additionalClass="button-accept"
caption={mozL10n.get("context_done_label")}
disabled={this.props.savingContext}
onClick={this.handleFormSubmit} />
</sharedViews.ButtonGroup>
</form> </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> </div>
); );
} }
@ -819,6 +787,7 @@ loop.roomViews = (function(mozL10n) {
publishStream={this.publishStream} publishStream={this.publishStream}
screenShare={screenShareData} screenShare={screenShareData}
settingsMenuItems={settingsMenuItems} settingsMenuItems={settingsMenuItems}
show={!shouldRenderEditContextView}
video={{enabled: !this.state.videoMuted, visible: true}} /> video={{enabled: !this.state.videoMuted, visible: true}} />
<DesktopRoomInvitationView <DesktopRoomInvitationView
dispatcher={this.props.dispatcher} dispatcher={this.props.dispatcher}

View File

@ -75,6 +75,87 @@ p {
width: 100%; width: 100%;
} }
/* Buttons */
.button-group {
display: flex;
flex-direction: row;
width: 100%;
padding-top: 6px;
}
.button-group > .button {
flex: 1;
margin: 0 5px;
min-height: 3rem;
font-size: 1.2rem;
line-height: 1rem;
font-weight: 300;
border-radius: 4px;
}
.button-group > .button:first-child {
-moz-margin-start: 0;
}
.button-group > .button:last-child {
-moz-margin-end: 0;
}
.button {
padding: 2px 5px;
background-color: #fbfbfb;
color: #333;
border-radius: 2px;
min-height: 26px;
font-size: 1.2rem;
line-height: 1.2rem;
border: none;
}
.button:hover {
background-color: #ebebeb;
}
.button:active {
background-color: #ccc;
color: #fff;
}
.button.button-accept {
background-color: #00a9dc;
color: #fff;
}
.button.button-accept:hover,
.button.button-accept:hover:active {
background-color: #5cccee;
color: #fff;
}
.button.button-cancel {
background-color: #ebebeb;
border: 0;
color: #000;
}
.button.button-cancel:hover,
.button.button-cancel:hover:active {
background-color: #dcd6d6;
color: #000;
}
.button.button-cancel:disabled {
background-color: #ebebeb;
color: #c3c3c3;
}
.button.button-accept:active {
background-color: #3aa689;
border-color: #3aa689;
color: #fff;
}
/* A reset for all button-appearing elements, with the lowest-common /* A reset for all button-appearing elements, with the lowest-common
* denominator of the needed rules. Intended to be used as a base class * denominator of the needed rules. Intended to be used as a base class
* together with .btn-* * together with .btn-*

View File

@ -915,13 +915,6 @@ html[dir="rtl"] .room-conversation-wrapper header a {
align-items: center; align-items: center;
} }
.room-invitation-overlay input[type="text"] {
display: block;
background-color: rgba(0,0,0,.5);
border-radius: 3px;
padding: .5em;
}
.room-invitation-overlay .btn-group { .room-invitation-overlay .btn-group {
padding: 0 0 5rem 0; padding: 0 0 5rem 0;
} }
@ -999,10 +992,9 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
} }
.room-context { .room-context {
background: rgba(0,0,0,.8); background: #fff;
border-top: 2px solid #444; border-top: 2px solid #444;
border-bottom: 2px solid #444; border-bottom: 2px solid #444;
padding: .5rem;
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 0; bottom: 0;
@ -1049,13 +1041,11 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
width: 100%; width: 100%;
} }
.room-context-label { .room-context-header {
margin-bottom: 1em; color: #333;
} font-size: 1.2rem;
font-weight: bold;
.room-context-label, margin: 1rem auto;
.room-context > .checkbox-wrapper > label {
color: #fff;
} }
.room-context-comment { .room-context-comment {
@ -1079,71 +1069,40 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
} }
.room-context > form { .room-context > form {
margin-bottom: 1rem;
padding: .5rem;
width: 100%; width: 100%;
} }
.room-context > form > textarea, .room-context > form > textarea,
.room-context > form > input[type="text"] { .room-context > form > input[type="text"] {
display: block; display: block;
background: rgba(0,0,0,.5);
font-family: "Helvetica Neue", Arial, sans;
border: 1px solid rgba(255,255,255,.2);
width: 100%; width: 100%;
padding: .5em; outline: none;
border-radius: 3px; border-radius: 4px;
resize: none; margin: 10px 0;
color: #fff; border: 1px solid #c3c3c3;
height: 2.6rem;
padding: 6px;
font-size: 1.1rem;
color: #4a4a4a;
box-shadow: none;
} }
.room-context > form > textarea { .room-context > form > textarea {
font-size: 1em; font-family: inherit;
height: 5.2rem;
resize: none;
} }
.room-context > form > input:not([disabled]).room-context-url { .room-context > form > textarea::-moz-placeholder,
color: #0095dd; .room-context > form > input::-moz-placeholder {
color: #999;
} }
.room-context > form > input[disabled] { .room-context > form > textarea:focus,
background-color: rgba(255,255,255,.2); .room-context > form > input:focus {
color: rgba(255,255,255,.4); border: 0.1rem solid #5cccee;
}
.room-context > form > textarea:not(:last-of-type),
.room-context > form > input[type="text"] {
margin: 0 0 .5em 0;
}
.room-context > .btn {
margin: .5em 0 0;
font-size: 1.1em;
padding: 0 .5em;
align-self: flex-end;
}
.room-context-btn-close {
position: absolute;
right: 8px;
/* 8px offset + 2px border-top */
top: 10px;
width: 8px;
height: 8px;
background-color: transparent;
background-image: url("../img/icons-10x10.svg#close-darkergrey");
background-size: 8px 8px;
background-repeat: no-repeat;
border: 0;
padding: 0;
cursor: pointer;
}
.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 {
right: auto;
left: 8px;
} }
.media-layout { .media-layout {

View File

@ -359,6 +359,7 @@ loop.shared.views = (function(_, mozL10n) {
publishStream: React.PropTypes.func.isRequired, publishStream: React.PropTypes.func.isRequired,
screenShare: React.PropTypes.object, screenShare: React.PropTypes.object,
settingsMenuItems: React.PropTypes.array, settingsMenuItems: React.PropTypes.array,
show: React.PropTypes.bool.isRequired,
video: React.PropTypes.object.isRequired video: React.PropTypes.object.isRequired
}, },
@ -440,6 +441,10 @@ loop.shared.views = (function(_, mozL10n) {
}, },
render: function() { render: function() {
if (!this.props.show) {
return null;
}
var cx = React.addons.classSet; var cx = React.addons.classSet;
var conversationToolbarCssClasses = cx({ var conversationToolbarCssClasses = cx({
"conversation-toolbar": true, "conversation-toolbar": true,
@ -798,7 +803,7 @@ loop.shared.views = (function(_, mozL10n) {
return null; return null;
} }
return React.createElement("p", null, mozL10n.get("context_inroom_label")); return React.createElement("p", null, mozL10n.get("context_inroom_label2"));
}, },
render: function() { render: function() {

View File

@ -359,6 +359,7 @@ loop.shared.views = (function(_, mozL10n) {
publishStream: React.PropTypes.func.isRequired, publishStream: React.PropTypes.func.isRequired,
screenShare: React.PropTypes.object, screenShare: React.PropTypes.object,
settingsMenuItems: React.PropTypes.array, settingsMenuItems: React.PropTypes.array,
show: React.PropTypes.bool.isRequired,
video: React.PropTypes.object.isRequired video: React.PropTypes.object.isRequired
}, },
@ -440,6 +441,10 @@ loop.shared.views = (function(_, mozL10n) {
}, },
render: function() { render: function() {
if (!this.props.show) {
return null;
}
var cx = React.addons.classSet; var cx = React.addons.classSet;
var conversationToolbarCssClasses = cx({ var conversationToolbarCssClasses = cx({
"conversation-toolbar": true, "conversation-toolbar": true,
@ -798,7 +803,7 @@ loop.shared.views = (function(_, mozL10n) {
return null; return null;
} }
return <p>{mozL10n.get("context_inroom_label")}</p>; return <p>{mozL10n.get("context_inroom_label2")}</p>;
}, },
render: function() { render: function() {

View File

@ -15,6 +15,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm"); "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
"resource://services-common/utils.js"); "resource://services-common/utils.js");
XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
"resource://gre/modules/WebChannel.jsm");
XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() { XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {}); const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
return new EventEmitter(); return new EventEmitter();
@ -45,6 +48,9 @@ const MAX_TIME_BEFORE_ENCRYPTION = 30 * 60 * 1000;
// Wait time between individual re-encryption cycles (1 second). // Wait time between individual re-encryption cycles (1 second).
const TIME_BETWEEN_ENCRYPTIONS = 1000; const TIME_BETWEEN_ENCRYPTIONS = 1000;
// This is the pref name for the url of the standalone pages.
const LINKCLICKER_URL_PREFNAME = "loop.linkClicker.url";
const roomsPushNotification = function(version, channelID) { const roomsPushNotification = function(version, channelID) {
return LoopRoomsInternal.onNotification(version, channelID); return LoopRoomsInternal.onNotification(version, channelID);
}; };
@ -58,6 +64,8 @@ var gDirty = true;
var gCurrentUser = null; var gCurrentUser = null;
// Global variable that keeps track of the room cache. // Global variable that keeps track of the room cache.
var gRoomsCache = null; var gRoomsCache = null;
// Global variable that keeps track of the link clicker channel.
var gLinkClickerChannel = null;
/** /**
* Extend a `target` object with the properties defined in `source`. * Extend a `target` object with the properties defined in `source`.
@ -177,6 +185,40 @@ var LoopRoomsInternal = {
} }
}, },
/**
* Initialises the rooms, sets up the link clicker listener.
*/
init: function() {
Services.prefs.addObserver(LINKCLICKER_URL_PREFNAME,
this.setupLinkClickerListener.bind(this), false);
this.setupLinkClickerListener();
},
/**
* Sets up a WebChannel listener for the link clicker so that we can open
* rooms in the Firefox UI.
*/
setupLinkClickerListener: function() {
// Ensure any existing channel is tidied up.
if (gLinkClickerChannel) {
gLinkClickerChannel.stopListening();
gLinkClickerChannel = null;
}
let linkClickerUrl = Services.prefs.getCharPref(LINKCLICKER_URL_PREFNAME);
// Don't do anything if there's no url.
if (!linkClickerUrl) {
return;
}
let uri = Services.io.newURI(linkClickerUrl, null, null);
gLinkClickerChannel = new WebChannel("loop-link-clicker", uri);
gLinkClickerChannel.listen(this._handleLinkClickerMessage.bind(this));
},
/** /**
* @var {String} sessionType The type of user session. May be 'FXA' or 'GUEST'. * @var {String} sessionType The type of user session. May be 'FXA' or 'GUEST'.
*/ */
@ -905,6 +947,42 @@ var LoopRoomsInternal = {
eventEmitter.emit("refresh"); eventEmitter.emit("refresh");
this.getAll(null, () => {}); this.getAll(null, () => {});
} }
},
/**
* Handles a message received from the content channel.
*
* @param {String} id The channel id.
* @param {Object} message The message received.
* @param {Object} sendingContext The context for the sending location.
*/
_handleLinkClickerMessage: function(id, message, sendingContext) {
if (!message) {
return;
}
let sendResponse = response => {
gLinkClickerChannel.send({
response: response
}, sendingContext);
};
let hasRoom = this.rooms.has(message.roomToken);
switch (message.command) {
case "checkWillOpenRoom":
sendResponse(hasRoom);
break;
case "openRoom":
if (hasRoom) {
this.open(message.roomToken);
}
sendResponse(hasRoom);
break;
default:
sendResponse(false);
break;
}
} }
}; };
Object.freeze(LoopRoomsInternal); Object.freeze(LoopRoomsInternal);
@ -926,6 +1004,10 @@ Object.freeze(LoopRoomsInternal);
* See the internal code for the API documentation. * See the internal code for the API documentation.
*/ */
this.LoopRooms = { this.LoopRooms = {
init: function() {
LoopRoomsInternal.init();
},
get participantsCount() { get participantsCount() {
return LoopRoomsInternal.participantsCount; return LoopRoomsInternal.participantsCount;
}, },
@ -1016,6 +1098,24 @@ this.LoopRooms = {
once: (...params) => eventEmitter.once(...params), once: (...params) => eventEmitter.once(...params),
off: (...params) => eventEmitter.off(...params) off: (...params) => eventEmitter.off(...params),
/**
* Expose the internal rooms map for testing purposes only. This avoids
* needing to mock the server interfaces.
*
* @param {Map} roomsCache The new cache data to set for testing purposes. If
* not specified, it will reset the cache.
*/
_setRoomsCache: function(roomsCache) {
LoopRoomsInternal.rooms.clear();
if (roomsCache) {
// Need a clone as the internal map is read-only.
for (let [key, value] of roomsCache) {
LoopRoomsInternal.rooms.set(key, value);
}
}
}
}; };
Object.freeze(this.LoopRooms); Object.freeze(this.LoopRooms);

View File

@ -1211,6 +1211,9 @@ this.MozLoopService = {
// stub out API functions for unit testing // stub out API functions for unit testing
Object.freeze(this); Object.freeze(this);
// Initialise anything that needs it in rooms.
LoopRooms.init();
// Don't do anything if loop is not enabled. // Don't do anything if loop is not enabled.
if (!Services.prefs.getBoolPref("loop.enabled")) { if (!Services.prefs.getBoolPref("loop.enabled")) {
return Promise.reject(new Error("loop is not enabled")); return Promise.reject(new Error("loop is not enabled"));

View File

@ -46,6 +46,7 @@ TESTS="
${LOOPDIR}/test/mochitest ${LOOPDIR}/test/mochitest
browser/components/uitour/test/browser_UITour_loop.js browser/components/uitour/test/browser_UITour_loop.js
browser/base/content/test/general/browser_devices_get_user_media_about_urls.js browser/base/content/test/general/browser_devices_get_user_media_about_urls.js
browser/base/content/test/general/browser_parsable_css.js
" "
./mach mochitest $TESTS ./mach mochitest $TESTS
@ -53,9 +54,3 @@ TESTS="
if [ "$1" != "--skip-e10s" ]; then if [ "$1" != "--skip-e10s" ]; then
./mach mochitest --e10s $TESTS ./mach mochitest --e10s $TESTS
fi fi
# This is currently disabled because the test itself is busted. Once bug
# 1062821 is landed, we should see if things work again, and then re-enable it.
# The re-enabling is tracked in bug 1113350.
#
# browser/base/content/test/general/browser_parsable_css.js \

View File

@ -582,6 +582,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
hangup: this.leaveRoom, hangup: this.leaveRoom,
hangupButtonLabel: mozL10n.get("rooms_leave_button_label"), hangupButtonLabel: mozL10n.get("rooms_leave_button_label"),
publishStream: this.publishStream, publishStream: this.publishStream,
show: true,
video: {enabled: !this.state.videoMuted, video: {enabled: !this.state.videoMuted,
visible: this._roomIsActive()}}) visible: this._roomIsActive()}})
), ),

View File

@ -582,6 +582,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
hangup={this.leaveRoom} hangup={this.leaveRoom}
hangupButtonLabel={mozL10n.get("rooms_leave_button_label")} hangupButtonLabel={mozL10n.get("rooms_leave_button_label")}
publishStream={this.publishStream} publishStream={this.publishStream}
show={true}
video={{enabled: !this.state.videoMuted, video={{enabled: !this.state.videoMuted,
visible: this._roomIsActive()}} /> visible: this._roomIsActive()}} />
</sharedViews.MediaLayoutView> </sharedViews.MediaLayoutView>

View File

@ -84,9 +84,9 @@ status_error=Something went wrong
# Text chat strings # Text chat strings
chat_textbox_placeholder=Type here… chat_textbox_placeholder=Type here…
# LOCALIZATION NOTE (context_inroom_label): this string is followed by the # LOCALIZATION NOTE (context_inroom_label2): this string is followed by the
# title/URL of the website you are having a conversation about, displayed on a # title/URL of the website you are having a conversation about, displayed on a
# separate line. If this structure doesn't work for your locale, you might want # separate line. If this structure doesn't work for your locale, you might want
# to consider this as a stand-alone title. See example screenshot: # to consider this as a stand-alone title. See example screenshot:
# https://bug1084991.bugzilla.mozilla.org/attachment.cgi?id=8614721 # https://bug1084991.bugzilla.mozilla.org/attachment.cgi?id=8614721
context_inroom_label=Let's talk about: context_inroom_label2=Let's Talk About:

View File

@ -708,16 +708,13 @@ describe("loop.roomViews", function () {
expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null); expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null);
}); });
it("should hide the form when the edit button is clicked again", function() { it("should not have a settings menu when the edit button is clicked", function() {
view = mountTestComponent(); view = mountTestComponent();
var editButton = view.getDOMNode().querySelector(".settings-menu > li.entry-settings-edit"); var editButton = view.getDOMNode().querySelector(".settings-menu > li.entry-settings-edit");
React.addons.TestUtils.Simulate.click(editButton); React.addons.TestUtils.Simulate.click(editButton);
// Click again. expect(view.getDOMNode().querySelector(".settings-menu")).to.eql(null);
React.addons.TestUtils.Simulate.click(editButton);
expect(view.getDOMNode().querySelector(".room-context")).to.eql(null);
}); });
}); });
}); });
@ -840,12 +837,12 @@ describe("loop.roomViews", function () {
expect(view.getDOMNode()).to.eql(null); expect(view.getDOMNode()).to.eql(null);
}); });
it("should close the view when the close button is clicked", function() { it("should close the view when the cancel button is clicked", function() {
view = mountTestComponent({ view = mountTestComponent({
roomData: { roomContextUrls: [fakeContextURL] } roomData: { roomContextUrls: [fakeContextURL] }
}); });
var closeBtn = view.getDOMNode().querySelector(".room-context-btn-close"); var closeBtn = view.getDOMNode().querySelector(".button-cancel");
React.addons.TestUtils.Simulate.click(closeBtn); React.addons.TestUtils.Simulate.click(closeBtn);
expect(view.getDOMNode()).to.eql(null); expect(view.getDOMNode()).to.eql(null);
}); });
@ -866,35 +863,6 @@ describe("loop.roomViews", function () {
expect(node.querySelector(".room-context-url").value).to.eql(fakeContextURL.location); expect(node.querySelector(".room-context-url").value).to.eql(fakeContextURL.location);
expect(node.querySelector(".room-context-comments").value).to.eql(fakeContextURL.description); expect(node.querySelector(".room-context-comments").value).to.eql(fakeContextURL.description);
}); });
it("should show the checkbox as disabled when context is already set", function() {
view = mountTestComponent({
roomData: {
roomToken: "fakeToken",
roomName: "fakeName",
roomContextUrls: [fakeContextURL]
}
});
var checkbox = view.getDOMNode().querySelector(".checkbox");
expect(checkbox.classList.contains("disabled")).to.eql(true);
});
it("should hide the checkbox when no context data is stored or available", function() {
view = mountTestComponent({
roomData: {
roomToken: "fakeToken",
roomName: "Hello, is it me you're looking for?"
}
});
// 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);
});
}); });
describe("Update Room", function() { describe("Update Room", function() {
@ -919,7 +887,7 @@ describe("loop.roomViews", function () {
value: "reallyFake" value: "reallyFake"
}}); }});
React.addons.TestUtils.Simulate.click(view.getDOMNode().querySelector(".btn-info")); React.addons.TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-accept"));
sinon.assert.calledOnce(dispatcher.dispatch); sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch, sinon.assert.calledWithExactly(dispatcher.dispatch,
@ -955,7 +923,7 @@ describe("loop.roomViews", function () {
view.setProps({ savingContext: true }, function() { view.setProps({ savingContext: true }, function() {
var node = view.getDOMNode(); var node = view.getDOMNode();
// The button should show up as disabled. // The button should show up as disabled.
expect(node.querySelector(".btn-info").hasAttribute("disabled")).to.eql(true); expect(node.querySelector(".button-accept").hasAttribute("disabled")).to.eql(true);
// Now simulate a successful save. // Now simulate a successful save.
view.setProps({ savingContext: false }, function() { view.setProps({ savingContext: false }, function() {
@ -967,45 +935,6 @@ describe("loop.roomViews", function () {
}); });
}); });
describe("#handleCheckboxChange", function() {
var node, checkbox;
beforeEach(function() {
fakeMozLoop.getSelectedTabMetadata = sinon.stub().callsArgWith(0, {
favicon: fakeContextURL.thumbnail,
title: fakeContextURL.description,
url: fakeContextURL.location
});
view = mountTestComponent({
roomData: {
roomToken: "fakeToken",
roomName: "fakeName"
}
});
node = view.getDOMNode();
checkbox = node.querySelector(".checkbox");
});
it("should prefill the form with available context data when clicked", function() {
React.addons.TestUtils.Simulate.click(checkbox);
expect(node.querySelector(".room-context-name").value).to.eql("fakeName");
expect(node.querySelector(".room-context-url").value).to.eql(fakeContextURL.location);
expect(node.querySelector(".room-context-comments").value).to.eql(fakeContextURL.description);
});
it("should undo prefill when clicking the checkbox again", function() {
React.addons.TestUtils.Simulate.click(checkbox);
// Twice.
React.addons.TestUtils.Simulate.click(checkbox);
expect(node.querySelector(".room-context-name").value).to.eql("fakeName");
expect(node.querySelector(".room-context-url").value).to.eql("");
expect(node.querySelector(".room-context-comments").value).to.eql("");
});
});
describe("#handleContextClick", function() { describe("#handleContextClick", function() {
var fakeEvent; var fakeEvent;

View File

@ -32,5 +32,8 @@
"MozLoopServiceInternal": true, "MozLoopServiceInternal": true,
"LoopRoomsInternal": true, "LoopRoomsInternal": true,
"LoopUI": false, "LoopUI": false,
// Other items
"Chat": true,
"WebChannel": true
} }
} }

View File

@ -7,6 +7,7 @@ support-files =
google_service.sjs google_service.sjs
head.js head.js
loop_fxa.sjs loop_fxa.sjs
test_loopLinkClicker_channel.html
../../../../base/content/test/general/browser_fxa_oauth_with_keys.html ../../../../base/content/test/general/browser_fxa_oauth_with_keys.html
[browser_CardDavImporter.js] [browser_CardDavImporter.js]
@ -15,6 +16,8 @@ support-files =
skip-if = e10s skip-if = e10s
[browser_loop_fxa_server.js] [browser_loop_fxa_server.js]
[browser_LoopContacts.js] [browser_LoopContacts.js]
[browser_LoopRooms_channel.js]
skip-if = asan && e10s # Bug 1206457
[browser_mozLoop_appVersionInfo.js] [browser_mozLoop_appVersionInfo.js]
[browser_mozLoop_context.js] [browser_mozLoop_context.js]
[browser_mozLoop_prefs.js] [browser_mozLoop_prefs.js]

View File

@ -0,0 +1,108 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* This file contains tests for checking the channel from the standalone to
* LoopRooms works for checking if rooms can be opened within the conversation
* window.
*/
"use strict";
var {WebChannel} = Cu.import("resource://gre/modules/WebChannel.jsm", {});
var {Chat} = Cu.import("resource:///modules/Chat.jsm", {});
const TEST_URI =
"example.com/browser/browser/components/loop/test/mochitest/test_loopLinkClicker_channel.html";
const TEST_URI_GOOD = Services.io.newURI("https://" + TEST_URI, null, null);
const TEST_URI_BAD = Services.io.newURI("http://" + TEST_URI, null, null);
const ROOM_TOKEN = "fake1234";
const LINKCLICKER_URL_PREFNAME = "loop.linkClicker.url";
var openChatOrig = Chat.open;
var fakeRoomList = new Map([[ ROOM_TOKEN, { roomToken: ROOM_TOKEN } ]]);
// Loads the specified URI in a new tab and waits for it to send us data on our
// test web-channel and resolves with that data.
function promiseNewChannelResponse(uri, hash) {
let waitForChannelPromise = new Promise((resolve, reject) => {
let channel = new WebChannel("test-loop-link-clicker-backchannel", uri);
channel.listen((id, data, target) => {
channel.stopListening();
resolve(data);
});
});
return BrowserTestUtils.withNewTab({
gBrowser: gBrowser,
url: uri.spec + "#" + hash
}, () => waitForChannelPromise);
}
add_task(function* test_loopRooms_webChannel_permissions() {
// We haven't set the allowed web page yet - so even the "good" URI should fail.
let got = yield promiseNewChannelResponse(TEST_URI_GOOD, "checkWillOpenRoom");
// Should have no data.
Assert.ok(got.message === undefined, "should have failed to get any data");
// Add a permission manager entry for our URI.
Services.prefs.setCharPref(LINKCLICKER_URL_PREFNAME, TEST_URI_GOOD.spec);
registerCleanupFunction(() => {
Services.prefs.clearUserPref(LINKCLICKER_URL_PREFNAME);
});
// Try again - now we are expecting a response with actual data.
got = yield promiseNewChannelResponse(TEST_URI_GOOD, "checkWillOpenRoom");
// The room doesn't exist, so we should get a negative response.
Assert.equal(got.message.response, false, "should have got a response of false");
// Now a http:// URI - should get nothing even with the permission setup.
got = yield promiseNewChannelResponse(TEST_URI_BAD, "checkWillOpenRoom");
Assert.ok(got.message === undefined, "should have failed to get any data");
});
add_task(function* test_loopRooms_webchannel_checkWillOpenRoom() {
// We've already tested if the room doesn't exist above, so here we add the
// room and check the result.
LoopRooms._setRoomsCache(fakeRoomList);
let got = yield promiseNewChannelResponse(TEST_URI_GOOD, "checkWillOpenRoom");
Assert.equal(got.message.response, true, "should have got a response of true");
});
add_task(function* test_loopRooms_webchannel_openRoom() {
let openedUrl;
Chat.open = function(contentWindow, origin, title, url) {
openedUrl = url;
};
registerCleanupFunction(() => {
Chat.open = openChatOrig;
});
// Test when the room doesn't exist
LoopRooms._setRoomsCache();
let got = yield promiseNewChannelResponse(TEST_URI_GOOD, "openRoom");
Assert.ok(!openedUrl, "should not open a chat window");
Assert.equal(got.message.response, false, "should have got a response of false");
// Now add a room & check it.
LoopRooms._setRoomsCache(fakeRoomList);
got = yield promiseNewChannelResponse(TEST_URI_GOOD, "openRoom");
// Check the room was opened.
Assert.ok(openedUrl, "should open a chat window");
let windowId = openedUrl.match(/about:loopconversation\#(\w+)$/)[1];
let windowData = MozLoopService.getConversationWindowData(windowId);
Assert.equal(windowData.type, "room", "window data should contain room as the type");
Assert.equal(windowData.roomToken, ROOM_TOKEN, "window data should have the roomToken");
Assert.equal(got.message.response, true, "should have got a response of true");
});

View File

@ -0,0 +1,46 @@
<!DOCTYPE HTML>
<html>
<script>
"use strict";
// Add a listener for responses to our remote requests.
window.addEventListener("WebChannelMessageToContent", function (event) {
if (event.detail.id == "loop-link-clicker") {
// Send what we got back to the test.
var backEvent = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "test-loop-link-clicker-backchannel",
message: {
message: event.detail.message
}
}
});
window.dispatchEvent(backEvent);
// and stick it in our DOM just for good measure/diagnostics.
document.getElementById("troubleshooting").textContent =
JSON.stringify(event.detail.message, null, 2);
}
});
// Send a message on load requesting that the room is opened.
window.onload = function() {
var hash = window.location.hash;
var event = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "loop-link-clicker",
message: {
command: hash.substring(1, hash.length),
roomToken: "fake1234"
}
}
});
window.dispatchEvent(event);
};
</script>
<body>
<pre id="troubleshooting"/>
</body>
</html>

View File

@ -429,7 +429,8 @@ describe("loop.shared.views", function() {
function mountTestComponent(props) { function mountTestComponent(props) {
props = _.extend({ props = _.extend({
dispatcher: dispatcher, dispatcher: dispatcher,
mozLoop: {} mozLoop: {},
show: true
}, props || {}); }, props || {});
return TestUtils.renderIntoDocument( return TestUtils.renderIntoDocument(
React.createElement(sharedViews.ConversationToolbar, props)); React.createElement(sharedViews.ConversationToolbar, props));
@ -445,6 +446,16 @@ describe("loop.shared.views", function() {
clock.restore(); clock.restore();
}); });
it("should not render the component when 'show' is false", function() {
var comp = mountTestComponent({
hangup: hangup,
publishStream: publishStream,
show: false
});
expect(comp.getDOMNode()).to.eql(null);
});
it("should start no idle", function() { it("should start no idle", function() {
var comp = mountTestComponent({ var comp = mountTestComponent({
hangupButtonLabel: "foo", hangupButtonLabel: "foo",

View File

@ -1118,6 +1118,7 @@
publishStream: noop, publishStream: noop,
screenShare: { state: SCREEN_SHARE_STATES.INACTIVE, visible: true}, screenShare: { state: SCREEN_SHARE_STATES.INACTIVE, visible: true},
settingsMenuItems: [{ id: "feedback" }], settingsMenuItems: [{ id: "feedback" }],
show: true,
video: { enabled: true, visible: true}}) video: { enabled: true, visible: true}})
) )
), ),
@ -1132,6 +1133,7 @@
publishStream: noop, publishStream: noop,
screenShare: { state: SCREEN_SHARE_STATES.PENDING, visible: true}, screenShare: { state: SCREEN_SHARE_STATES.PENDING, visible: true},
settingsMenuItems: [{ id: "feedback" }], settingsMenuItems: [{ id: "feedback" }],
show: true,
video: { enabled: false, visible: true}}) video: { enabled: false, visible: true}})
) )
), ),
@ -1146,6 +1148,7 @@
publishStream: noop, publishStream: noop,
screenShare: { state: SCREEN_SHARE_STATES.ACTIVE, visible: true}, screenShare: { state: SCREEN_SHARE_STATES.ACTIVE, visible: true},
settingsMenuItems: [{ id: "feedback" }], settingsMenuItems: [{ id: "feedback" }],
show: true,
video: { enabled: true, visible: true}}) video: { enabled: true, visible: true}})
) )
) )

View File

@ -1118,6 +1118,7 @@
publishStream={noop} publishStream={noop}
screenShare={{ state: SCREEN_SHARE_STATES.INACTIVE, visible: true }} screenShare={{ state: SCREEN_SHARE_STATES.INACTIVE, visible: true }}
settingsMenuItems={[{ id: "feedback" }]} settingsMenuItems={[{ id: "feedback" }]}
show={true}
video={{ enabled: true, visible: true }} /> video={{ enabled: true, visible: true }} />
</div> </div>
</FramedExample> </FramedExample>
@ -1132,6 +1133,7 @@
publishStream={noop} publishStream={noop}
screenShare={{ state: SCREEN_SHARE_STATES.PENDING, visible: true }} screenShare={{ state: SCREEN_SHARE_STATES.PENDING, visible: true }}
settingsMenuItems={[{ id: "feedback" }]} settingsMenuItems={[{ id: "feedback" }]}
show={true}
video={{ enabled: false, visible: true }} /> video={{ enabled: false, visible: true }} />
</div> </div>
</FramedExample> </FramedExample>
@ -1146,6 +1148,7 @@
publishStream={noop} publishStream={noop}
screenShare={{ state: SCREEN_SHARE_STATES.ACTIVE, visible: true }} screenShare={{ state: SCREEN_SHARE_STATES.ACTIVE, visible: true }}
settingsMenuItems={[{ id: "feedback" }]} settingsMenuItems={[{ id: "feedback" }]}
show={true}
video={{ enabled: true, visible: true }} /> video={{ enabled: true, visible: true }} />
</div> </div>
</FramedExample> </FramedExample>

View File

@ -70,11 +70,11 @@
<!-- Passwords --> <!-- Passwords -->
<groupbox id="passwordsGroup" orient="vertical" data-category="paneSecurity" hidden="true"> <groupbox id="passwordsGroup" orient="vertical" data-category="paneSecurity" hidden="true">
<caption><label>&passwords.label;</label></caption> <caption><label>&logins.label;</label></caption>
<hbox id="savePasswordsBox"> <hbox id="savePasswordsBox">
<checkbox id="savePasswords" <checkbox id="savePasswords"
label="&rememberPasswords.label;" accesskey="&rememberPasswords.accesskey;" label="&rememberLogins.label;" accesskey="&rememberLogins.accesskey;"
preference="signon.rememberSignons" preference="signon.rememberSignons"
onsyncfrompreference="return gSecurityPane.readSavePasswords();"/> onsyncfrompreference="return gSecurityPane.readSavePasswords();"/>
<spacer flex="1"/> <spacer flex="1"/>
@ -103,7 +103,7 @@
<row id="showPasswordRow"> <row id="showPasswordRow">
<hbox id="showPasswordsBox"/> <hbox id="showPasswordsBox"/>
<button id="showPasswords" <button id="showPasswords"
label="&savedPasswords.label;" accesskey="&savedPasswords.accesskey;" label="&savedLogins.label;" accesskey="&savedLogins.accesskey;"
preference="pref.privacy.disable_button.view_passwords"/> preference="pref.privacy.disable_button.view_passwords"/>
</row> </row>
</rows> </rows>

View File

@ -22,7 +22,3 @@ BROWSER_CHROME_MANIFESTS += [
XPCSHELL_TESTS_MANIFESTS += [ XPCSHELL_TESTS_MANIFESTS += [
'test/unit/xpcshell.ini' 'test/unit/xpcshell.ini'
] ]
EXTRA_PP_COMPONENTS += [
'translation.manifest',
]

View File

@ -1,3 +0,0 @@
#ifdef MOZ_SERVICES_HEALTHREPORT
category healthreport-js-provider-default TranslationProvider resource:///modules/translation/Translation.jsm
#endif

View File

@ -684,8 +684,11 @@ AnimationsTimeline.prototype = {
let getTime = time => L10N.getFormatStr("player.timeLabel", let getTime = time => L10N.getFormatStr("player.timeLabel",
L10N.numberWithDecimals(time / 1000, 2)); L10N.numberWithDecimals(time / 1000, 2));
let title = L10N.getFormatStr("timeline." + state.type + ".nameLabel", // The type isn't always available, older servers don't send it.
state.name); let title =
state.type
? L10N.getFormatStr("timeline." + state.type + ".nameLabel", state.name)
: state.name;
let delay = L10N.getStr("player.animationDelayLabel") + " " + let delay = L10N.getStr("player.animationDelayLabel") + " " +
getTime(state.delay); getTime(state.delay);
let duration = L10N.getStr("player.animationDurationLabel") + " " + let duration = L10N.getStr("player.animationDurationLabel") + " " +

View File

@ -43,7 +43,7 @@ label,
margin-top: 0; margin-top: 0;
} }
.panel-header[hidden] { .panel-header[hidden], .panel-item[hidden] {
display: none; display: none;
} }

View File

@ -386,7 +386,6 @@
@RESPATH@/browser/components/webideComponents.manifest @RESPATH@/browser/components/webideComponents.manifest
@RESPATH@/browser/components/Experiments.manifest @RESPATH@/browser/components/Experiments.manifest
@RESPATH@/browser/components/ExperimentsService.js @RESPATH@/browser/components/ExperimentsService.js
@RESPATH@/browser/components/translation.manifest
@RESPATH@/components/Downloads.manifest @RESPATH@/components/Downloads.manifest
@RESPATH@/components/DownloadLegacy.js @RESPATH@/components/DownloadLegacy.js
@RESPATH@/components/BrowserPageThumbs.manifest @RESPATH@/components/BrowserPageThumbs.manifest
@ -567,11 +566,6 @@
@RESPATH@/components/InterAppMessagePort.js @RESPATH@/components/InterAppMessagePort.js
#endif #endif
@RESPATH@/components/TCPSocket.js
@RESPATH@/components/TCPServerSocket.js
@RESPATH@/components/TCPSocketParentIntermediary.js
@RESPATH@/components/TCPSocket.manifest
#ifdef MOZ_ACTIVITIES #ifdef MOZ_ACTIVITIES
@RESPATH@/components/SystemMessageCache.js @RESPATH@/components/SystemMessageCache.js
@RESPATH@/components/SystemMessageInternal.js @RESPATH@/components/SystemMessageInternal.js

View File

@ -115,10 +115,6 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY fxaUnverified.label "Verify Your Account"> <!ENTITY fxaUnverified.label "Verify Your Account">
<!ENTITY syncStartPanel2.heading "&syncBrand.shortName.label; enabled"> <!ENTITY syncStartPanel2.heading "&syncBrand.shortName.label; enabled">
<!ENTITY syncStartPanel2.subTitle "&brandShortName; will begin syncing momentarily."> <!ENTITY syncStartPanel2.subTitle "&brandShortName; will begin syncing momentarily.">
<!ENTITY syncErrorPanel.heading "Cannot connect to &syncBrand.shortName.label;">
<!ENTITY syncErrorPanel.subTitle "Please sign in to resume syncing.">
<!ENTITY syncErrorPanel.signInButton.label "Sign In">
<!ENTITY syncErrorPanel.signInButton.accesskey "S">
<!ENTITY fullScreenMinimize.tooltip "Minimize"> <!ENTITY fullScreenMinimize.tooltip "Minimize">

View File

@ -31,6 +31,15 @@ first_time_experience_button_label=Get Started
first_time_experience_subheading=Join the conversation first_time_experience_subheading=Join the conversation
invite_header_text=Invite someone to join you. invite_header_text=Invite someone to join you.
invite_header_text2=Invite a friend to join you
invite_facebook_button=share on Facebook
invite_facebook_triggered=shared!
invite_contacts_button=share with contacts
invite_contacts_triggered=shared!
invite_copy_button=copy link
invite_copy_triggered=copied!
invite_email_button=email link
invite_email_triggered=emailed!
# Status text # Status text
display_name_guest=Guest display_name_guest=Guest
@ -343,12 +352,16 @@ infobar_menuitem_dontshowagain_accesskey=D
# Context in conversation strings # Context in conversation strings
# LOCALIZATION NOTE (context_inroom_label): this string is followed by the # LOCALIZATION NOTE (context_inroom_header): this string is displayed in the
# title/URL of the website you are having a conversation about, displayed on a # conversation window when the user edits context. It is a header to the edit
# section.
context_inroom_header=Let's Talk About…
# LOCALIZATION NOTE (context_inroom_label2): this string is followed by the
# title and domain of the website you are having a conversation about, displayed on a
# separate line. If this structure doesn't work for your locale, you might want # separate line. If this structure doesn't work for your locale, you might want
# to consider this as a stand-alone title. See example screenshot: # to consider this as a stand-alone title. See example screenshot:
# https://bug1115342.bugzilla.mozilla.org/attachment.cgi?id=8563677 # https://bug1115342.bugzilla.mozilla.org/attachment.cgi?id=8563677
context_inroom_label=Let's talk about: context_inroom_label2=Let's Talk About:
## LOCALIZATION_NOTE (context_edit_activate_label): {{title}} will be replaced ## LOCALIZATION_NOTE (context_edit_activate_label): {{title}} will be replaced
## by the title of the active tab, also known as the title of an HTML document. ## by the title of the active tab, also known as the title of an HTML document.
## The quotes around the title are intentional. ## The quotes around the title are intentional.
@ -357,7 +370,8 @@ context_edit_name_placeholder=Conversation Name
context_edit_comments_placeholder=Comments context_edit_comments_placeholder=Comments
context_add_some_label=Add some context context_add_some_label=Add some context
context_show_tooltip=Show Context context_show_tooltip=Show Context
context_save_label2=Save context_cancel_label=Cancel
context_done_label=Done
context_link_modified=This link was modified. context_link_modified=This link was modified.
context_learn_more_link_label=Learn more. context_learn_more_link_label=Learn more.
conversation_settings_menu_edit_context=Edit Context conversation_settings_menu_edit_context=Edit Context

View File

@ -24,10 +24,10 @@
<!ENTITY addonExceptions.accesskey "E"> <!ENTITY addonExceptions.accesskey "E">
<!ENTITY passwords.label "Passwords"> <!ENTITY logins.label "Logins">
<!ENTITY rememberPasswords.label "Remember passwords for sites"> <!ENTITY rememberLogins.label "Remember logins for sites">
<!ENTITY rememberPasswords.accesskey "R"> <!ENTITY rememberLogins.accesskey "R">
<!ENTITY passwordExceptions.label "Exceptions…"> <!ENTITY passwordExceptions.label "Exceptions…">
<!ENTITY passwordExceptions.accesskey "x"> <!ENTITY passwordExceptions.accesskey "x">
@ -36,5 +36,5 @@
<!ENTITY changeMasterPassword.label "Change Master Password…"> <!ENTITY changeMasterPassword.label "Change Master Password…">
<!ENTITY changeMasterPassword.accesskey "M"> <!ENTITY changeMasterPassword.accesskey "M">
<!ENTITY savedPasswords.label "Saved Passwords…"> <!ENTITY savedLogins.label "Saved Logins…">
<!ENTITY savedPasswords.accesskey "P"> <!ENTITY savedLogins.accesskey "L">

View File

@ -1725,15 +1725,13 @@ toolbarbutton.chevron > .toolbarbutton-icon {
margin-top: 1em; margin-top: 1em;
} }
#sync-error-panel-title,
#sync-start-panel-title { #sync-start-panel-title {
font-size: 120%; font-size: 120%;
font-weight: bold; font-weight: bold;
margin-bottom: 5px; margin-bottom: 5px;
} }
#sync-start-panel-subtitle, #sync-start-panel-subtitle {
#sync-error-panel-subtitle {
margin: 0; margin: 0;
} }

View File

@ -3275,15 +3275,13 @@ notification[value="loop-sharing-notification"] .messageImage {
@hudButtonFocused@ @hudButtonFocused@
} }
#sync-error-panel-title,
#sync-start-panel-title { #sync-start-panel-title {
font-size: 120%; font-size: 120%;
font-weight: bold; font-weight: bold;
margin-bottom: 5px; margin-bottom: 5px;
} }
#sync-start-panel-subtitle, #sync-start-panel-subtitle {
#sync-error-panel-subtitle {
margin: 0; margin: 0;
} }

View File

@ -1,6 +1,5 @@
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] { #PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
border-bottom: 1px solid hsla(210, 4%, 10%, 0.14); border-bottom: 1px solid hsla(210, 4%, 10%, 0.14);
color: -moz-FieldText;
background-color: hsla(210, 4%, 10%, 0.07); background-color: hsla(210, 4%, 10%, 0.07);
padding: 6px 0; padding: 6px 0;
-moz-padding-start: 44px; -moz-padding-start: 44px;

View File

@ -2473,15 +2473,13 @@ notification[value="loop-sharing-notification"] .messageImage {
margin-top: 1em; margin-top: 1em;
} }
#sync-error-panel-title,
#sync-start-panel-title { #sync-start-panel-title {
font-size: 120%; font-size: 120%;
font-weight: bold; font-weight: bold;
margin-bottom: 5px; margin-bottom: 5px;
} }
#sync-start-panel-subtitle, #sync-start-panel-subtitle {
#sync-error-panel-subtitle {
margin: 0; margin: 0;
} }

View File

@ -173,6 +173,9 @@ nsNullPrincipal::Read(nsIObjectInputStream* aStream)
NS_IMETHODIMP NS_IMETHODIMP
nsNullPrincipal::Write(nsIObjectOutputStream* aStream) nsNullPrincipal::Write(nsIObjectOutputStream* aStream)
{ {
NS_ENSURE_TRUE(mOriginAttributes.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
NS_ERROR_INVALID_ARG);
nsAutoCString suffix; nsAutoCString suffix;
OriginAttributesRef().CreateSuffix(suffix); OriginAttributesRef().CreateSuffix(suffix);

View File

@ -427,6 +427,8 @@ NS_IMETHODIMP
nsPrincipal::Write(nsIObjectOutputStream* aStream) nsPrincipal::Write(nsIObjectOutputStream* aStream)
{ {
NS_ENSURE_STATE(mCodebase); NS_ENSURE_STATE(mCodebase);
NS_ENSURE_TRUE(mOriginAttributes.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
NS_ERROR_INVALID_ARG);
nsresult rv = NS_WriteOptionalCompoundObject(aStream, mCodebase, NS_GET_IID(nsIURI), nsresult rv = NS_WriteOptionalCompoundObject(aStream, mCodebase, NS_GET_IID(nsIURI),
true); true);

View File

@ -103,6 +103,20 @@ function run_test() {
var simplePrin = ssm.getSimpleCodebasePrincipal(makeURI('http://example.com')); var simplePrin = ssm.getSimpleCodebasePrincipal(makeURI('http://example.com'));
try { simplePrin.origin; do_check_true(false); } catch (e) { do_check_true(true); } try { simplePrin.origin; do_check_true(false); } catch (e) { do_check_true(true); }
// Make sure we don't crash when serializing them either.
try {
let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
createInstance(Ci.nsIObjectOutputStream);
let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
pipe.init(false, false, 0, 0xffffffff, null);
binaryStream.setOutputStream(pipe.outputStream);
binaryStream.writeCompoundObject(simplePrin, Ci.nsISupports, true);
binaryStream.close();
} catch (e) {
do_check_true(true);
}
// Just userContext. // Just userContext.
var exampleOrg_userContext = ssm.createCodebasePrincipal(makeURI('http://example.org'), {userContextId: 42}); var exampleOrg_userContext = ssm.createCodebasePrincipal(makeURI('http://example.org'), {userContextId: 42});
checkOriginAttributes(exampleOrg_userContext, { userContextId: 42 }, '^userContextId=42'); checkOriginAttributes(exampleOrg_userContext, { userContextId: 42 }, '^userContextId=42');

View File

@ -168,7 +168,9 @@ const mozilla::Module::ContractIDEntry kDocShellContracts[] = {
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "neterror", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "neterror", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "networking", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "networking", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "newaddon", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newaddon", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
#ifdef NIGHTLY_BUILD
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "performance", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "performance", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
#endif
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "plugins", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "plugins", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "serviceworkers", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "serviceworkers", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "srcdoc", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "srcdoc", &kNS_ABOUT_REDIRECTOR_MODULE_CID },

View File

@ -43,6 +43,7 @@
#include "mozilla/dom/Permissions.h" #include "mozilla/dom/Permissions.h"
#include "mozilla/dom/Presentation.h" #include "mozilla/dom/Presentation.h"
#include "mozilla/dom/ServiceWorkerContainer.h" #include "mozilla/dom/ServiceWorkerContainer.h"
#include "mozilla/dom/TCPSocket.h"
#include "mozilla/dom/Telephony.h" #include "mozilla/dom/Telephony.h"
#include "mozilla/dom/Voicemail.h" #include "mozilla/dom/Voicemail.h"
#include "mozilla/dom/TVManager.h" #include "mozilla/dom/TVManager.h"
@ -1805,6 +1806,13 @@ Navigator::GetInputPortManager(ErrorResult& aRv)
return mInputPortManager; return mInputPortManager;
} }
already_AddRefed<LegacyMozTCPSocket>
Navigator::MozTCPSocket()
{
nsRefPtr<LegacyMozTCPSocket> socket = new LegacyMozTCPSocket(GetWindow());
return socket.forget();
}
#ifdef MOZ_B2G #ifdef MOZ_B2G
already_AddRefed<Promise> already_AddRefed<Promise>
Navigator::GetMobileIdAssertion(const MobileIdOptions& aOptions, Navigator::GetMobileIdAssertion(const MobileIdOptions& aOptions,

View File

@ -99,6 +99,7 @@ class TVManager;
class InputPortManager; class InputPortManager;
class DeviceStorageAreaListener; class DeviceStorageAreaListener;
class Presentation; class Presentation;
class LegacyMozTCPSocket;
namespace time { namespace time {
class TimeManager; class TimeManager;
@ -242,6 +243,7 @@ public:
Voicemail* GetMozVoicemail(ErrorResult& aRv); Voicemail* GetMozVoicemail(ErrorResult& aRv);
TVManager* GetTv(); TVManager* GetTv();
InputPortManager* GetInputPortManager(ErrorResult& aRv); InputPortManager* GetInputPortManager(ErrorResult& aRv);
already_AddRefed<LegacyMozTCPSocket> MozTCPSocket();
network::Connection* GetConnection(ErrorResult& aRv); network::Connection* GetConnection(ErrorResult& aRv);
nsDOMCameraManager* GetMozCameras(ErrorResult& aRv); nsDOMCameraManager* GetMozCameras(ErrorResult& aRv);
MediaDevices* GetMediaDevices(ErrorResult& aRv); MediaDevices* GetMediaDevices(ErrorResult& aRv);

View File

@ -753,6 +753,7 @@ GK_ATOM(onDOMNodeInsertedIntoDocument, "onDOMNodeInsertedIntoDocument")
GK_ATOM(onDOMNodeRemoved, "onDOMNodeRemoved") GK_ATOM(onDOMNodeRemoved, "onDOMNodeRemoved")
GK_ATOM(onDOMNodeRemovedFromDocument, "onDOMNodeRemovedFromDocument") GK_ATOM(onDOMNodeRemovedFromDocument, "onDOMNodeRemovedFromDocument")
GK_ATOM(onDOMSubtreeModified, "onDOMSubtreeModified") GK_ATOM(onDOMSubtreeModified, "onDOMSubtreeModified")
GK_ATOM(ondata, "ondata")
GK_ATOM(ondrag, "ondrag") GK_ATOM(ondrag, "ondrag")
GK_ATOM(ondragdrop, "ondragdrop") GK_ATOM(ondragdrop, "ondragdrop")
GK_ATOM(ondragend, "ondragend") GK_ATOM(ondragend, "ondragend")
@ -762,6 +763,7 @@ GK_ATOM(ondraggesture, "ondraggesture")
GK_ATOM(ondragleave, "ondragleave") GK_ATOM(ondragleave, "ondragleave")
GK_ATOM(ondragover, "ondragover") GK_ATOM(ondragover, "ondragover")
GK_ATOM(ondragstart, "ondragstart") GK_ATOM(ondragstart, "ondragstart")
GK_ATOM(ondrain, "ondrain")
GK_ATOM(ondrop, "ondrop") GK_ATOM(ondrop, "ondrop")
GK_ATOM(oneitbroadcasted, "oneitbroadcasted") GK_ATOM(oneitbroadcasted, "oneitbroadcasted")
GK_ATOM(onenabled, "onenabled") GK_ATOM(onenabled, "onenabled")

View File

@ -696,6 +696,11 @@ DOMInterfaces = {
'concrete': False 'concrete': False
}, },
'LegacyMozTCPSocket': {
'headerFile': 'TCPSocket.h',
'wrapperCache': False,
},
'LocalMediaStream': { 'LocalMediaStream': {
'headerFile': 'DOMMediaStream.h', 'headerFile': 'DOMMediaStream.h',
'nativeType': 'mozilla::DOMLocalMediaStream' 'nativeType': 'mozilla::DOMLocalMediaStream'
@ -1308,6 +1313,10 @@ DOMInterfaces = {
'wrapperCache': False 'wrapperCache': False
}, },
'TCPSocket': {
'implicitJSContext': ['send']
},
'ThreadSafeChromeUtils': { 'ThreadSafeChromeUtils': {
# The codegen is dumb, and doesn't understand that this interface is only a # The codegen is dumb, and doesn't understand that this interface is only a
# collection of static methods, so we have this `concrete: False` hack. # collection of static methods, so we have this `concrete: False` hack.

View File

@ -205,11 +205,7 @@ function beginTest() {
} }
} }
var prefs = [ beginTest();
[ "canvas.capturestream.enabled", true ],
];
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
</script> </script>
</pre> </pre>
</body> </body>

View File

@ -211,13 +211,8 @@ function beginTest() {
document.manager.runTests(corsTests, startTest); document.manager.runTests(corsTests, startTest);
} }
var prefs = [
[ "canvas.capturestream.enabled", true ],
];
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest); beginTest();
</script> </script>
</pre> </pre>
</body> </body>

View File

@ -20,7 +20,7 @@ pref(webgl.force-layers-readback,true) == webgl-clear-test.html?readback wrappe
== webgl-resize-test.html wrapper.html?green.png == webgl-resize-test.html wrapper.html?green.png
# Check that captureStream() displays in a local video element # Check that captureStream() displays in a local video element
pref(canvas.capturestream.enabled,true) skip-if(winWidget&&layersGPUAccelerated&&d2d) == webgl-capturestream-test.html?preserve wrapper.html?green.png skip-if(winWidget&&layersGPUAccelerated&&d2d) == webgl-capturestream-test.html?preserve wrapper.html?green.png
# Some of the failure conditions are a little crazy. I'm (jgilbert) setting these based on # Some of the failure conditions are a little crazy. I'm (jgilbert) setting these based on
# failures encountered when running on Try, and then targetting the Try config by # failures encountered when running on Try, and then targetting the Try config by
@ -157,6 +157,6 @@ pref(canvas.focusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion
pref(canvas.customfocusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html pref(canvas.customfocusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html
# Check that captureStream() displays in a local video element # Check that captureStream() displays in a local video element
pref(canvas.capturestream.enabled,true) skip-if(winWidget&&layersGPUAccelerated&&d2d) == capturestream.html wrapper.html?green.png skip-if(winWidget&&layersGPUAccelerated&&d2d) == capturestream.html wrapper.html?green.png
fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),1,1) == 1177726-text-stroke-bounds.html 1177726-text-stroke-bounds-ref.html fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),1,1) == 1177726-text-stroke-bounds.html 1177726-text-stroke-bounds-ref.html

View File

@ -107,9 +107,6 @@ function beginTest() {
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
var prefs = [ beginTest();
[ "canvas.capturestream.enabled", true ],
];
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
</script> </script>

View File

@ -171,9 +171,6 @@ function beginTest() {
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
var prefs = [ beginTest();
[ "canvas.capturestream.enabled", true ],
];
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
</script> </script>

View File

@ -249,6 +249,7 @@ public:
bool CanBeFormatted(); bool CanBeFormatted();
bool CanBeShared(); bool CanBeShared();
bool IsRemovable(); bool IsRemovable();
bool LowDiskSpace();
bool Default(); bool Default();
void GetStorageName(nsAString& aStorageName); void GetStorageName(nsAString& aStorageName);

View File

@ -72,6 +72,7 @@ DeviceStorageStatics::InitializeDirs()
DeviceStorageStatics::DeviceStorageStatics() DeviceStorageStatics::DeviceStorageStatics()
: mInitialized(false) : mInitialized(false)
, mPromptTesting(false) , mPromptTesting(false)
, mLowDiskSpace(false)
{ {
DS_LOG_INFO(""); DS_LOG_INFO("");
} }
@ -358,6 +359,16 @@ DeviceStorageStatics::IsPromptTesting()
return sInstance->mPromptTesting; return sInstance->mPromptTesting;
} }
/* static */ bool
DeviceStorageStatics::LowDiskSpace()
{
StaticMutexAutoLock lock(sMutex);
if (NS_WARN_IF(!sInstance)) {
return false;
}
return sInstance->mLowDiskSpace;
}
/* static */ void /* static */ void
DeviceStorageStatics::GetWritableName(nsString& aName) DeviceStorageStatics::GetWritableName(nsString& aName)
{ {
@ -605,27 +616,29 @@ DeviceStorageStatics::Observe(nsISupports* aSubject,
} }
if (!strcmp(aTopic, kDiskSpaceWatcher)) { if (!strcmp(aTopic, kDiskSpaceWatcher)) {
// 'disk-space-watcher' notifications are sent when there is a modification
// of a file in a specific location while a low device storage situation
// exists or after recovery of a low storage situation. For Firefox OS,
// these notifications are specific for apps storage.
bool lowDiskSpace = false;
if (!NS_strcmp(aData, MOZ_UTF16("full"))) {
lowDiskSpace = true;
} else if (NS_strcmp(aData, MOZ_UTF16("free"))) {
return NS_OK;
}
StaticMutexAutoLock lock(sMutex); StaticMutexAutoLock lock(sMutex);
if (NS_WARN_IF(!sInstance)) { if (NS_WARN_IF(!sInstance)) {
return NS_OK; return NS_OK;
} }
// 'disk-space-watcher' notifications are sent when there is a modification
// of a file in a specific location while a low device storage situation
// exists or after recovery of a low storage situation. For Firefox OS,
// these notifications are specific for apps storage.
if (!NS_strcmp(aData, MOZ_UTF16("full"))) {
sInstance->mLowDiskSpace = true;
} else if (!NS_strcmp(aData, MOZ_UTF16("free"))) {
sInstance->mLowDiskSpace = false;
} else {
return NS_OK;
}
uint32_t i = mListeners.Length(); uint32_t i = mListeners.Length();
DS_LOG_INFO("disk space %d (%u)", lowDiskSpace, i); DS_LOG_INFO("disk space %d (%u)", sInstance->mLowDiskSpace, i);
while (i > 0) { while (i > 0) {
--i; --i;
mListeners[i]->OnDiskSpaceWatcher(lowDiskSpace); mListeners[i]->OnDiskSpaceWatcher(sInstance->mLowDiskSpace);
} }
return NS_OK; return NS_OK;
} }

View File

@ -30,6 +30,7 @@ public:
static void AddListener(nsDOMDeviceStorage* aListener); static void AddListener(nsDOMDeviceStorage* aListener);
static void RemoveListener(nsDOMDeviceStorage* aListener); static void RemoveListener(nsDOMDeviceStorage* aListener);
static bool LowDiskSpace();
static bool IsPromptTesting(); static bool IsPromptTesting();
static void GetWritableName(nsString& aName); static void GetWritableName(nsString& aName);
static void SetWritableName(const nsAString& aName); static void SetWritableName(const nsAString& aName);
@ -92,6 +93,7 @@ private:
bool mInitialized; bool mInitialized;
bool mPromptTesting; bool mPromptTesting;
bool mLowDiskSpace;
nsString mWritableName; nsString mWritableName;
static StaticRefPtr<DeviceStorageStatics> sInstance; static StaticRefPtr<DeviceStorageStatics> sInstance;

View File

@ -3374,6 +3374,12 @@ nsDOMDeviceStorage::IsRemovable()
return mIsRemovable; return mIsRemovable;
} }
bool
nsDOMDeviceStorage::LowDiskSpace()
{
return DeviceStorageStatics::LowDiskSpace();
}
already_AddRefed<Promise> already_AddRefed<Promise>
nsDOMDeviceStorage::GetRoot(ErrorResult& aRv) nsDOMDeviceStorage::GetRoot(ErrorResult& aRv)
{ {

View File

@ -484,6 +484,18 @@ const kEventConstructors = {
return e; return e;
}, },
}, },
TCPSocketErrorEvent: { create: function(aName, aProps) {
return new TCPSocketErrorEvent(aName, aProps);
},
},
TCPSocketEvent: { create: function(aName, aProps) {
return new TCPSocketEvent(aName, aProps);
},
},
TCPServerSocketEvent: { create: function(aName, aProps) {
return new TCPServerSocketEvent(aName, aProps);
},
},
TimeEvent: { create: function (aName, aProps) { TimeEvent: { create: function (aName, aProps) {
var e = document.createEvent("timeevent"); var e = document.createEvent("timeevent");
e.initTimeEvent(aName, aProps.view, aProps.detail); e.initTimeEvent(aName, aProps.view, aProps.detail);

View File

@ -42,6 +42,7 @@
#include "WorkerRunnable.h" #include "WorkerRunnable.h"
#include "WorkerScope.h" #include "WorkerScope.h"
#include "Workers.h" #include "Workers.h"
#include "FetchUtil.h"
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -909,53 +910,6 @@ ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUS
} }
namespace { namespace {
class StreamDecoder final
{
nsCOMPtr<nsIUnicodeDecoder> mDecoder;
nsString mDecoded;
public:
StreamDecoder()
: mDecoder(EncodingUtils::DecoderForEncoding("UTF-8"))
{
MOZ_ASSERT(mDecoder);
}
nsresult
AppendText(const char* aSrcBuffer, uint32_t aSrcBufferLen)
{
int32_t destBufferLen;
nsresult rv =
mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, &destBufferLen);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!mDecoded.SetCapacity(mDecoded.Length() + destBufferLen, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
char16_t* destBuffer = mDecoded.BeginWriting() + mDecoded.Length();
int32_t totalChars = mDecoded.Length();
int32_t srcLen = (int32_t) aSrcBufferLen;
int32_t outLen = destBufferLen;
rv = mDecoder->Convert(aSrcBuffer, &srcLen, destBuffer, &outLen);
MOZ_ASSERT(NS_SUCCEEDED(rv));
totalChars += outLen;
mDecoded.SetLength(totalChars);
return NS_OK;
}
nsString&
GetText()
{
return mDecoded;
}
};
/* /*
* Called on successfully reading the complete stream. * Called on successfully reading the complete stream.
*/ */
@ -1436,128 +1390,81 @@ FetchBody<Derived>::ContinueConsumeBody(nsresult aStatus, uint32_t aResultLength
jsapi.Init(DerivedClass()->GetParentObject()); jsapi.Init(DerivedClass()->GetParentObject());
JSContext* cx = jsapi.cx(); JSContext* cx = jsapi.cx();
ErrorResult error;
switch (mConsumeType) { switch (mConsumeType) {
case CONSUME_ARRAYBUFFER: { case CONSUME_ARRAYBUFFER: {
JS::Rooted<JSObject*> arrayBuffer(cx); JS::Rooted<JSObject*> arrayBuffer(cx);
arrayBuffer = JS_NewArrayBufferWithContents(cx, aResultLength, reinterpret_cast<void *>(aResult)); FetchUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult,
if (!arrayBuffer) { error);
JS_ClearPendingException(cx);
localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
NS_WARNING("OUT OF MEMORY");
return;
}
JS::Rooted<JS::Value> val(cx); error.WouldReportJSException();
val.setObjectOrNull(arrayBuffer); if (!error.Failed()) {
localPromise->MaybeResolve(cx, val); JS::Rooted<JS::Value> val(cx);
// ArrayBuffer takes over ownership. val.setObjectOrNull(arrayBuffer);
autoFree.Reset();
return; localPromise->MaybeResolve(cx, val);
// ArrayBuffer takes over ownership.
autoFree.Reset();
}
break;
} }
case CONSUME_BLOB: { case CONSUME_BLOB: {
nsRefPtr<dom::Blob> blob = nsRefPtr<dom::Blob> blob = FetchUtil::ConsumeBlob(
Blob::CreateMemoryBlob(DerivedClass()->GetParentObject(), DerivedClass()->GetParentObject(), NS_ConvertUTF8toUTF16(mMimeType),
reinterpret_cast<void *>(aResult), aResultLength, aResultLength, aResult, error);
NS_ConvertUTF8toUTF16(mMimeType)); error.WouldReportJSException();
if (!error.Failed()) {
if (!blob) { localPromise->MaybeResolve(blob);
localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR); // File takes over ownership.
return; autoFree.Reset();
} }
break;
localPromise->MaybeResolve(blob);
// File takes over ownership.
autoFree.Reset();
return;
} }
case CONSUME_FORMDATA: { case CONSUME_FORMDATA: {
nsCString data; nsCString data;
data.Adopt(reinterpret_cast<char*>(aResult), aResultLength); data.Adopt(reinterpret_cast<char*>(aResult), aResultLength);
autoFree.Reset(); autoFree.Reset();
NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data"); nsRefPtr<nsFormData> fd = FetchUtil::ConsumeFormData(
DerivedClass()->GetParentObject(),
// Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary= mMimeType, data, error);
// but disallow multipart/form-datafoobar. if (!error.Failed()) {
bool isValidFormDataMimeType = StringBeginsWith(mMimeType, formDataMimeType);
if (isValidFormDataMimeType && mMimeType.Length() > formDataMimeType.Length()) {
isValidFormDataMimeType = mMimeType[formDataMimeType.Length()] == ';';
}
if (isValidFormDataMimeType) {
FormDataParser parser(mMimeType, data, DerivedClass()->GetParentObject());
if (!parser.Parse()) {
ErrorResult result;
result.ThrowTypeError(MSG_BAD_FORMDATA);
localPromise->MaybeReject(result);
return;
}
nsRefPtr<nsFormData> fd = parser.FormData();
MOZ_ASSERT(fd);
localPromise->MaybeResolve(fd); localPromise->MaybeResolve(fd);
} else {
NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
bool isValidUrlEncodedMimeType = StringBeginsWith(mMimeType, urlDataMimeType);
if (isValidUrlEncodedMimeType && mMimeType.Length() > urlDataMimeType.Length()) {
isValidUrlEncodedMimeType = mMimeType[urlDataMimeType.Length()] == ';';
}
if (isValidUrlEncodedMimeType) {
URLParams params;
params.ParseInput(data);
nsRefPtr<nsFormData> fd = new nsFormData(DerivedClass()->GetParentObject());
FillFormIterator iterator(fd);
DebugOnly<bool> status = params.ForEach(iterator);
MOZ_ASSERT(status);
localPromise->MaybeResolve(fd);
} else {
ErrorResult result;
result.ThrowTypeError(MSG_BAD_FORMDATA);
localPromise->MaybeReject(result);
}
} }
return; break;
} }
case CONSUME_TEXT: case CONSUME_TEXT:
// fall through handles early exit. // fall through handles early exit.
case CONSUME_JSON: { case CONSUME_JSON: {
StreamDecoder decoder; nsString decoded;
decoder.AppendText(reinterpret_cast<char*>(aResult), aResultLength); if (NS_SUCCEEDED(FetchUtil::ConsumeText(aResultLength, aResult, decoded))) {
if (mConsumeType == CONSUME_TEXT) {
nsString& decoded = decoder.GetText(); localPromise->MaybeResolve(decoded);
if (mConsumeType == CONSUME_TEXT) { } else {
localPromise->MaybeResolve(decoded); JS::Rooted<JS::Value> json(cx);
return; FetchUtil::ConsumeJson(cx, &json, decoded, error);
} if (!error.Failed()) {
localPromise->MaybeResolve(cx, json);
AutoForceSetExceptionOnContext forceExn(cx); }
JS::Rooted<JS::Value> json(cx);
if (!JS_ParseJSON(cx, decoded.get(), decoded.Length(), &json)) {
if (!JS_IsExceptionPending(cx)) {
localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
return;
} }
};
JS::Rooted<JS::Value> exn(cx); break;
DebugOnly<bool> gotException = JS_GetPendingException(cx, &exn);
MOZ_ASSERT(gotException);
JS_ClearPendingException(cx);
localPromise->MaybeReject(cx, exn);
return;
}
localPromise->MaybeResolve(cx, json);
return;
} }
default:
NS_NOTREACHED("Unexpected consume body type");
} }
NS_NOTREACHED("Unexpected consume body type"); error.WouldReportJSException();
if (error.Failed()) {
if (error.IsJSException()) {
JS::Rooted<JS::Value> exn(cx);
error.StealJSException(cx, &exn);
localPromise->MaybeReject(cx, exn);
} else {
localPromise->MaybeReject(error);
}
}
} }
template <class Derived> template <class Derived>
@ -1622,5 +1529,6 @@ FetchBody<Request>::SetMimeType();
template template
void void
FetchBody<Response>::SetMimeType(); FetchBody<Response>::SetMimeType();
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla

View File

@ -1,10 +1,63 @@
#include "FetchUtil.h" #include "FetchUtil.h"
#include "nsError.h" #include "nsError.h"
#include "nsIUnicodeDecoder.h"
#include "nsString.h" #include "nsString.h"
#include "mozilla/dom/EncodingUtils.h"
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
namespace {
class StreamDecoder final
{
nsCOMPtr<nsIUnicodeDecoder> mDecoder;
nsString mDecoded;
public:
StreamDecoder()
: mDecoder(EncodingUtils::DecoderForEncoding("UTF-8"))
{
MOZ_ASSERT(mDecoder);
}
nsresult
AppendText(const char* aSrcBuffer, uint32_t aSrcBufferLen)
{
int32_t destBufferLen;
nsresult rv =
mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, &destBufferLen);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!mDecoded.SetCapacity(mDecoded.Length() + destBufferLen, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
char16_t* destBuffer = mDecoded.BeginWriting() + mDecoded.Length();
int32_t totalChars = mDecoded.Length();
int32_t srcLen = (int32_t) aSrcBufferLen;
int32_t outLen = destBufferLen;
rv = mDecoder->Convert(aSrcBuffer, &srcLen, destBuffer, &outLen);
MOZ_ASSERT(NS_SUCCEEDED(rv));
totalChars += outLen;
mDecoded.SetLength(totalChars);
return NS_OK;
}
nsString&
GetText()
{
return mDecoded;
}
};
}
// static // static
nsresult nsresult
FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod) FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod)
@ -33,5 +86,133 @@ FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod
return NS_OK; return NS_OK;
} }
// static
void
FetchUtil::ConsumeArrayBuffer(JSContext* aCx,
JS::MutableHandle<JSObject*> aValue,
uint32_t aInputLength, uint8_t* aInput,
ErrorResult& aRv)
{
JS::Rooted<JSObject*> arrayBuffer(aCx);
arrayBuffer = JS_NewArrayBufferWithContents(aCx, aInputLength,
reinterpret_cast<void *>(aInput));
if (!arrayBuffer) {
JS_ClearPendingException(aCx);
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
aValue.set(arrayBuffer);
}
// static
already_AddRefed<Blob>
FetchUtil::ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
uint32_t aInputLength, uint8_t* aInput,
ErrorResult& aRv)
{
nsRefPtr<Blob> blob =
Blob::CreateMemoryBlob(aParent,
reinterpret_cast<void *>(aInput), aInputLength,
aMimeType);
if (!blob) {
aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
return nullptr;
}
return blob.forget();
}
// static
already_AddRefed<nsFormData>
FetchUtil::ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
const nsCString& aStr, ErrorResult& aRv)
{
NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data");
// Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary=
// but disallow multipart/form-datafoobar.
bool isValidFormDataMimeType = StringBeginsWith(aMimeType, formDataMimeType);
if (isValidFormDataMimeType && aMimeType.Length() > formDataMimeType.Length()) {
isValidFormDataMimeType = aMimeType[formDataMimeType.Length()] == ';';
}
if (isValidFormDataMimeType) {
FormDataParser parser(aMimeType, aStr, aParent);
if (!parser.Parse()) {
aRv.ThrowTypeError(MSG_BAD_FORMDATA);
return nullptr;
}
nsRefPtr<nsFormData> fd = parser.FormData();
MOZ_ASSERT(fd);
return fd.forget();
}
NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
bool isValidUrlEncodedMimeType = StringBeginsWith(aMimeType, urlDataMimeType);
if (isValidUrlEncodedMimeType && aMimeType.Length() > urlDataMimeType.Length()) {
isValidUrlEncodedMimeType = aMimeType[urlDataMimeType.Length()] == ';';
}
if (isValidUrlEncodedMimeType) {
URLParams params;
params.ParseInput(aStr);
nsRefPtr<nsFormData> fd = new nsFormData(aParent);
FillFormIterator iterator(fd);
DebugOnly<bool> status = params.ForEach(iterator);
MOZ_ASSERT(status);
return fd.forget();
}
aRv.ThrowTypeError(MSG_BAD_FORMDATA);
return nullptr;
}
// static
nsresult
FetchUtil::ConsumeText(uint32_t aInputLength, uint8_t* aInput,
nsString& aText)
{
StreamDecoder decoder;
nsresult rv = decoder.AppendText(reinterpret_cast<char*>(aInput),
aInputLength);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aText = decoder.GetText();
return NS_OK;
}
// static
void
FetchUtil::ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
const nsString& aStr, ErrorResult& aRv)
{
aRv.MightThrowJSException();
AutoForceSetExceptionOnContext forceExn(aCx);
JS::Rooted<JS::Value> json(aCx);
if (!JS_ParseJSON(aCx, aStr.get(), aStr.Length(), &json)) {
if (!JS_IsExceptionPending(aCx)) {
aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
return;
}
JS::Rooted<JS::Value> exn(aCx);
DebugOnly<bool> gotException = JS_GetPendingException(aCx, &exn);
MOZ_ASSERT(gotException);
JS_ClearPendingException(aCx);
aRv.ThrowJSException(aCx, exn);
return;
}
aValue.set(json);
}
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla

View File

@ -3,6 +3,10 @@
#include "nsString.h" #include "nsString.h"
#include "nsError.h" #include "nsError.h"
#include "nsFormData.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/File.h"
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -21,6 +25,46 @@ public:
*/ */
static nsresult static nsresult
GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod); GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod);
/**
* Creates an array buffer from an array, assigning the result to |aValue|.
* The array buffer takes ownership of |aInput|, which must be allocated
* by |malloc|.
*/
static void
ConsumeArrayBuffer(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
uint32_t aInputLength, uint8_t* aInput, ErrorResult& aRv);
/**
* Creates an in-memory blob from an array. The blob takes ownership of
* |aInput|, which must be allocated by |malloc|.
*/
static already_AddRefed<Blob>
ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
uint32_t aInputLength, uint8_t* aInput, ErrorResult& aRv);
/**
* Creates a form data object from a UTF-8 encoded |aStr|. Returns |nullptr|
* and sets |aRv| to MSG_BAD_FORMDATA if |aStr| contains invalid data.
*/
static already_AddRefed<nsFormData>
ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
const nsCString& aStr, ErrorResult& aRv);
/**
* UTF-8 decodes |aInput| into |aText|. The caller may free |aInput|
* once this method returns.
*/
static nsresult
ConsumeText(uint32_t aInputLength, uint8_t* aInput, nsString& aText);
/**
* Parses a UTF-8 encoded |aStr| as JSON, assigning the result to |aValue|.
* Sets |aRv| to a syntax error if |aStr| contains invalid data.
*/
static void
ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
const nsString& aStr, ErrorResult& aRv);
}; };
} // namespace dom } // namespace dom

View File

@ -82,6 +82,16 @@ public:
aURL.Assign(mURL); aURL.Assign(mURL);
} }
void
GetUnfilteredUrl(nsCString& aURL) const
{
if (mWrappedResponse) {
return mWrappedResponse->GetUrl(aURL);
}
return GetUrl(aURL);
}
// SetUrl should only be called when the fragment has alredy been stripped // SetUrl should only be called when the fragment has alredy been stripped
void void
SetUrl(const nsACString& aURL) SetUrl(const nsACString& aURL)

View File

@ -34,7 +34,7 @@ interface nsIServiceWorkerInfo : nsISupports
readonly attribute DOMString waitingCacheName; readonly attribute DOMString waitingCacheName;
}; };
[scriptable, builtinclass, uuid(8d80dd18-597b-4378-b41e-768bfe48dd4f)] [scriptable, builtinclass, uuid(471b2d5d-64c3-4dea-bde1-219853dcaac8)]
interface nsIServiceWorkerManager : nsISupports interface nsIServiceWorkerManager : nsISupports
{ {
/** /**
@ -141,9 +141,10 @@ interface nsIServiceWorkerManager : nsISupports
in AString aIcon, in AString aIcon,
in AString aData, in AString aData,
in AString aBehavior); in AString aBehavior);
void sendPushEvent(in ACString aOriginAttributes, [optional_argc] void sendPushEvent(in ACString aOriginAttributes,
in ACString aScope, in ACString aScope,
in DOMString aData); [optional] in uint32_t aDataLength,
[optional, array, size_is(aDataLength)] in uint8_t aDataBytes);
void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes, void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes,
in ACString scope); in ACString scope);

View File

@ -33,6 +33,7 @@
29 = Unbalanced curly brace. 29 = Unbalanced curly brace.
30 = Creating an element with an invalid QName. 30 = Creating an element with an invalid QName.
31 = Variable binding shadows variable binding within the same template. 31 = Variable binding shadows variable binding within the same template.
32 = Call to the key function not allowed.
LoadingError = Error loading stylesheet: %S LoadingError = Error loading stylesheet: %S
TransformError = Error during XSLT transformation: %S TransformError = Error during XSLT transformation: %S

View File

@ -358,6 +358,14 @@ DOMMediaStream::StopTrack(TrackID aTrackID)
} }
} }
already_AddRefed<Promise>
DOMMediaStream::ApplyConstraintsToTrack(TrackID aTrackID,
const MediaTrackConstraints& aConstraints,
ErrorResult &aRv)
{
return nullptr;
}
bool bool
DOMMediaStream::CombineWithPrincipal(nsIPrincipal* aPrincipal) DOMMediaStream::CombineWithPrincipal(nsIPrincipal* aPrincipal)
{ {

View File

@ -48,6 +48,7 @@ class VideoTrack;
class AudioTrackList; class AudioTrackList;
class VideoTrackList; class VideoTrackList;
class MediaTrackListListener; class MediaTrackListListener;
struct MediaTrackConstraints;
} // namespace dom } // namespace dom
namespace layers { namespace layers {
@ -77,6 +78,7 @@ class DOMMediaStream : public DOMEventTargetHelper
typedef dom::MediaTrackListListener MediaTrackListListener; typedef dom::MediaTrackListListener MediaTrackListListener;
public: public:
typedef dom::MediaTrackConstraints MediaTrackConstraints;
typedef uint8_t TrackTypeHints; typedef uint8_t TrackTypeHints;
DOMMediaStream(); DOMMediaStream();
@ -121,6 +123,11 @@ public:
virtual void StopTrack(TrackID aTrackID); virtual void StopTrack(TrackID aTrackID);
virtual already_AddRefed<dom::Promise>
ApplyConstraintsToTrack(TrackID aTrackID,
const MediaTrackConstraints& aConstraints,
ErrorResult &aRv);
virtual DOMLocalMediaStream* AsDOMLocalMediaStream() { return nullptr; } virtual DOMLocalMediaStream* AsDOMLocalMediaStream() { return nullptr; }
virtual DOMHwMediaStream* AsDOMHwMediaStream() { return nullptr; } virtual DOMHwMediaStream* AsDOMHwMediaStream() { return nullptr; }

View File

@ -79,8 +79,6 @@
#include "mozilla/WindowsVersion.h" #include "mozilla/WindowsVersion.h"
#endif #endif
#include <map>
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
// GetTickCount() and conflicts with MediaStream::GetCurrentTime. // GetTickCount() and conflicts with MediaStream::GetCurrentTime.
#ifdef GetCurrentTime #ifdef GetCurrentTime
@ -119,6 +117,7 @@ using dom::File;
using dom::MediaStreamConstraints; using dom::MediaStreamConstraints;
using dom::MediaTrackConstraintSet; using dom::MediaTrackConstraintSet;
using dom::MediaTrackConstraints; using dom::MediaTrackConstraints;
using dom::MediaStreamTrack;
using dom::MediaStreamError; using dom::MediaStreamError;
using dom::GetUserMediaRequest; using dom::GetUserMediaRequest;
using dom::Sequence; using dom::Sequence;
@ -214,6 +213,154 @@ HostHasPermission(nsIURI &docURI)
return false; return false;
} }
// Generic class for running long media operations like Start off the main
// thread, and then (because nsDOMMediaStreams aren't threadsafe),
// ProxyReleases mStream since it's cycle collected.
class MediaOperationTask : public Task
{
public:
// so we can send Stop without AddRef()ing from the MSG thread
MediaOperationTask(MediaOperation aType,
GetUserMediaCallbackMediaStreamListener* aListener,
DOMMediaStream* aStream,
DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
AudioDevice* aAudioDevice,
VideoDevice* aVideoDevice,
bool aBool,
uint64_t aWindowID,
already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
const dom::MediaTrackConstraints& aConstraints = dom::MediaTrackConstraints())
: mType(aType)
, mStream(aStream)
, mOnTracksAvailableCallback(aOnTracksAvailableCallback)
, mAudioDevice(aAudioDevice)
, mVideoDevice(aVideoDevice)
, mListener(aListener)
, mBool(aBool)
, mWindowID(aWindowID)
, mOnFailure(aError)
, mConstraints(aConstraints)
{}
~MediaOperationTask()
{
// MediaStreams can be released on any thread.
}
void
ReturnCallbackError(nsresult rv, const char* errorLog);
void
Run()
{
SourceMediaStream *source = mListener->GetSourceStream();
// No locking between these is required as all the callbacks for the
// same MediaStream will occur on the same thread.
if (!source) // means the stream was never Activated()
return;
switch (mType) {
case MEDIA_START:
{
NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
nsresult rv;
if (mAudioDevice) {
rv = mAudioDevice->GetSource()->Start(source, kAudioTrack);
if (NS_FAILED(rv)) {
ReturnCallbackError(rv, "Starting audio failed");
return;
}
}
if (mVideoDevice) {
rv = mVideoDevice->GetSource()->Start(source, kVideoTrack);
if (NS_FAILED(rv)) {
ReturnCallbackError(rv, "Starting video failed");
return;
}
}
// Start() queued the tracks to be added synchronously to avoid races
source->FinishAddTracks();
source->SetPullEnabled(true);
source->AdvanceKnownTracksTime(STREAM_TIME_MAX);
MM_LOG(("started all sources"));
// Forward mOnTracksAvailableCallback to GetUserMediaNotificationEvent,
// because mOnTracksAvailableCallback needs to be added to mStream
// on the main thread.
nsIRunnable *event =
new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING,
mStream.forget(),
mOnTracksAvailableCallback.forget(),
mAudioDevice != nullptr,
mVideoDevice != nullptr,
mWindowID, mOnFailure.forget());
// event must always be released on mainthread due to the JS callbacks
// in the TracksAvailableCallback
NS_DispatchToMainThread(event);
}
break;
case MEDIA_STOP:
case MEDIA_STOP_TRACK:
{
NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
if (mAudioDevice) {
mAudioDevice->GetSource()->Stop(source, kAudioTrack);
mAudioDevice->GetSource()->Deallocate();
}
if (mVideoDevice) {
mVideoDevice->GetSource()->Stop(source, kVideoTrack);
mVideoDevice->GetSource()->Deallocate();
}
// Do this after stopping all tracks with EndTrack()
if (mBool) {
source->Finish();
}
nsIRunnable *event =
new GetUserMediaNotificationEvent(mListener,
mType == MEDIA_STOP ?
GetUserMediaNotificationEvent::STOPPING :
GetUserMediaNotificationEvent::STOPPED_TRACK,
mAudioDevice != nullptr,
mVideoDevice != nullptr,
mWindowID);
// event must always be released on mainthread due to the JS callbacks
// in the TracksAvailableCallback
NS_DispatchToMainThread(event);
}
break;
case MEDIA_DIRECT_LISTENERS:
{
NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
if (mVideoDevice) {
mVideoDevice->GetSource()->SetDirectListeners(mBool);
}
}
break;
default:
MOZ_ASSERT(false,"invalid MediaManager operation");
break;
}
}
private:
MediaOperation mType;
nsRefPtr<DOMMediaStream> mStream;
nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
nsRefPtr<AudioDevice> mAudioDevice; // threadsafe
nsRefPtr<VideoDevice> mVideoDevice; // threadsafe
nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
bool mBool;
uint64_t mWindowID;
nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
dom::MediaTrackConstraints mConstraints;
};
/** /**
* Send an error back to content. * Send an error back to content.
* Do this only on the main thread. The onSuccess callback is also passed here * Do this only on the main thread. The onSuccess callback is also passed here
@ -470,6 +617,16 @@ nsresult AudioDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
return GetSource()->Allocate(aConstraints, aPrefs, mID); return GetSource()->Allocate(aConstraints, aPrefs, mID);
} }
nsresult VideoDevice::Restart(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs) {
return GetSource()->Restart(aConstraints, aPrefs, mID);
}
nsresult AudioDevice::Restart(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs) {
return GetSource()->Restart(aConstraints, aPrefs, mID);
}
/** /**
* A subclass that we only use to stash internal pointers to MediaStreamGraph objects * A subclass that we only use to stash internal pointers to MediaStreamGraph objects
* that need to be cleaned up. * that need to be cleaned up.
@ -480,23 +637,23 @@ public:
static already_AddRefed<nsDOMUserMediaStream> static already_AddRefed<nsDOMUserMediaStream>
CreateTrackUnionStream(nsIDOMWindow* aWindow, CreateTrackUnionStream(nsIDOMWindow* aWindow,
GetUserMediaCallbackMediaStreamListener* aListener, GetUserMediaCallbackMediaStreamListener* aListener,
MediaEngineSource* aAudioSource, AudioDevice* aAudioDevice,
MediaEngineSource* aVideoSource, VideoDevice* aVideoDevice,
MediaStreamGraph* aMSG) MediaStreamGraph* aMSG)
{ {
nsRefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aListener, nsRefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aListener,
aAudioSource, aAudioDevice,
aVideoSource); aVideoDevice);
stream->InitTrackUnionStream(aWindow, aMSG); stream->InitTrackUnionStream(aWindow, aMSG);
return stream.forget(); return stream.forget();
} }
nsDOMUserMediaStream(GetUserMediaCallbackMediaStreamListener* aListener, nsDOMUserMediaStream(GetUserMediaCallbackMediaStreamListener* aListener,
MediaEngineSource *aAudioSource, AudioDevice *aAudioDevice,
MediaEngineSource *aVideoSource) : VideoDevice *aVideoDevice) :
mListener(aListener), mListener(aListener),
mAudioSource(aAudioSource), mAudioDevice(aAudioDevice),
mVideoSource(aVideoSource), mVideoDevice(aVideoDevice),
mEchoOn(true), mEchoOn(true),
mAgcOn(false), mAgcOn(false),
mNoiseOn(true), mNoiseOn(true),
@ -543,13 +700,60 @@ public:
// risky to do late in a release since that will affect all track ends, and not // risky to do late in a release since that will affect all track ends, and not
// just StopTrack()s. // just StopTrack()s.
if (GetDOMTrackFor(aTrackID)) { if (GetDOMTrackFor(aTrackID)) {
mListener->StopTrack(aTrackID, !!GetDOMTrackFor(aTrackID)->AsAudioStreamTrack()); mListener->StopTrack(aTrackID,
!!GetDOMTrackFor(aTrackID)->AsAudioStreamTrack());
} else { } else {
LOG(("StopTrack(%d) on non-existant track", aTrackID)); LOG(("StopTrack(%d) on non-existent track", aTrackID));
} }
} }
} }
virtual already_AddRefed<Promise>
ApplyConstraintsToTrack(TrackID aTrackID,
const MediaTrackConstraints& aConstraints,
ErrorResult &aRv) override
{
nsPIDOMWindow* window = static_cast<nsPIDOMWindow*>(mWindow.get());
nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
nsRefPtr<Promise> promise = Promise::Create(go, aRv);
if (sInShutdown) {
nsRefPtr<MediaStreamError> error = new MediaStreamError(window,
NS_LITERAL_STRING("AbortError"),
NS_LITERAL_STRING("In shutdown"));
promise->MaybeReject(error);
return promise.forget();
}
if (!mSourceStream) {
nsRefPtr<MediaStreamError> error = new MediaStreamError(window,
NS_LITERAL_STRING("InternalError"),
NS_LITERAL_STRING("No stream."));
promise->MaybeReject(error);
return promise.forget();
}
nsRefPtr<dom::MediaStreamTrack> track = GetDOMTrackFor(aTrackID);
if (!track) {
LOG(("ApplyConstraintsToTrack(%d) on non-existent track", aTrackID));
nsRefPtr<MediaStreamError> error = new MediaStreamError(window,
NS_LITERAL_STRING("InternalError"),
NS_LITERAL_STRING("No track."));
promise->MaybeReject(error);
return promise.forget();
}
typedef media::Pledge<bool, MediaStreamError*> PledgeVoid;
nsRefPtr<PledgeVoid> p = mListener->ApplyConstraintsToTrack(window,
aTrackID, !!track->AsAudioStreamTrack(), aConstraints);
p->Then([promise](bool& aDummy) mutable {
promise->MaybeResolve(false);
}, [promise](MediaStreamError*& reason) mutable {
promise->MaybeReject(reason);
});
return promise.forget();
}
#if 0 #if 0
virtual void NotifyMediaStreamTrackEnded(dom::MediaStreamTrack* aTrack) virtual void NotifyMediaStreamTrackEnded(dom::MediaStreamTrack* aTrack)
{ {
@ -598,14 +802,14 @@ public:
} }
// let us intervene for direct listeners when someone does track.enabled = false // let us intervene for direct listeners when someone does track.enabled = false
virtual void SetTrackEnabled(TrackID aID, bool aEnabled) override virtual void SetTrackEnabled(TrackID aTrackID, bool aEnabled) override
{ {
// We encapsulate the SourceMediaStream and TrackUnion into one entity, so // We encapsulate the SourceMediaStream and TrackUnion into one entity, so
// we can handle the disabling at the SourceMediaStream // we can handle the disabling at the SourceMediaStream
// We need to find the input track ID for output ID aID, so we let the TrackUnion // We need to find the input track ID for output ID aTrackID, so we let the TrackUnion
// forward the request to the source and translate the ID // forward the request to the source and translate the ID
GetStream()->AsProcessedStream()->ForwardTrackEnabled(aID, aEnabled); GetStream()->AsProcessedStream()->ForwardTrackEnabled(aTrackID, aEnabled);
} }
virtual DOMLocalMediaStream* AsDOMLocalMediaStream() override virtual DOMLocalMediaStream* AsDOMLocalMediaStream() override
@ -618,10 +822,10 @@ public:
// MediaEngine supports only one video and on video track now and TrackID is // MediaEngine supports only one video and on video track now and TrackID is
// fixed in MediaEngine. // fixed in MediaEngine.
if (aTrackID == kVideoTrack) { if (aTrackID == kVideoTrack) {
return mVideoSource; return mVideoDevice ? mVideoDevice->GetSource() : nullptr;
} }
else if (aTrackID == kAudioTrack) { else if (aTrackID == kAudioTrack) {
return mAudioSource; return mAudioDevice ? mAudioDevice->GetSource() : nullptr;
} }
return nullptr; return nullptr;
@ -632,8 +836,8 @@ public:
nsRefPtr<SourceMediaStream> mSourceStream; nsRefPtr<SourceMediaStream> mSourceStream;
nsRefPtr<MediaInputPort> mPort; nsRefPtr<MediaInputPort> mPort;
nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
nsRefPtr<MediaEngineSource> mAudioSource; // so we can turn on AEC nsRefPtr<AudioDevice> mAudioDevice; // so we can turn on AEC
nsRefPtr<MediaEngineSource> mVideoSource; nsRefPtr<VideoDevice> mVideoDevice;
bool mEchoOn; bool mEchoOn;
bool mAgcOn; bool mAgcOn;
bool mNoiseOn; bool mNoiseOn;
@ -686,11 +890,11 @@ public:
uint64_t aWindowID, uint64_t aWindowID,
GetUserMediaCallbackMediaStreamListener* aListener, GetUserMediaCallbackMediaStreamListener* aListener,
const nsCString& aOrigin, const nsCString& aOrigin,
MediaEngineSource* aAudioSource, AudioDevice* aAudioDevice,
MediaEngineSource* aVideoSource, VideoDevice* aVideoDevice,
PeerIdentity* aPeerIdentity) PeerIdentity* aPeerIdentity)
: mAudioSource(aAudioSource) : mAudioDevice(aAudioDevice)
, mVideoSource(aVideoSource) , mVideoDevice(aVideoDevice)
, mWindowID(aWindowID) , mWindowID(aWindowID)
, mListener(aListener) , mListener(aListener)
, mOrigin(aOrigin) , mOrigin(aOrigin)
@ -787,7 +991,7 @@ public:
#endif #endif
MediaStreamGraph::GraphDriverType graphDriverType = MediaStreamGraph::GraphDriverType graphDriverType =
mAudioSource ? MediaStreamGraph::AUDIO_THREAD_DRIVER mAudioDevice ? MediaStreamGraph::AUDIO_THREAD_DRIVER
: MediaStreamGraph::SYSTEM_THREAD_DRIVER; : MediaStreamGraph::SYSTEM_THREAD_DRIVER;
MediaStreamGraph* msg = MediaStreamGraph* msg =
MediaStreamGraph::GetInstance(graphDriverType, MediaStreamGraph::GetInstance(graphDriverType,
@ -800,8 +1004,8 @@ public:
// using the audio source and the SourceMediaStream, which acts as // using the audio source and the SourceMediaStream, which acts as
// placeholders. We re-route a number of stream internaly in the MSG and mix // placeholders. We re-route a number of stream internaly in the MSG and mix
// them down instead. // them down instead.
if (mAudioSource && if (mAudioDevice &&
mAudioSource->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) { mAudioDevice->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
domStream = DOMLocalMediaStream::CreateAudioCaptureStream(window, msg); domStream = DOMLocalMediaStream::CreateAudioCaptureStream(window, msg);
// It should be possible to pipe the capture stream to anything. CORS is // It should be possible to pipe the capture stream to anything. CORS is
// not a problem here, we got explicit user content. // not a problem here, we got explicit user content.
@ -814,7 +1018,7 @@ public:
// avoid us blocking // avoid us blocking
nsRefPtr<nsDOMUserMediaStream> trackunion = nsRefPtr<nsDOMUserMediaStream> trackunion =
nsDOMUserMediaStream::CreateTrackUnionStream(window, mListener, nsDOMUserMediaStream::CreateTrackUnionStream(window, mListener,
mAudioSource, mVideoSource, mAudioDevice, mVideoDevice,
msg); msg);
trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true); trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true);
nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()-> nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()->
@ -859,7 +1063,7 @@ public:
// Activate our listener. We'll call Start() on the source when get a callback // Activate our listener. We'll call Start() on the source when get a callback
// that the MediaStream has started consuming. The listener is freed // that the MediaStream has started consuming. The listener is freed
// when the page is invalidated (on navigation or close). // when the page is invalidated (on navigation or close).
mListener->Activate(stream.forget(), mAudioSource, mVideoSource); mListener->Activate(stream.forget(), mAudioDevice, mVideoDevice);
// Note: includes JS callbacks; must be released on MainThread // Note: includes JS callbacks; must be released on MainThread
TracksAvailableCallback* tracksAvailableCallback = TracksAvailableCallback* tracksAvailableCallback =
@ -874,11 +1078,11 @@ public:
// because that can take a while. // because that can take a while.
// Pass ownership of trackunion to the MediaOperationTask // Pass ownership of trackunion to the MediaOperationTask
// to ensure it's kept alive until the MediaOperationTask runs (at least). // to ensure it's kept alive until the MediaOperationTask runs (at least).
MediaManager::PostTask( MediaManager::PostTask(FROM_HERE,
FROM_HERE, new MediaOperationTask(MEDIA_START, mListener, domStream, new MediaOperationTask(MEDIA_START, mListener, domStream,
tracksAvailableCallback, mAudioSource, tracksAvailableCallback,
mVideoSource, false, mWindowID, mAudioDevice, mVideoDevice,
mOnFailure.forget())); false, mWindowID, mOnFailure.forget()));
// We won't need mOnFailure now. // We won't need mOnFailure now.
mOnFailure = nullptr; mOnFailure = nullptr;
@ -893,8 +1097,8 @@ public:
private: private:
nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess; nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure; nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
nsRefPtr<MediaEngineSource> mAudioSource; nsRefPtr<AudioDevice> mAudioDevice;
nsRefPtr<MediaEngineSource> mVideoSource; nsRefPtr<VideoDevice> mVideoDevice;
uint64_t mWindowID; uint64_t mWindowID;
nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
nsCString mOrigin; nsCString mOrigin;
@ -949,74 +1153,14 @@ GetSources(MediaEngine *engine, dom::MediaSourceEnum aSrcType,
} }
} }
// Apply constrains to a supplied list of sources (removes items from the list) static const char*
SelectSettings(MediaStreamConstraints &aConstraints,
template<class DeviceType> nsTArray<nsRefPtr<MediaDevice>>& aSources)
static void
ApplyConstraints(const MediaTrackConstraints &aConstraints,
nsTArray<nsRefPtr<DeviceType>>& aSources)
{
auto& c = aConstraints;
// First apply top-level constraints.
// Stack constraintSets that pass, starting with the required one, because the
// whole stack must be re-satisfied each time a capability-set is ruled out
// (this avoids storing state or pushing algorithm into the lower-level code).
nsTArray<const MediaTrackConstraintSet*> aggregateConstraints;
aggregateConstraints.AppendElement(&c);
std::multimap<uint32_t, nsRefPtr<DeviceType>> ordered;
for (uint32_t i = 0; i < aSources.Length();) {
uint32_t distance = aSources[i]->GetBestFitnessDistance(aggregateConstraints);
if (distance == UINT32_MAX) {
aSources.RemoveElementAt(i);
} else {
ordered.insert(std::pair<uint32_t, nsRefPtr<DeviceType>>(distance,
aSources[i]));
++i;
}
}
// Order devices by shortest distance
for (auto& ordinal : ordered) {
aSources.RemoveElement(ordinal.second);
aSources.AppendElement(ordinal.second);
}
// Then apply advanced constraints.
if (c.mAdvanced.WasPassed()) {
auto &array = c.mAdvanced.Value();
for (int i = 0; i < int(array.Length()); i++) {
aggregateConstraints.AppendElement(&array[i]);
nsTArray<nsRefPtr<DeviceType>> rejects;
for (uint32_t j = 0; j < aSources.Length();) {
if (aSources[j]->GetBestFitnessDistance(aggregateConstraints) == UINT32_MAX) {
rejects.AppendElement(aSources[j]);
aSources.RemoveElementAt(j);
} else {
++j;
}
}
if (!aSources.Length()) {
aSources.AppendElements(Move(rejects));
aggregateConstraints.RemoveElementAt(aggregateConstraints.Length() - 1);
}
}
}
}
static bool
ApplyConstraints(MediaStreamConstraints &aConstraints,
nsTArray<nsRefPtr<MediaDevice>>& aSources)
{ {
// Since the advanced part of the constraints algorithm needs to know when // Since the advanced part of the constraints algorithm needs to know when
// a candidate set is overconstrained (zero members), we must split up the // a candidate set is overconstrained (zero members), we must split up the
// list into videos and audios, and put it back together again at the end. // list into videos and audios, and put it back together again at the end.
bool overconstrained = false;
nsTArray<nsRefPtr<VideoDevice>> videos; nsTArray<nsRefPtr<VideoDevice>> videos;
nsTArray<nsRefPtr<AudioDevice>> audios; nsTArray<nsRefPtr<AudioDevice>> audios;
@ -1032,25 +1176,23 @@ ApplyConstraints(MediaStreamConstraints &aConstraints,
aSources.Clear(); aSources.Clear();
MOZ_ASSERT(!aSources.Length()); MOZ_ASSERT(!aSources.Length());
const char* badConstraint = nullptr;
if (IsOn(aConstraints.mVideo)) { if (IsOn(aConstraints.mVideo)) {
ApplyConstraints(GetInvariant(aConstraints.mVideo), videos); badConstraint = MediaConstraintsHelper::SelectSettings(
if (!videos.Length()) { GetInvariant(aConstraints.mVideo), videos);
overconstrained = true;
}
for (auto& video : videos) { for (auto& video : videos) {
aSources.AppendElement(video); aSources.AppendElement(video);
} }
} }
if (IsOn(aConstraints.mAudio)) { if (audios.Length() && IsOn(aConstraints.mAudio)) {
ApplyConstraints(GetInvariant(aConstraints.mAudio), audios); badConstraint = MediaConstraintsHelper::SelectSettings(
if (!audios.Length()) { GetInvariant(aConstraints.mAudio), audios);
overconstrained = true;
}
for (auto& audio : audios) { for (auto& audio : audios) {
aSources.AppendElement(audio); aSources.AppendElement(audio);
} }
} }
return !overconstrained; return badConstraint;
} }
/** /**
@ -1143,13 +1285,10 @@ public:
peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity); peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
} }
NS_DispatchToMainThread(do_AddRef(new GetUserMediaStreamRunnable( NS_DispatchToMainThread(do_AddRef(
mOnSuccess, mOnFailure, mWindowID, mListener, mOrigin, new GetUserMediaStreamRunnable(mOnSuccess, mOnFailure, mWindowID,
(mAudioDevice? mAudioDevice->GetSource() : nullptr), mListener, mOrigin, mAudioDevice,
(mVideoDevice? mVideoDevice->GetSource() : nullptr), mVideoDevice, peerIdentity)));
peerIdentity
)));
MOZ_ASSERT(!mOnSuccess); MOZ_ASSERT(!mOnSuccess);
MOZ_ASSERT(!mOnFailure); MOZ_ASSERT(!mOnFailure);
} }
@ -1697,7 +1836,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
auto& vc = c.mVideo.GetAsMediaTrackConstraints(); auto& vc = c.mVideo.GetAsMediaTrackConstraints();
videoType = StringToEnum(dom::MediaSourceEnumValues::strings, videoType = StringToEnum(dom::MediaSourceEnumValues::strings,
vc.mMediaSource, vc.mMediaSource,
videoType); dom::MediaSourceEnum::Other);
switch (videoType) { switch (videoType) {
case dom::MediaSourceEnum::Camera: case dom::MediaSourceEnum::Camera:
break; break;
@ -1740,7 +1879,10 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
case dom::MediaSourceEnum::Other: case dom::MediaSourceEnum::Other:
default: { default: {
nsRefPtr<MediaStreamError> error = nsRefPtr<MediaStreamError> error =
new MediaStreamError(aWindow, NS_LITERAL_STRING("NotFoundError")); new MediaStreamError(aWindow,
NS_LITERAL_STRING("OverconstrainedError"),
NS_LITERAL_STRING(""),
NS_LITERAL_STRING("mediaSource"));
onFailure->OnError(error); onFailure->OnError(error);
return NS_OK; return NS_OK;
} }
@ -1785,7 +1927,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
auto& ac = c.mAudio.GetAsMediaTrackConstraints(); auto& ac = c.mAudio.GetAsMediaTrackConstraints();
audioType = StringToEnum(dom::MediaSourceEnumValues::strings, audioType = StringToEnum(dom::MediaSourceEnumValues::strings,
ac.mMediaSource, ac.mMediaSource,
audioType); dom::MediaSourceEnum::Other);
// Work around WebIDL default since spec uses same dictionary w/audio & video. // Work around WebIDL default since spec uses same dictionary w/audio & video.
if (audioType == dom::MediaSourceEnum::Camera) { if (audioType == dom::MediaSourceEnum::Camera) {
audioType = dom::MediaSourceEnum::Microphone; audioType = dom::MediaSourceEnum::Microphone;
@ -1812,7 +1954,10 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
case dom::MediaSourceEnum::Other: case dom::MediaSourceEnum::Other:
default: { default: {
nsRefPtr<MediaStreamError> error = nsRefPtr<MediaStreamError> error =
new MediaStreamError(aWindow, NS_LITERAL_STRING("NotFoundError")); new MediaStreamError(aWindow,
NS_LITERAL_STRING("OverconstrainedError"),
NS_LITERAL_STRING(""),
NS_LITERAL_STRING("mediaSource"));
onFailure->OnError(error); onFailure->OnError(error);
return NS_OK; return NS_OK;
} }
@ -1905,8 +2050,19 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
} }
// Apply any constraints. This modifies the list. // Apply any constraints. This modifies the list.
const char* badConstraint = SelectSettings(c, *devices);
if (!ApplyConstraints(c, *devices)) { if (badConstraint) {
nsString constraint;
constraint.AssignASCII(badConstraint);
nsRefPtr<MediaStreamError> error =
new MediaStreamError(window,
NS_LITERAL_STRING("OverconstrainedError"),
NS_LITERAL_STRING(""),
constraint);
onFailure->OnError(error);
return;
}
if (!devices->Length()) {
nsRefPtr<MediaStreamError> error = nsRefPtr<MediaStreamError> error =
new MediaStreamError(window, NS_LITERAL_STRING("NotFoundError")); new MediaStreamError(window, NS_LITERAL_STRING("NotFoundError"));
onFailure->OnError(error); onFailure->OnError(error);
@ -1957,8 +2113,8 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
#ifdef MOZ_WEBRTC #ifdef MOZ_WEBRTC
EnableWebRtcLog(); EnableWebRtcLog();
#endif #endif
}, [onFailure](MediaStreamError& reason) mutable { }, [onFailure](MediaStreamError*& reason) mutable {
onFailure->OnError(&reason); onFailure->OnError(reason);
}); });
return NS_OK; return NS_OK;
} }
@ -2138,10 +2294,10 @@ MediaManager::EnumerateDevices(nsPIDOMWindow* aWindow,
mgr->RemoveFromWindowList(windowId, listener); mgr->RemoveFromWindowList(windowId, listener);
nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices); nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
onSuccess->OnSuccess(array); onSuccess->OnSuccess(array);
}, [onFailure, windowId, listener](MediaStreamError& reason) mutable { }, [onFailure, windowId, listener](MediaStreamError*& reason) mutable {
nsRefPtr<MediaManager> mgr = MediaManager_GetInstance(); nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
mgr->RemoveFromWindowList(windowId, listener); mgr->RemoveFromWindowList(windowId, listener);
onFailure->OnError(&reason); onFailure->OnError(reason);
}); });
return NS_OK; return NS_OK;
} }
@ -2853,10 +3009,10 @@ GetUserMediaCallbackMediaStreamListener::AudioConfig(bool aEchoOn,
bool aNoiseOn, uint32_t aNoise, bool aNoiseOn, uint32_t aNoise,
int32_t aPlayoutDelay) int32_t aPlayoutDelay)
{ {
if (mAudioSource) { if (mAudioDevice) {
#ifdef MOZ_WEBRTC #ifdef MOZ_WEBRTC
MediaManager::PostTask(FROM_HERE, MediaManager::PostTask(FROM_HERE,
NewRunnableMethod(mAudioSource.get(), &MediaEngineSource::Config, NewRunnableMethod(mAudioDevice->GetSource(), &MediaEngineSource::Config,
aEchoOn, aEcho, aAgcOn, aAGC, aNoiseOn, aEchoOn, aEcho, aAgcOn, aAGC, aNoiseOn,
aNoise, aPlayoutDelay)); aNoise, aPlayoutDelay));
#endif #endif
@ -2874,7 +3030,7 @@ GetUserMediaCallbackMediaStreamListener::Invalidate()
MediaManager::PostTask(FROM_HERE, MediaManager::PostTask(FROM_HERE,
new MediaOperationTask(MEDIA_STOP, new MediaOperationTask(MEDIA_STOP,
this, nullptr, nullptr, this, nullptr, nullptr,
mAudioSource, mVideoSource, mAudioDevice, mVideoDevice,
mFinished, mWindowID, nullptr)); mFinished, mWindowID, nullptr));
} }
@ -2884,18 +3040,18 @@ void
GetUserMediaCallbackMediaStreamListener::StopSharing() GetUserMediaCallbackMediaStreamListener::StopSharing()
{ {
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
if (mVideoSource && !mStopped && if (mVideoDevice && !mStopped &&
(mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Screen || (mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen ||
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Application || mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application ||
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Window)) { mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window)) {
// Stop the whole stream if there's no audio; just the video track if we have both // Stop the whole stream if there's no audio; just the video track if we have both
MediaManager::PostTask(FROM_HERE, MediaManager::PostTask(FROM_HERE,
new MediaOperationTask(mAudioSource ? MEDIA_STOP_TRACK : MEDIA_STOP, new MediaOperationTask(mAudioDevice ? MEDIA_STOP_TRACK : MEDIA_STOP,
this, nullptr, nullptr, this, nullptr, nullptr,
nullptr, mVideoSource, nullptr, mVideoDevice,
mFinished, mWindowID, nullptr)); mFinished, mWindowID, nullptr));
} else if (mAudioSource && } else if (mAudioDevice &&
mAudioSource->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) { mAudioDevice->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID); nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
MOZ_ASSERT(window); MOZ_ASSERT(window);
window->SetAudioCapture(false); window->SetAudioCapture(false);
@ -2907,25 +3063,119 @@ GetUserMediaCallbackMediaStreamListener::StopSharing()
} }
} }
// ApplyConstraints for track
already_AddRefed<GetUserMediaCallbackMediaStreamListener::PledgeVoid>
GetUserMediaCallbackMediaStreamListener::ApplyConstraintsToTrack(
nsPIDOMWindow* aWindow,
TrackID aTrackID,
bool aIsAudio,
const MediaTrackConstraints& aConstraints)
{
MOZ_ASSERT(NS_IsMainThread());
nsRefPtr<PledgeVoid> p = new PledgeVoid();
if (!(((aIsAudio && mAudioDevice) ||
(!aIsAudio && mVideoDevice)) && !mStopped))
{
LOG(("gUM track %d applyConstraints, but we don't have type %s",
aTrackID, aIsAudio ? "audio" : "video"));
p->Resolve(false);
return p.forget();
}
// XXX to support multiple tracks of a type in a stream, this should key off
// the TrackID and not just the type
nsRefPtr<AudioDevice> audioDevice = aIsAudio ? mAudioDevice.get() : nullptr;
nsRefPtr<VideoDevice> videoDevice = !aIsAudio ? mVideoDevice.get() : nullptr;
nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
uint32_t id = mgr->mOutstandingVoidPledges.Append(*p);
uint64_t windowId = aWindow->WindowID();
MediaManager::PostTask(FROM_HERE, NewTaskFrom([id, windowId,
audioDevice, videoDevice,
aConstraints]() mutable {
MOZ_ASSERT(MediaManager::IsInMediaThread());
nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
const char* badConstraint = nullptr;
nsresult rv = NS_OK;
if (audioDevice) {
rv = audioDevice->Restart(aConstraints, mgr->mPrefs);
if (rv == NS_ERROR_NOT_AVAILABLE) {
nsTArray<nsRefPtr<AudioDevice>> audios;
audios.AppendElement(audioDevice);
badConstraint = MediaConstraintsHelper::SelectSettings(aConstraints,
audios);
}
} else {
rv = videoDevice->Restart(aConstraints, mgr->mPrefs);
if (rv == NS_ERROR_NOT_AVAILABLE) {
nsTArray<nsRefPtr<VideoDevice>> videos;
videos.AppendElement(videoDevice);
badConstraint = MediaConstraintsHelper::SelectSettings(aConstraints,
videos);
}
}
NS_DispatchToMainThread(do_AddRef(NewRunnableFrom([id, windowId, rv,
badConstraint]() mutable {
MOZ_ASSERT(NS_IsMainThread());
nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
if (!mgr) {
return NS_OK;
}
nsRefPtr<PledgeVoid> p = mgr->mOutstandingVoidPledges.Remove(id);
if (p) {
if (NS_SUCCEEDED(rv)) {
p->Resolve(false);
} else {
nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
(nsGlobalWindow::GetInnerWindowWithId(windowId));
if (window) {
if (rv == NS_ERROR_NOT_AVAILABLE) {
nsString constraint;
constraint.AssignASCII(badConstraint);
nsRefPtr<MediaStreamError> error =
new MediaStreamError(window,
NS_LITERAL_STRING("OverconstrainedError"),
NS_LITERAL_STRING(""),
constraint);
p->Reject(error);
} else {
nsRefPtr<MediaStreamError> error =
new MediaStreamError(window,
NS_LITERAL_STRING("InternalError"));
p->Reject(error);
}
}
}
}
return NS_OK;
})));
}));
return p.forget();
}
// Stop backend for track // Stop backend for track
void void
GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aID, bool aIsAudio) GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID, bool aIsAudio)
{ {
if (((aIsAudio && mAudioSource) || if (((aIsAudio && mAudioDevice) ||
(!aIsAudio && mVideoSource)) && !mStopped) (!aIsAudio && mVideoDevice)) && !mStopped)
{ {
// XXX to support multiple tracks of a type in a stream, this should key off // XXX to support multiple tracks of a type in a stream, this should key off
// the TrackID and not just the type // the TrackID and not just the type
MediaManager::PostTask(FROM_HERE, MediaManager::PostTask(FROM_HERE,
new MediaOperationTask(MEDIA_STOP_TRACK, new MediaOperationTask(MEDIA_STOP_TRACK,
this, nullptr, nullptr, this, nullptr, nullptr,
aIsAudio ? mAudioSource.get() : nullptr, aIsAudio ? mAudioDevice.get() : nullptr,
!aIsAudio ? mVideoSource.get() : nullptr, !aIsAudio ? mVideoDevice.get() : nullptr,
mFinished, mWindowID, nullptr)); mFinished, mWindowID, nullptr));
} else { } else {
LOG(("gUM track %d ended, but we don't have type %s", LOG(("gUM track %d ended, but we don't have type %s",
aID, aIsAudio ? "audio" : "video")); aTrackID, aIsAudio ? "audio" : "video"));
} }
} }
@ -2946,7 +3196,7 @@ GetUserMediaCallbackMediaStreamListener::NotifyDirectListeners(MediaStreamGraph*
MediaManager::PostTask(FROM_HERE, MediaManager::PostTask(FROM_HERE,
new MediaOperationTask(MEDIA_DIRECT_LISTENERS, new MediaOperationTask(MEDIA_DIRECT_LISTENERS,
this, nullptr, nullptr, this, nullptr, nullptr,
mAudioSource, mVideoSource, mAudioDevice, mVideoDevice,
aHasListeners, mWindowID, nullptr)); aHasListeners, mWindowID, nullptr));
} }

View File

@ -47,12 +47,72 @@
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
struct MediaStreamConstraints; struct MediaStreamConstraints;
struct MediaTrackConstraints;
struct MediaTrackConstraintSet; struct MediaTrackConstraintSet;
} // namespace dom } // namespace dom
extern PRLogModuleInfo* GetMediaManagerLog(); extern PRLogModuleInfo* GetMediaManagerLog();
#define MM_LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg) #define MM_LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
class MediaDevice : public nsIMediaDevice
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
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, 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:
dom::MediaSourceEnum GetMediaSource() {
return mMediaSource;
}
bool mIsVideo;
};
class VideoDevice : public MediaDevice
{
public:
typedef MediaEngineVideoSource Source;
explicit VideoDevice(Source* aSource);
NS_IMETHOD GetType(nsAString& aType);
Source* GetSource();
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs);
nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs);
};
class AudioDevice : public MediaDevice
{
public:
typedef MediaEngineAudioSource Source;
explicit AudioDevice(Source* aSource);
NS_IMETHOD GetType(nsAString& aType);
Source* GetSource();
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs);
nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs);
};
/** /**
* This class is an implementation of MediaStreamListener. This is used * This class is an implementation of MediaStreamListener. This is used
* to Start() and Stop() the underlying MediaEngineSource when MediaStreams * to Start() and Stop() the underlying MediaEngineSource when MediaStreams
@ -79,13 +139,13 @@ public:
} }
void Activate(already_AddRefed<SourceMediaStream> aStream, void Activate(already_AddRefed<SourceMediaStream> aStream,
MediaEngineSource* aAudioSource, AudioDevice* aAudioDevice,
MediaEngineSource* aVideoSource) VideoDevice* aVideoDevice)
{ {
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
mStream = aStream; mStream = aStream;
mAudioSource = aAudioSource; mAudioDevice = aAudioDevice;
mVideoSource = aVideoSource; mVideoDevice = aVideoDevice;
mStream->AddListener(this); mStream->AddListener(this);
} }
@ -107,46 +167,57 @@ public:
void StopTrack(TrackID aID, bool aIsAudio); void StopTrack(TrackID aID, bool aIsAudio);
// mVideo/AudioSource are set by Activate(), so we assume they're capturing typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
already_AddRefed<PledgeVoid>
ApplyConstraintsToTrack(nsPIDOMWindow* aWindow,
TrackID aID, bool aIsAudio,
const dom::MediaTrackConstraints& aConstraints);
// mVideo/AudioDevice are set by Activate(), so we assume they're capturing
// if set and represent a real capture device. // if set and represent a real capture device.
bool CapturingVideo() bool CapturingVideo()
{ {
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
return mVideoSource && !mStopped && return mVideoDevice && !mStopped &&
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Camera && mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
(!mVideoSource->IsFake() || (!mVideoDevice->GetSource()->IsFake() ||
Preferences::GetBool("media.navigator.permission.fake")); Preferences::GetBool("media.navigator.permission.fake"));
} }
bool CapturingAudio() bool CapturingAudio()
{ {
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
return mAudioSource && !mStopped && return mAudioDevice && !mStopped &&
(!mAudioSource->IsFake() || (!mAudioDevice->GetSource()->IsFake() ||
Preferences::GetBool("media.navigator.permission.fake")); Preferences::GetBool("media.navigator.permission.fake"));
} }
bool CapturingScreen() bool CapturingScreen()
{ {
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
return mVideoSource && !mStopped && !mVideoSource->IsAvailable() && return mVideoDevice && !mStopped &&
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Screen; !mVideoDevice->GetSource()->IsAvailable() &&
mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen;
} }
bool CapturingWindow() bool CapturingWindow()
{ {
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
return mVideoSource && !mStopped && !mVideoSource->IsAvailable() && return mVideoDevice && !mStopped &&
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Window; !mVideoDevice->GetSource()->IsAvailable() &&
mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window;
} }
bool CapturingApplication() bool CapturingApplication()
{ {
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
return mVideoSource && !mStopped && !mVideoSource->IsAvailable() && return mVideoDevice && !mStopped &&
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Application; !mVideoDevice->GetSource()->IsAvailable() &&
mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application;
} }
bool CapturingBrowser() bool CapturingBrowser()
{ {
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
return mVideoSource && !mStopped && mVideoSource->IsAvailable() && return mVideoDevice && !mStopped &&
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Browser; mVideoDevice->GetSource()->IsAvailable() &&
mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Browser;
} }
void SetStopped() void SetStopped()
@ -187,11 +258,13 @@ public:
{ {
// Currently audio sources ignore NotifyPull, but they could // Currently audio sources ignore NotifyPull, but they could
// watch it especially for fake audio. // watch it especially for fake audio.
if (mAudioSource) { if (mAudioDevice) {
mAudioSource->NotifyPull(aGraph, mStream, kAudioTrack, aDesiredTime); mAudioDevice->GetSource()->NotifyPull(aGraph, mStream, kAudioTrack,
aDesiredTime);
} }
if (mVideoSource) { if (mVideoDevice) {
mVideoSource->NotifyPull(aGraph, mStream, kVideoTrack, aDesiredTime); mVideoDevice->GetSource()->NotifyPull(aGraph, mStream, kVideoTrack,
aDesiredTime);
} }
} }
@ -237,8 +310,8 @@ private:
// Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread // Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
// No locking needed as they're only addrefed except on the MediaManager thread // No locking needed as they're only addrefed except on the MediaManager thread
nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe refcnt nsRefPtr<AudioDevice> mAudioDevice; // threadsafe refcnt
nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe refcnt nsRefPtr<VideoDevice> mVideoDevice; // threadsafe refcnt
nsRefPtr<SourceMediaStream> mStream; // threadsafe refcnt nsRefPtr<SourceMediaStream> mStream; // threadsafe refcnt
bool mFinished; bool mFinished;
@ -253,7 +326,7 @@ class GetUserMediaNotificationEvent: public nsRunnable
enum GetUserMediaStatus { enum GetUserMediaStatus {
STARTING, STARTING,
STOPPING, STOPPING,
STOPPED_TRACK STOPPED_TRACK,
}; };
GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener, GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener,
GetUserMediaStatus aStatus, GetUserMediaStatus aStatus,
@ -291,7 +364,7 @@ typedef enum {
MEDIA_START, MEDIA_START,
MEDIA_STOP, MEDIA_STOP,
MEDIA_STOP_TRACK, MEDIA_STOP_TRACK,
MEDIA_DIRECT_LISTENERS MEDIA_DIRECT_LISTENERS,
} MediaOperation; } MediaOperation;
class MediaManager; class MediaManager;
@ -310,206 +383,9 @@ private:
nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback; nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
}; };
// Generic class for running long media operations like Start off the main
// thread, and then (because nsDOMMediaStreams aren't threadsafe),
// ProxyReleases mStream since it's cycle collected.
class MediaOperationTask : public Task
{
public:
// so we can send Stop without AddRef()ing from the MSG thread
MediaOperationTask(MediaOperation aType,
GetUserMediaCallbackMediaStreamListener* aListener,
DOMMediaStream* aStream,
DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
MediaEngineSource* aAudioSource,
MediaEngineSource* aVideoSource,
bool aBool,
uint64_t aWindowID,
already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError)
: mType(aType)
, mStream(aStream)
, mOnTracksAvailableCallback(aOnTracksAvailableCallback)
, mAudioSource(aAudioSource)
, mVideoSource(aVideoSource)
, mListener(aListener)
, mBool(aBool)
, mWindowID(aWindowID)
, mOnFailure(aError)
{}
~MediaOperationTask()
{
// MediaStreams can be released on any thread.
}
void
ReturnCallbackError(nsresult rv, const char* errorLog);
void
Run()
{
SourceMediaStream *source = mListener->GetSourceStream();
// No locking between these is required as all the callbacks for the
// same MediaStream will occur on the same thread.
if (!source) // means the stream was never Activated()
return;
switch (mType) {
case MEDIA_START:
{
NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
nsresult rv;
if (mAudioSource) {
rv = mAudioSource->Start(source, kAudioTrack);
if (NS_FAILED(rv)) {
ReturnCallbackError(rv, "Starting audio failed");
return;
}
}
if (mVideoSource) {
rv = mVideoSource->Start(source, kVideoTrack);
if (NS_FAILED(rv)) {
ReturnCallbackError(rv, "Starting video failed");
return;
}
}
// Start() queued the tracks to be added synchronously to avoid races
source->FinishAddTracks();
source->SetPullEnabled(true);
source->AdvanceKnownTracksTime(STREAM_TIME_MAX);
MM_LOG(("started all sources"));
// Forward mOnTracksAvailableCallback to GetUserMediaNotificationEvent,
// because mOnTracksAvailableCallback needs to be added to mStream
// on the main thread.
nsIRunnable *event =
new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING,
mStream.forget(),
mOnTracksAvailableCallback.forget(),
mAudioSource != nullptr,
mVideoSource != nullptr,
mWindowID, mOnFailure.forget());
// event must always be released on mainthread due to the JS callbacks
// in the TracksAvailableCallback
NS_DispatchToMainThread(event);
}
break;
case MEDIA_STOP:
case MEDIA_STOP_TRACK:
{
NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
if (mAudioSource) {
mAudioSource->Stop(source, kAudioTrack);
mAudioSource->Deallocate();
}
if (mVideoSource) {
mVideoSource->Stop(source, kVideoTrack);
mVideoSource->Deallocate();
}
// Do this after stopping all tracks with EndTrack()
if (mBool) {
source->Finish();
}
nsIRunnable *event =
new GetUserMediaNotificationEvent(mListener,
mType == MEDIA_STOP ?
GetUserMediaNotificationEvent::STOPPING :
GetUserMediaNotificationEvent::STOPPED_TRACK,
mAudioSource != nullptr,
mVideoSource != nullptr,
mWindowID);
// event must always be released on mainthread due to the JS callbacks
// in the TracksAvailableCallback
NS_DispatchToMainThread(event);
}
break;
case MEDIA_DIRECT_LISTENERS:
{
NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
if (mVideoSource) {
mVideoSource->SetDirectListeners(mBool);
}
}
break;
default:
MOZ_ASSERT(false,"invalid MediaManager operation");
break;
}
}
private:
MediaOperation mType;
nsRefPtr<DOMMediaStream> mStream;
nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe
nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe
nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
bool mBool;
uint64_t mWindowID;
nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
};
typedef nsTArray<nsRefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners; typedef nsTArray<nsRefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable; typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
class MediaDevice : public nsIMediaDevice
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
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, 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
{
public:
typedef MediaEngineVideoSource Source;
explicit VideoDevice(Source* aSource);
NS_IMETHOD GetType(nsAString& aType);
Source* GetSource();
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs);
};
class AudioDevice : public MediaDevice
{
public:
typedef MediaEngineAudioSource Source;
explicit AudioDevice(Source* aSource);
NS_IMETHOD GetType(nsAString& aType);
Source* GetSource();
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs);
};
// we could add MediaManager if needed // we could add MediaManager if needed
typedef void (*WindowListenerCallback)(MediaManager *aThis, typedef void (*WindowListenerCallback)(MediaManager *aThis,
uint64_t aWindowID, uint64_t aWindowID,
@ -519,6 +395,7 @@ typedef void (*WindowListenerCallback)(MediaManager *aThis,
class MediaManager final : public nsIMediaManagerService, class MediaManager final : public nsIMediaManagerService,
public nsIObserver public nsIObserver
{ {
friend GetUserMediaCallbackMediaStreamListener;
public: public:
static already_AddRefed<MediaManager> GetInstance(); static already_AddRefed<MediaManager> GetInstance();
@ -586,7 +463,7 @@ public:
typedef nsTArray<nsRefPtr<MediaDevice>> SourceSet; typedef nsTArray<nsRefPtr<MediaDevice>> SourceSet;
static bool IsPrivateBrowsing(nsPIDOMWindow *window); static bool IsPrivateBrowsing(nsPIDOMWindow *window);
private: private:
typedef media::Pledge<SourceSet*, dom::MediaStreamError> PledgeSourceSet; typedef media::Pledge<SourceSet*, dom::MediaStreamError*> PledgeSourceSet;
static bool IsPrivileged(); static bool IsPrivileged();
static bool IsLoop(nsIURI* aDocURI); static bool IsLoop(nsIURI* aDocURI);
@ -646,6 +523,7 @@ private:
static StaticRefPtr<MediaManager> sSingleton; static StaticRefPtr<MediaManager> sSingleton;
media::CoatCheck<PledgeSourceSet> mOutstandingPledges; media::CoatCheck<PledgeSourceSet> mOutstandingPledges;
media::CoatCheck<GetUserMediaCallbackMediaStreamListener::PledgeVoid> mOutstandingVoidPledges;
#if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK) #if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK)
nsRefPtr<nsDOMCameraManager> mCameraManager; nsRefPtr<nsDOMCameraManager> mCameraManager;
#endif #endif

View File

@ -12,10 +12,10 @@ namespace mozilla {
BaseMediaMgrError::BaseMediaMgrError(const nsAString& aName, BaseMediaMgrError::BaseMediaMgrError(const nsAString& aName,
const nsAString& aMessage, const nsAString& aMessage,
const nsAString& aConstraintName) const nsAString& aConstraint)
: mName(aName) : mName(aName)
, mMessage(aMessage) , mMessage(aMessage)
, mConstraintName(aConstraintName) , mConstraint(aConstraint)
{ {
if (mMessage.IsEmpty()) { if (mMessage.IsEmpty()) {
if (mName.EqualsLiteral("NotFoundError")) { if (mName.EqualsLiteral("NotFoundError")) {
@ -28,8 +28,9 @@ BaseMediaMgrError::BaseMediaMgrError(const nsAString& aName,
} else if (mName.EqualsLiteral("InternalError")) { } else if (mName.EqualsLiteral("InternalError")) {
mMessage.AssignLiteral("Internal error."); mMessage.AssignLiteral("Internal error.");
} else if (mName.EqualsLiteral("NotSupportedError")) { } else if (mName.EqualsLiteral("NotSupportedError")) {
mMessage.AssignLiteral("Constraints with no audio or video in it are not " mMessage.AssignLiteral("The operation is not supported.");
"supported"); } else if (mName.EqualsLiteral("OverconstrainedError")) {
mMessage.AssignLiteral("Constraints could be not satisfied.");
} }
} }
} }
@ -43,8 +44,8 @@ MediaStreamError::MediaStreamError(
nsPIDOMWindow* aParent, nsPIDOMWindow* aParent,
const nsAString& aName, const nsAString& aName,
const nsAString& aMessage, const nsAString& aMessage,
const nsAString& aConstraintName) const nsAString& aConstraint)
: BaseMediaMgrError(aName, aMessage, aConstraintName) : BaseMediaMgrError(aName, aMessage, aConstraint)
, mParent(aParent) {} , mParent(aParent) {}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaStreamError, mParent) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaStreamError, mParent)
@ -75,9 +76,9 @@ MediaStreamError::GetMessage(nsAString& aMessage) const
} }
void void
MediaStreamError::GetConstraintName(nsAString& aConstraintName) const MediaStreamError::GetConstraint(nsAString& aConstraint) const
{ {
aConstraintName = mConstraintName; aConstraint = mConstraint;
} }
} // namespace dom } // namespace dom

View File

@ -34,10 +34,10 @@ class BaseMediaMgrError
protected: protected:
BaseMediaMgrError(const nsAString& aName, BaseMediaMgrError(const nsAString& aName,
const nsAString& aMessage, const nsAString& aMessage,
const nsAString& aConstraintName); const nsAString& aConstraint);
const nsString mName; const nsString mName;
nsString mMessage; nsString mMessage;
const nsString mConstraintName; const nsString mConstraint;
}; };
class MediaMgrError final : public nsISupports, class MediaMgrError final : public nsISupports,
@ -46,8 +46,8 @@ class MediaMgrError final : public nsISupports,
public: public:
explicit MediaMgrError(const nsAString& aName, explicit MediaMgrError(const nsAString& aName,
const nsAString& aMessage = EmptyString(), const nsAString& aMessage = EmptyString(),
const nsAString& aConstraintName = EmptyString()) const nsAString& aConstraint = EmptyString())
: BaseMediaMgrError(aName, aMessage, aConstraintName) {} : BaseMediaMgrError(aName, aMessage, aConstraint) {}
NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_THREADSAFE_ISUPPORTS
@ -64,11 +64,11 @@ public:
MediaStreamError(nsPIDOMWindow* aParent, MediaStreamError(nsPIDOMWindow* aParent,
const nsAString& aName, const nsAString& aName,
const nsAString& aMessage = EmptyString(), const nsAString& aMessage = EmptyString(),
const nsAString& aConstraintName = EmptyString()); const nsAString& aConstraint = EmptyString());
MediaStreamError(nsPIDOMWindow* aParent, MediaStreamError(nsPIDOMWindow* aParent,
const BaseMediaMgrError& aOther) const BaseMediaMgrError& aOther)
: BaseMediaMgrError(aOther.mName, aOther.mMessage, aOther.mConstraintName) : BaseMediaMgrError(aOther.mName, aOther.mMessage, aOther.mConstraint)
, mParent(aParent) {} , mParent(aParent) {}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@ -83,7 +83,7 @@ public:
} }
void GetName(nsAString& aName) const; void GetName(nsAString& aName) const;
void GetMessage(nsAString& aMessage) const; void GetMessage(nsAString& aMessage) const;
void GetConstraintName(nsAString& aConstraintName) const; void GetConstraint(nsAString& aConstraint) const;
private: private:
virtual ~MediaStreamError() {} virtual ~MediaStreamError() {}

View File

@ -62,5 +62,12 @@ MediaStreamTrack::Stop()
mStream->StopTrack(mTrackID); mStream->StopTrack(mTrackID);
} }
already_AddRefed<Promise>
MediaStreamTrack::ApplyConstraints(const MediaTrackConstraints& aConstraints,
ErrorResult &aRv)
{
return mStream->ApplyConstraintsToTrack(mTrackID, aConstraints, aRv);
}
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla

View File

@ -9,6 +9,7 @@
#include "mozilla/DOMEventTargetHelper.h" #include "mozilla/DOMEventTargetHelper.h"
#include "nsID.h" #include "nsID.h"
#include "StreamBuffer.h" #include "StreamBuffer.h"
#include "MediaTrackConstraints.h"
namespace mozilla { namespace mozilla {
@ -49,6 +50,8 @@ public:
bool Enabled() { return mEnabled; } bool Enabled() { return mEnabled; }
void SetEnabled(bool aEnabled); void SetEnabled(bool aEnabled);
void Stop(); void Stop();
already_AddRefed<Promise>
ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv);
// Notifications from the MediaStreamGraph // Notifications from the MediaStreamGraph
void NotifyEnded() { mEnded = true; } void NotifyEnded() { mEnded = true; }

View File

@ -63,7 +63,7 @@ class Pledge : public PledgeBase
}; };
public: public:
explicit Pledge() : mDone(false), mError(nullptr) {} explicit Pledge() : mDone(false), mRejected(false) {}
Pledge(const Pledge& aOther) = delete; Pledge(const Pledge& aOther) = delete;
Pledge& operator = (const Pledge&) = delete; Pledge& operator = (const Pledge&) = delete;
@ -97,10 +97,10 @@ public:
mFunctors = new Functors(aOnSuccess, aOnFailure); mFunctors = new Functors(aOnSuccess, aOnFailure);
if (mDone) { if (mDone) {
if (!mError) { if (!mRejected) {
mFunctors->Succeed(mValue); mFunctors->Succeed(mValue);
} else { } else {
mFunctors->Fail(*mError); mFunctors->Fail(mError);
} }
} }
} }
@ -110,22 +110,11 @@ public:
mValue = aValue; mValue = aValue;
Resolve(); Resolve();
} }
protected:
void Resolve()
{
if (!mDone) {
mDone = true;
MOZ_ASSERT(!mError);
if (mFunctors) {
mFunctors->Succeed(mValue);
}
}
}
void Reject(ErrorType rv) void Reject(ErrorType rv)
{ {
if (!mDone) { if (!mDone) {
mDone = true; mDone = mRejected = true;
mError = rv; mError = rv;
if (mFunctors) { if (mFunctors) {
mFunctors->Fail(mError); mFunctors->Fail(mError);
@ -133,104 +122,24 @@ protected:
} }
} }
ValueType mValue;
private:
~Pledge() {};
bool mDone;
nsRefPtr<ErrorType> mError;
ScopedDeletePtr<FunctorsBase> mFunctors;
};
template<typename ValueType>
class Pledge<ValueType, nsresult> : public PledgeBase
{
// TODO: Remove workaround once mozilla allows std::function from <functional>
// wo/std::function support, do template + virtual trick to accept lambdas
class FunctorsBase
{
public:
FunctorsBase() {}
virtual void Succeed(ValueType& result) = 0;
virtual void Fail(nsresult error) = 0;
virtual ~FunctorsBase() {};
};
public:
explicit Pledge() : mDone(false), mError(NS_OK) {}
Pledge(const Pledge& aOther) = delete;
Pledge& operator = (const Pledge&) = delete;
template<typename OnSuccessType>
void Then(OnSuccessType aOnSuccess)
{
Then(aOnSuccess, [](nsresult){});
}
template<typename OnSuccessType, typename OnFailureType>
void Then(OnSuccessType aOnSuccess, OnFailureType aOnFailure)
{
class Functors : public FunctorsBase
{
public:
Functors(OnSuccessType& aOnSuccess, OnFailureType& aOnFailure)
: mOnSuccess(aOnSuccess), mOnFailure(aOnFailure) {}
void Succeed(ValueType& result)
{
mOnSuccess(result);
}
void Fail(nsresult rv)
{
mOnFailure(rv);
};
OnSuccessType mOnSuccess;
OnFailureType mOnFailure;
};
mFunctors = new Functors(aOnSuccess, aOnFailure);
if (mDone) {
if (mError == NS_OK) {
mFunctors->Succeed(mValue);
} else {
mFunctors->Fail(mError);
}
}
}
void Resolve(const ValueType& aValue)
{
mValue = aValue;
Resolve();
}
protected: protected:
void Resolve() void Resolve()
{ {
if (!mDone) { if (!mDone) {
mDone = true; mDone = true;
MOZ_ASSERT(mError == NS_OK); MOZ_ASSERT(!mRejected);
if (mFunctors) { if (mFunctors) {
mFunctors->Succeed(mValue); mFunctors->Succeed(mValue);
} }
} }
} }
void Reject(nsresult error)
{
if (!mDone) {
mDone = true;
mError = error;
if (mFunctors) {
mFunctors->Fail(mError);
}
}
}
ValueType mValue; ValueType mValue;
private: private:
~Pledge() {}; ~Pledge() {};
bool mDone; bool mDone;
nsresult mError; bool mRejected;
ErrorType mError;
ScopedDeletePtr<FunctorsBase> mFunctors; ScopedDeletePtr<FunctorsBase> mFunctors;
}; };

View File

@ -234,7 +234,6 @@ function setupEnvironment() {
window.finish = () => SimpleTest.finish(); window.finish = () => SimpleTest.finish();
SpecialPowers.pushPrefEnv({ SpecialPowers.pushPrefEnv({
'set': [ 'set': [
['canvas.capturestream.enabled', true],
['media.peerconnection.enabled', true], ['media.peerconnection.enabled', true],
['media.peerconnection.identity.enabled', true], ['media.peerconnection.identity.enabled', true],
['media.peerconnection.identity.timeout', 120000], ['media.peerconnection.identity.timeout', 120000],

View File

@ -16,9 +16,13 @@ function mustSucceed(msg, f) {
e => is(e.name, null, msg + " must succeed: " + e.message)); e => is(e.name, null, msg + " must succeed: " + e.message));
} }
function mustFailWith(msg, reason, f) { function mustFailWith(msg, reason, constraint, f) {
return f().then(() => ok(false, msg + " must fail"), return f().then(() => ok(false, msg + " must fail"), e => {
e => is(e.name, reason, msg + " must fail: " + e.message)); is(e.name, reason, msg + " must fail: " + e.message);
if (constraint) {
is(e.constraint, constraint, msg + " must fail w/correct constraint.");
}
});
} }
var pushPrefs = (...p) => new Promise(r => SpecialPowers.pushPrefEnv({set: p}, r)); var pushPrefs = (...p) => new Promise(r => SpecialPowers.pushPrefEnv({set: p}, r));
@ -49,13 +53,15 @@ runTest(() =>
audio: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" }, audio: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
fake: true, fake: true,
}))) })))
.then(() => mustFailWith("unknown exact deviceId on video", "NotFoundError", .then(() => mustFailWith("unknown exact deviceId on video",
() => navigator.mediaDevices.getUserMedia({ "OverconstrainedError", "deviceId",
() => navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } }, video: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
fake: true, fake: true,
}))) })))
.then(() => mustFailWith("unknown exact deviceId on audio", "NotFoundError", .then(() => mustFailWith("unknown exact deviceId on audio",
() => navigator.mediaDevices.getUserMedia({ "OverconstrainedError", "deviceId",
() => navigator.mediaDevices.getUserMedia({
audio: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } }, audio: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
fake: true, fake: true,
}))) })))

View File

@ -39,9 +39,14 @@ var tests = [
{ message: "browser screensharing requires permission", { message: "browser screensharing requires permission",
constraints: { video: { mediaSource: 'browser' } }, constraints: { video: { mediaSource: 'browser' } },
error: "PermissionDeniedError" }, error: "PermissionDeniedError" },
{ message: "unknown mediaSource fails", { message: "unknown mediaSource in video fails",
constraints: { video: { mediaSource: 'uncle' } }, constraints: { video: { mediaSource: 'uncle' } },
error: "NotFoundError" }, error: "OverconstrainedError",
constraint: "mediaSource" },
{ message: "unknown mediaSource in audio fails",
constraints: { audio: { mediaSource: 'uncle' } },
error: "OverconstrainedError",
constraint: "mediaSource" },
{ message: "emtpy constraint fails", { message: "emtpy constraint fails",
constraints: { }, constraints: { },
error: "NotSupportedError" }, error: "NotSupportedError" },
@ -106,8 +111,18 @@ runTest(function() {
return tests.reduce((p, test) => return tests.reduce((p, test) =>
p.then(() => navigator.mediaDevices.getUserMedia(test.constraints)) p.then(() => navigator.mediaDevices.getUserMedia(test.constraints))
.then(() => is(null, test.error, test.message), .then(() => is(null, test.error, test.message), e => {
e => is(e.name, test.error, test.message + ": " + e.message)), p); is(e.name, test.error, test.message + ": " + e.message);
if (test.constraint) {
is(e.constraint, test.constraint,
test.message + " w/correct constraint.");
}
}), p)
.then(() => navigator.mediaDevices.getUserMedia({video: true, audio: true}))
.then(stream => stream.getVideoTracks()[0].applyConstraints({ width: 320 })
.then(() => stream.getAudioTracks()[0].applyConstraints({ })))
.then(() => ok(true, "applyConstraints code exercised"))
// TODO: Test outcome once fake devices support constraints (Bug 1088621)
}); });
</script> </script>

View File

@ -111,6 +111,11 @@ public:
/* Stop the device and release the corresponding MediaStream */ /* Stop the device and release the corresponding MediaStream */
virtual nsresult Stop(SourceMediaStream *aSource, TrackID aID) = 0; virtual nsresult Stop(SourceMediaStream *aSource, TrackID aID) = 0;
/* Restart with new capability */
virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId) = 0;
/* Change device configuration. */ /* Change device configuration. */
virtual nsresult Config(bool aEchoOn, uint32_t aEcho, virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
bool aAgcOn, uint32_t aAGC, bool aAgcOn, uint32_t aAGC,

View File

@ -201,6 +201,14 @@ MediaEngineDefaultVideoSource::Stop(SourceMediaStream *aSource, TrackID aID)
return NS_OK; return NS_OK;
} }
nsresult
MediaEngineDefaultVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId)
{
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
MediaEngineDefaultVideoSource::Notify(nsITimer* aTimer) MediaEngineDefaultVideoSource::Notify(nsITimer* aTimer)
{ {
@ -470,6 +478,14 @@ MediaEngineDefaultAudioSource::Stop(SourceMediaStream *aSource, TrackID aID)
return NS_OK; return NS_OK;
} }
nsresult
MediaEngineDefaultAudioSource::Restart(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId)
{
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
MediaEngineDefaultAudioSource::Notify(nsITimer* aTimer) MediaEngineDefaultAudioSource::Notify(nsITimer* aTimer)
{ {

View File

@ -49,6 +49,9 @@ public:
virtual nsresult Deallocate() override; virtual nsresult Deallocate() override;
virtual nsresult Start(SourceMediaStream*, TrackID) override; virtual nsresult Start(SourceMediaStream*, TrackID) override;
virtual nsresult Stop(SourceMediaStream*, TrackID) override; virtual nsresult Stop(SourceMediaStream*, TrackID) override;
virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId) override;
virtual void SetDirectListeners(bool aHasDirectListeners) override {}; virtual void SetDirectListeners(bool aHasDirectListeners) override {};
virtual nsresult Config(bool aEchoOn, uint32_t aEcho, virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
bool aAgcOn, uint32_t aAGC, bool aAgcOn, uint32_t aAGC,
@ -119,6 +122,9 @@ public:
virtual nsresult Deallocate() override; virtual nsresult Deallocate() override;
virtual nsresult Start(SourceMediaStream*, TrackID) override; virtual nsresult Start(SourceMediaStream*, TrackID) override;
virtual nsresult Stop(SourceMediaStream*, TrackID) override; virtual nsresult Stop(SourceMediaStream*, TrackID) override;
virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId) override;
virtual void SetDirectListeners(bool aHasDirectListeners) override {}; virtual void SetDirectListeners(bool aHasDirectListeners) override {};
virtual nsresult Config(bool aEchoOn, uint32_t aEcho, virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
bool aAgcOn, uint32_t aAGC, bool aAgcOn, uint32_t aAGC,

View File

@ -328,6 +328,14 @@ MediaEngineGonkVideoSource::Stop(SourceMediaStream* aSource, TrackID aID)
return NS_OK; return NS_OK;
} }
nsresult
MediaEngineGonkVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId)
{
return NS_OK;
}
/** /**
* Initialization and Shutdown functions for the video source, called by the * Initialization and Shutdown functions for the video source, called by the
* constructor and destructor respectively. * constructor and destructor respectively.

View File

@ -66,6 +66,9 @@ public:
virtual nsresult Deallocate() override; virtual nsresult Deallocate() override;
virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) override; virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) override;
virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) override; virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;
virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId) override;
virtual void NotifyPull(MediaStreamGraph* aGraph, virtual void NotifyPull(MediaStreamGraph* aGraph,
SourceMediaStream* aSource, SourceMediaStream* aSource,
TrackID aId, TrackID aId,

View File

@ -212,6 +212,31 @@ MediaEngineRemoteVideoSource::Stop(mozilla::SourceMediaStream* aSource,
return NS_OK; return NS_OK;
} }
nsresult
MediaEngineRemoteVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId)
{
if (!mInitDone) {
LOG(("Init not done"));
return NS_ERROR_FAILURE;
}
if (!ChooseCapability(aConstraints, aPrefs, aDeviceId)) {
return NS_ERROR_NOT_AVAILABLE;
}
if (mState != kStarted) {
return NS_OK;
}
mozilla::camera::StopCapture(mCapEngine, mCaptureIndex);
if (mozilla::camera::StartCapture(mCapEngine,
mCaptureIndex, mCapability, this)) {
LOG(("StartCapture failed"));
return NS_ERROR_FAILURE;
}
return NS_OK;
}
void void
MediaEngineRemoteVideoSource::NotifyPull(MediaStreamGraph* aGraph, MediaEngineRemoteVideoSource::NotifyPull(MediaStreamGraph* aGraph,
SourceMediaStream* aSource, SourceMediaStream* aSource,

View File

@ -71,6 +71,9 @@ public:
virtual nsresult Deallocate() override;; virtual nsresult Deallocate() override;;
virtual nsresult Start(SourceMediaStream*, TrackID) override; virtual nsresult Start(SourceMediaStream*, TrackID) override;
virtual nsresult Stop(SourceMediaStream*, TrackID) override; virtual nsresult Stop(SourceMediaStream*, TrackID) override;
virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId) override;
virtual void NotifyPull(MediaStreamGraph* aGraph, virtual void NotifyPull(MediaStreamGraph* aGraph,
SourceMediaStream* aSource, SourceMediaStream* aSource,
TrackID aId, TrackID aId,

View File

@ -291,6 +291,15 @@ MediaEngineTabVideoSource::Stop(mozilla::SourceMediaStream*, mozilla::TrackID)
return NS_OK; return NS_OK;
} }
nsresult
MediaEngineTabVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
const mozilla::MediaEnginePrefs& aPrefs,
const nsString& aDeviceId)
{
// TODO
return NS_OK;
}
nsresult nsresult
MediaEngineTabVideoSource::Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t) MediaEngineTabVideoSource::Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t)
{ {

View File

@ -29,6 +29,9 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
virtual void SetDirectListeners(bool aHasDirectListeners) override {}; virtual void SetDirectListeners(bool aHasDirectListeners) override {};
virtual void NotifyPull(mozilla::MediaStreamGraph*, mozilla::SourceMediaStream*, mozilla::TrackID, mozilla::StreamTime) override; virtual void NotifyPull(mozilla::MediaStreamGraph*, mozilla::SourceMediaStream*, mozilla::TrackID, mozilla::StreamTime) override;
virtual nsresult Stop(mozilla::SourceMediaStream*, mozilla::TrackID) override; virtual nsresult Stop(mozilla::SourceMediaStream*, mozilla::TrackID) override;
virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
const mozilla::MediaEnginePrefs& aPrefs,
const nsString& aDeviceId) override;
virtual nsresult Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t) override; virtual nsresult Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t) override;
virtual bool IsFake() override; virtual bool IsFake() override;
virtual const dom::MediaSourceEnum GetMediaSource() override { virtual const dom::MediaSourceEnum GetMediaSource() override {

View File

@ -84,6 +84,9 @@ public:
} }
nsresult Start(SourceMediaStream* aMediaStream, TrackID aId) override; nsresult Start(SourceMediaStream* aMediaStream, TrackID aId) override;
nsresult Stop(SourceMediaStream* aMediaStream, TrackID aId) override; nsresult Stop(SourceMediaStream* aMediaStream, TrackID aId) override;
nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId) override;
void SetDirectListeners(bool aDirect) override void SetDirectListeners(bool aDirect) override
{} {}
nsresult Config(bool aEchoOn, uint32_t aEcho, bool aAgcOn, nsresult Config(bool aEchoOn, uint32_t aEcho, bool aAgcOn,
@ -155,6 +158,9 @@ public:
virtual nsresult Deallocate() override; virtual nsresult Deallocate() override;
virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) override; virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) override;
virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) override; virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;
virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId) override;
virtual void SetDirectListeners(bool aHasDirectListeners) override {}; virtual void SetDirectListeners(bool aHasDirectListeners) override {};
virtual nsresult Config(bool aEchoOn, uint32_t aEcho, virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
bool aAgcOn, uint32_t aAGC, bool aAgcOn, uint32_t aAGC,

View File

@ -422,6 +422,14 @@ MediaEngineWebRTCMicrophoneSource::Stop(SourceMediaStream *aSource, TrackID aID)
return NS_OK; return NS_OK;
} }
nsresult
MediaEngineWebRTCMicrophoneSource::Restart(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId)
{
return NS_OK;
}
void void
MediaEngineWebRTCMicrophoneSource::NotifyPull(MediaStreamGraph *aGraph, MediaEngineWebRTCMicrophoneSource::NotifyPull(MediaStreamGraph *aGraph,
SourceMediaStream *aSource, SourceMediaStream *aSource,
@ -664,6 +672,15 @@ MediaEngineWebRTCAudioCaptureSource::Stop(SourceMediaStream *aMediaStream,
return NS_OK; return NS_OK;
} }
nsresult
MediaEngineWebRTCAudioCaptureSource::Restart(
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId)
{
return NS_OK;
}
uint32_t uint32_t
MediaEngineWebRTCAudioCaptureSource::GetBestFitnessDistance( MediaEngineWebRTCAudioCaptureSource::GetBestFitnessDistance(
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets, const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,

View File

@ -12,6 +12,8 @@
#include "mozilla/dom/MediaTrackConstraintSetBinding.h" #include "mozilla/dom/MediaTrackConstraintSetBinding.h"
#include "mozilla/dom/MediaTrackSupportedConstraintsBinding.h" #include "mozilla/dom/MediaTrackSupportedConstraintsBinding.h"
#include <map>
namespace mozilla { namespace mozilla {
template<class EnumValuesStrings, class Enum> template<class EnumValuesStrings, class Enum>
@ -104,6 +106,128 @@ protected:
GetMinimumFitnessDistance(const dom::MediaTrackConstraintSet &aConstraints, GetMinimumFitnessDistance(const dom::MediaTrackConstraintSet &aConstraints,
bool aAdvanced, bool aAdvanced,
const nsString& aDeviceId); const nsString& aDeviceId);
template<class DeviceType>
static bool
AreUnfitSettings(const dom::MediaTrackConstraints &aConstraints,
nsTArray<nsRefPtr<DeviceType>>& aSources)
{
nsTArray<const dom::MediaTrackConstraintSet*> aggregateConstraints;
aggregateConstraints.AppendElement(&aConstraints);
for (auto& source : aSources) {
if (source->GetBestFitnessDistance(aggregateConstraints) != UINT32_MAX) {
return false;
}
}
return true;
}
public:
// Apply constrains to a supplied list of sources (removes items from the list)
template<class DeviceType>
static const char*
SelectSettings(const dom::MediaTrackConstraints &aConstraints,
nsTArray<nsRefPtr<DeviceType>>& aSources)
{
auto& c = aConstraints;
// First apply top-level constraints.
// Stack constraintSets that pass, starting with the required one, because the
// whole stack must be re-satisfied each time a capability-set is ruled out
// (this avoids storing state or pushing algorithm into the lower-level code).
nsTArray<nsRefPtr<DeviceType>> unsatisfactory;
nsTArray<const dom::MediaTrackConstraintSet*> aggregateConstraints;
aggregateConstraints.AppendElement(&c);
std::multimap<uint32_t, nsRefPtr<DeviceType>> ordered;
for (uint32_t i = 0; i < aSources.Length();) {
uint32_t distance = aSources[i]->GetBestFitnessDistance(aggregateConstraints);
if (distance == UINT32_MAX) {
unsatisfactory.AppendElement(aSources[i]);
aSources.RemoveElementAt(i);
} else {
ordered.insert(std::pair<uint32_t, nsRefPtr<DeviceType>>(distance,
aSources[i]));
++i;
}
}
if (!aSources.Length()) {
// None selected. The spec says to report a constraint that satisfies NONE
// of the sources. Unfortunately, this is a bit laborious to find out, and
// requires updating as new constraints are added!
if (c.mDeviceId.IsConstrainDOMStringParameters()) {
dom::MediaTrackConstraints fresh;
fresh.mDeviceId = c.mDeviceId;
if (AreUnfitSettings(fresh, unsatisfactory)) {
return "deviceId";
}
}
if (c.mWidth.IsConstrainLongRange()) {
dom::MediaTrackConstraints fresh;
fresh.mWidth = c.mWidth;
if (AreUnfitSettings(fresh, unsatisfactory)) {
return "width";
}
}
if (c.mHeight.IsConstrainLongRange()) {
dom::MediaTrackConstraints fresh;
fresh.mHeight = c.mHeight;
if (AreUnfitSettings(fresh, unsatisfactory)) {
return "height";
}
}
if (c.mFrameRate.IsConstrainDoubleRange()) {
dom::MediaTrackConstraints fresh;
fresh.mFrameRate = c.mFrameRate;
if (AreUnfitSettings(fresh, unsatisfactory)) {
return "frameRate";
}
}
if (c.mFacingMode.IsConstrainDOMStringParameters()) {
dom::MediaTrackConstraints fresh;
fresh.mFacingMode = c.mFacingMode;
if (AreUnfitSettings(fresh, unsatisfactory)) {
return "facingMode";
}
}
return "";
}
// Order devices by shortest distance
for (auto& ordinal : ordered) {
aSources.RemoveElement(ordinal.second);
aSources.AppendElement(ordinal.second);
}
// Then apply advanced constraints.
if (c.mAdvanced.WasPassed()) {
auto &array = c.mAdvanced.Value();
for (int i = 0; i < int(array.Length()); i++) {
aggregateConstraints.AppendElement(&array[i]);
nsTArray<nsRefPtr<DeviceType>> rejects;
for (uint32_t j = 0; j < aSources.Length();) {
if (aSources[j]->GetBestFitnessDistance(aggregateConstraints) == UINT32_MAX) {
rejects.AppendElement(aSources[j]);
aSources.RemoveElementAt(j);
} else {
++j;
}
}
if (!aSources.Length()) {
aSources.AppendElements(Move(rejects));
aggregateConstraints.RemoveElementAt(aggregateConstraints.Length() - 1);
}
}
}
return nullptr;
}
}; };
} // namespace mozilla } // namespace mozilla

View File

@ -24,8 +24,6 @@ parent:
child: child:
CallbackAccept(PTCPSocket socket); CallbackAccept(PTCPSocket socket);
CallbackError(nsString message, nsString filename,
uint32_t lineNumber, uint32_t columnNumber);
__delete__(); __delete__();
}; };

View File

@ -13,11 +13,12 @@ using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
struct TCPError { struct TCPError {
nsString name; nsString name;
nsString message;
}; };
union SendableData { union SendableData {
uint8_t[]; uint8_t[];
nsString; nsCString;
}; };
union CallbackData { union CallbackData {
@ -38,7 +39,13 @@ parent:
// Forward calling to child's open() method to parent, expect TCPOptions // Forward calling to child's open() method to parent, expect TCPOptions
// is expanded to |useSSL| (from TCPOptions.useSecureTransport) and // is expanded to |useSSL| (from TCPOptions.useSecureTransport) and
// |binaryType| (from TCPOption.binaryType). // |binaryType| (from TCPOption.binaryType).
Open(nsString host, uint16_t port, bool useSSL, nsString binaryType); Open(nsString host, uint16_t port, bool useSSL, bool useArrayBuffers);
// Ask parent to open a socket and bind the newly-opened socket to a local
// address specified in |localAddr| and |localPort|.
OpenBind(nsCString host, uint16_t port,
nsCString localAddr, uint16_t localPort,
bool useSSL, bool aUseArrayBuffers);
// When child's send() is called, this message requrests parent to send // When child's send() is called, this message requrests parent to send
// data and update it's trackingNumber. // data and update it's trackingNumber.
@ -58,7 +65,7 @@ parent:
child: child:
// Forward events that are dispatched by parent. // Forward events that are dispatched by parent.
Callback(nsString type, CallbackData data, nsString readyState); Callback(nsString type, CallbackData data, uint32_t readyState);
// Update child's bufferedAmount when parent's bufferedAmount is updated. // Update child's bufferedAmount when parent's bufferedAmount is updated.
// trackingNumber is also passed back to child to ensure the bufferedAmount // trackingNumber is also passed back to child to ensure the bufferedAmount

View File

@ -0,0 +1,196 @@
/* -*- Mode: C++; 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/. */
#include "mozilla/dom/TCPServerSocketBinding.h"
#include "mozilla/dom/TCPServerSocketEvent.h"
#include "mozilla/dom/TCPSocketBinding.h"
#include "TCPServerSocketParent.h"
#include "TCPServerSocketChild.h"
#include "mozilla/dom/Event.h"
#include "mozilla/ErrorResult.h"
#include "TCPServerSocket.h"
#include "TCPSocket.h"
using namespace mozilla::dom;
NS_IMPL_CYCLE_COLLECTION_CLASS(TCPServerSocket)
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(TCPServerSocket,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TCPServerSocket,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerSocket)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerBridgeChild)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerBridgeParent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TCPServerSocket,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerSocket)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerBridgeChild)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerBridgeParent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_ADDREF_INHERITED(TCPServerSocket, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(TCPServerSocket, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TCPServerSocket)
NS_INTERFACE_MAP_ENTRY(nsIServerSocketListener)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
TCPServerSocket::TCPServerSocket(nsIGlobalObject* aGlobal, uint16_t aPort,
bool aUseArrayBuffers, uint16_t aBacklog)
: DOMEventTargetHelper(aGlobal)
, mPort(aPort)
, mBacklog(aBacklog)
, mUseArrayBuffers(aUseArrayBuffers)
{
}
TCPServerSocket::~TCPServerSocket()
{
}
nsresult
TCPServerSocket::Init()
{
if (mServerSocket || mServerBridgeChild) {
NS_WARNING("Child TCPServerSocket is already listening.");
return NS_ERROR_FAILURE;
}
if (XRE_GetProcessType() == GeckoProcessType_Content) {
mServerBridgeChild = new TCPServerSocketChild(this, mPort, mBacklog, mUseArrayBuffers);
return NS_OK;
}
nsresult rv;
mServerSocket = do_CreateInstance("@mozilla.org/network/server-socket;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mServerSocket->Init(mPort, false, mBacklog);
NS_ENSURE_SUCCESS(rv, rv);
rv = mServerSocket->GetPort(&mPort);
NS_ENSURE_SUCCESS(rv, rv);
rv = mServerSocket->AsyncListen(this);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
already_AddRefed<TCPServerSocket>
TCPServerSocket::Constructor(const GlobalObject& aGlobal,
uint16_t aPort,
const ServerSocketOptions& aOptions,
uint16_t aBacklog,
mozilla::ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
if (!global) {
aRv = NS_ERROR_FAILURE;
return nullptr;
}
bool useArrayBuffers = aOptions.mBinaryType == TCPSocketBinaryType::Arraybuffer;
nsRefPtr<TCPServerSocket> socket = new TCPServerSocket(global, aPort, useArrayBuffers, aBacklog);
nsresult rv = socket->Init();
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv = NS_ERROR_FAILURE;
return nullptr;
}
return socket.forget();
}
uint16_t
TCPServerSocket::LocalPort()
{
return mPort;
}
void
TCPServerSocket::Close()
{
if (mServerBridgeChild) {
mServerBridgeChild->Close();
}
if (mServerSocket) {
mServerSocket->Close();
}
}
void
TCPServerSocket::FireEvent(const nsAString& aType, TCPSocket* aSocket)
{
AutoJSAPI api;
api.Init(GetOwner());
TCPServerSocketEventInit init;
init.mBubbles = false;
init.mCancelable = false;
init.mSocket = aSocket;
nsRefPtr<TCPServerSocketEvent> event =
TCPServerSocketEvent::Constructor(this, aType, init);
event->SetTrusted(true);
bool dummy;
DispatchEvent(event, &dummy);
if (mServerBridgeParent) {
mServerBridgeParent->OnConnect(event);
}
}
NS_IMETHODIMP
TCPServerSocket::OnSocketAccepted(nsIServerSocket* aServer, nsISocketTransport* aTransport)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
nsRefPtr<TCPSocket> socket = TCPSocket::CreateAcceptedSocket(global, aTransport, mUseArrayBuffers);
if (mServerBridgeParent) {
socket->SetAppIdAndBrowser(mServerBridgeParent->GetAppId(),
mServerBridgeParent->GetInBrowser());
}
FireEvent(NS_LITERAL_STRING("connect"), socket);
return NS_OK;
}
NS_IMETHODIMP
TCPServerSocket::OnStopListening(nsIServerSocket* aServer, nsresult aStatus)
{
if (aStatus != NS_BINDING_ABORTED) {
nsRefPtr<Event> event = new Event(GetOwner());
nsresult rv = event->InitEvent(NS_LITERAL_STRING("error"), false, false);
NS_ENSURE_SUCCESS(rv, rv);
event->SetTrusted(true);
bool dummy;
DispatchEvent(event, &dummy);
NS_WARNING("Server socket was closed by unexpected reason.");
return NS_ERROR_FAILURE;
}
mServerSocket = nullptr;
return NS_OK;
}
nsresult
TCPServerSocket::AcceptChildSocket(TCPSocketChild* aSocketChild)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
nsRefPtr<TCPSocket> socket = TCPSocket::CreateAcceptedSocket(global, aSocketChild, mUseArrayBuffers);
NS_ENSURE_TRUE(socket, NS_ERROR_FAILURE);
FireEvent(NS_LITERAL_STRING("connect"), socket);
return NS_OK;
}
void
TCPServerSocket::SetServerBridgeParent(TCPServerSocketParent* aBridgeParent)
{
mServerBridgeParent = aBridgeParent;
}
JSObject*
TCPServerSocket::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return TCPServerSocketBinding::Wrap(aCx, this, aGivenProto);
}

View File

@ -0,0 +1,83 @@
/* -*- Mode: C++; 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/. */
#ifndef mozilla_dom_TCPServerSocket_h
#define mozilla_dom_TCPServerSocket_h
#include "mozilla/DOMEventTargetHelper.h"
#include "nsIServerSocket.h"
namespace mozilla {
class ErrorResult;
namespace dom {
struct ServerSocketOptions;
class GlobalObject;
class TCPSocket;
class TCPSocketChild;
class TCPServerSocketChild;
class TCPServerSocketParent;
class TCPServerSocket final : public DOMEventTargetHelper
, public nsIServerSocketListener
{
public:
TCPServerSocket(nsIGlobalObject* aGlobal, uint16_t aPort, bool aUseArrayBuffers,
uint16_t aBacklog);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(TCPServerSocket, DOMEventTargetHelper)
NS_DECL_NSISERVERSOCKETLISTENER
nsPIDOMWindow* GetParentObject() const
{
return GetOwner();
}
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
nsresult Init();
uint16_t LocalPort();
void Close();
static already_AddRefed<TCPServerSocket>
Constructor(const GlobalObject& aGlobal,
uint16_t aPort,
const ServerSocketOptions& aOptions,
uint16_t aBacklog,
mozilla::ErrorResult& aRv);
IMPL_EVENT_HANDLER(connect);
IMPL_EVENT_HANDLER(error);
// Relay an accepted socket notification from the parent process and
// initialize this object with an existing child actor for the new socket.
nsresult AcceptChildSocket(TCPSocketChild* aSocketChild);
// Associate this object with an IPC actor in the parent process to relay
// notifications to content processes.
void SetServerBridgeParent(TCPServerSocketParent* aBridgeParent);
private:
~TCPServerSocket();
// Dispatch a TCPServerSocketEvent event of a given type at this object.
void FireEvent(const nsAString& aType, TCPSocket* aSocket);
// The server socket associated with this object.
nsCOMPtr<nsIServerSocket> mServerSocket;
// The IPC actor in the content process.
nsRefPtr<TCPServerSocketChild> mServerBridgeChild;
// The IPC actor in the parent process.
nsRefPtr<TCPServerSocketParent> mServerBridgeParent;
int32_t mPort;
uint16_t mBacklog;
// True if any accepted sockets should use array buffers for received messages.
bool mUseArrayBuffers;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TCPServerSocket_h

View File

@ -1,187 +0,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/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
const CC = Components.Constructor;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const ServerSocket = CC(
'@mozilla.org/network/server-socket;1', 'nsIServerSocket', 'init'),
TCPSocketInternal = Cc[
'@mozilla.org/tcp-socket;1'].createInstance(Ci.nsITCPSocketInternal);
/*
* Debug logging function
*/
var debug = true;
function LOG(msg) {
if (debug) {
dump("TCPServerSocket: " + msg + "\n");
}
}
/*
* nsIDOMTCPServerSocket object
*/
function TCPServerSocket() {
this._localPort = 0;
this._binaryType = null;
this._onconnect = null;
this._onerror = null;
this._inChild = false;
this._neckoTCPServerSocket = null;
this._serverBridge = null;
this.useWin = null;
}
// When this API moves to WebIDL and these __exposedProps__ go away, remove
// this call here and remove the API from XPConnect.
Cu.skipCOWCallableChecks();
TCPServerSocket.prototype = {
__exposedProps__: {
localPort: 'r',
onconnect: 'rw',
onerror: 'rw'
},
get localPort() {
return this._localPort;
},
get onconnect() {
return this._onconnect;
},
set onconnect(f) {
this._onconnect = f;
},
get onerror() {
return this._onerror;
},
set onerror(f) {
this._onerror = f;
},
_callListenerAcceptCommon: function tss_callListenerAcceptCommon(socket) {
if (this._onconnect) {
try {
this["onconnect"].call(null, socket);
} catch (e) {
socket.close();
}
}
else {
socket.close();
dump("Received unexpected connection!");
}
},
init: function tss_init(aWindowObj) {
this.useWin = aWindowObj;
},
/* nsITCPServerSocketInternal method */
listen: function tss_listen(localPort, options, backlog) {
this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
this._binaryType = options.binaryType;
if (this._inChild) {
if (this._serverBridge == null) {
this._serverBridge = Cc["@mozilla.org/tcp-server-socket-child;1"]
.createInstance(Ci.nsITCPServerSocketChild);
this._serverBridge.listen(this, localPort, backlog, options.binaryType);
}
else {
throw new Error("Child TCPServerSocket has already listening. \n");
}
}
else {
if (this._neckoTCPServerSocket == null) {
this._neckoTCPServerSocket = new ServerSocket(localPort, false, backlog);
this._localPort = this._neckoTCPServerSocket.port;
this._neckoTCPServerSocket.asyncListen(this);
}
else {
throw new Error("Parent TCPServerSocket has already listening. \n");
}
}
},
callListenerAccept: function tss_callListenerSocket(socketChild) {
// this method is called at child process when the socket is accepted at parent process.
let socket = TCPSocketInternal.createAcceptedChild(socketChild, this._binaryType, this.useWin);
this._callListenerAcceptCommon(socket);
},
callListenerError: function tss_callListenerError(message, filename, lineNumber, columnNumber) {
if (this._onerror) {
var type = "error";
var error = new Error(message, filename, lineNumber, columnNumber);
this["onerror"].call(null, new TCPSocketEvent(type, this, error));
}
},
/* end nsITCPServerSocketInternal method */
close: function tss_close() {
if (this._inChild) {
this._serverBridge.close();
return;
}
/* Close ServerSocket */
if (this._neckoTCPServerSocket) {
this._neckoTCPServerSocket.close();
}
},
// nsIServerSocketListener (Triggered by _neckoTCPServerSocket.asyncListen)
onSocketAccepted: function tss_onSocketAccepted(server, trans) {
// precondition: this._inChild == false
try {
let that = TCPSocketInternal.createAcceptedParent(trans, this._binaryType,
this.useWin);
this._callListenerAcceptCommon(that);
}
catch(e) {
trans.close(Cr.NS_BINDING_ABORTED);
}
},
// nsIServerSocketListener (Triggered by _neckoTCPServerSocket.asyncListen)
onStopListening: function tss_onStopListening(server, status) {
if (status != Cr.NS_BINDING_ABORTED) {
throw new Error("Server socket was closed by unexpected reason.");
}
this._neckoTCPServerSocket = null;
},
classID: Components.ID("{73065eae-27dc-11e2-895a-000c29987aa2}"),
classInfo: XPCOMUtils.generateCI({
classID: Components.ID("{73065eae-27dc-11e2-895a-000c29987aa2}"),
classDescription: "Server TCP Socket",
interfaces: [
Ci.nsIDOMTCPServerSocket,
Ci.nsISupportsWeakReference
],
flags: Ci.nsIClassInfo.DOM_OBJECT,
}),
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIDOMTCPServerSocket,
Ci.nsITCPServerSocketInternal,
Ci.nsISupportsWeakReference
])
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPServerSocket]);

View File

@ -6,10 +6,10 @@
#include "TCPServerSocketChild.h" #include "TCPServerSocketChild.h"
#include "TCPSocketChild.h" #include "TCPSocketChild.h"
#include "TCPServerSocket.h"
#include "mozilla/net/NeckoChild.h" #include "mozilla/net/NeckoChild.h"
#include "mozilla/dom/PBrowserChild.h" #include "mozilla/dom/PBrowserChild.h"
#include "mozilla/dom/TabChild.h" #include "mozilla/dom/TabChild.h"
#include "nsIDOMTCPSocket.h"
#include "nsJSUtils.h" #include "nsJSUtils.h"
#include "jsfriendapi.h" #include "jsfriendapi.h"
@ -23,7 +23,6 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPServerSocketChildBase)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPServerSocketChildBase) NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPServerSocketChildBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPServerSocketChildBase) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPServerSocketChildBase)
NS_INTERFACE_MAP_ENTRY(nsITCPServerSocketChild)
NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END NS_INTERFACE_MAP_END
@ -46,18 +45,12 @@ NS_IMETHODIMP_(MozExternalRefCountType) TCPServerSocketChild::Release(void)
return refcnt; return refcnt;
} }
TCPServerSocketChild::TCPServerSocketChild() TCPServerSocketChild::TCPServerSocketChild(TCPServerSocket* aServerSocket, uint16_t aLocalPort,
{ uint16_t aBacklog, bool aUseArrayBuffers)
}
NS_IMETHODIMP
TCPServerSocketChild::Listen(nsITCPServerSocketInternal* aServerSocket, uint16_t aLocalPort,
uint16_t aBacklog, const nsAString & aBinaryType, JSContext* aCx)
{ {
mServerSocket = aServerSocket; mServerSocket = aServerSocket;
AddIPDLReference(); AddIPDLReference();
gNeckoChild->SendPTCPServerSocketConstructor(this, aLocalPort, aBacklog, nsString(aBinaryType)); gNeckoChild->SendPTCPServerSocketConstructor(this, aLocalPort, aBacklog, aUseArrayBuffers);
return NS_OK;
} }
void void
@ -83,34 +76,16 @@ TCPServerSocketChild::~TCPServerSocketChild()
bool bool
TCPServerSocketChild::RecvCallbackAccept(PTCPSocketChild *psocket) TCPServerSocketChild::RecvCallbackAccept(PTCPSocketChild *psocket)
{ {
TCPSocketChild* socket = static_cast<TCPSocketChild*>(psocket); nsRefPtr<TCPSocketChild> socket = static_cast<TCPSocketChild*>(psocket);
nsresult rv = mServerSocket->AcceptChildSocket(socket);
nsresult rv = mServerSocket->CallListenerAccept(static_cast<nsITCPSocketChild*>(socket)); NS_ENSURE_SUCCESS(rv, true);
if (NS_FAILED(rv)) {
NS_WARNING("CallListenerAccept threw exception.");
}
return true; return true;
} }
bool void
TCPServerSocketChild::RecvCallbackError(const nsString& aMessage,
const nsString& aFilename,
const uint32_t& aLineNumber,
const uint32_t& aColumnNumber)
{
nsresult rv = mServerSocket->CallListenerError(aMessage, aFilename,
aLineNumber, aColumnNumber);
if (NS_FAILED(rv)) {
NS_WARNING("CallListenerError threw exception.");
}
return true;
}
NS_IMETHODIMP
TCPServerSocketChild::Close() TCPServerSocketChild::Close()
{ {
SendClose(); SendClose();
return NS_OK;
} }
} // namespace dom } // namespace dom

View File

@ -4,8 +4,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file, * 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/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_TCPServerSocketChild_h
#define mozilla_dom_TCPServerSocketChild_h
#include "mozilla/net/PTCPServerSocketChild.h" #include "mozilla/net/PTCPServerSocketChild.h"
#include "nsITCPServerSocketChild.h"
#include "nsCycleCollectionParticipant.h" #include "nsCycleCollectionParticipant.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
@ -17,7 +19,9 @@ class nsITCPServerSocketInternal;
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
class TCPServerSocketChildBase : public nsITCPServerSocketChild { class TCPServerSocket;
class TCPServerSocketChildBase : public nsISupports {
public: public:
NS_DECL_CYCLE_COLLECTION_CLASS(TCPServerSocketChildBase) NS_DECL_CYCLE_COLLECTION_CLASS(TCPServerSocketChildBase)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@ -29,7 +33,7 @@ protected:
TCPServerSocketChildBase(); TCPServerSocketChildBase();
virtual ~TCPServerSocketChildBase(); virtual ~TCPServerSocketChildBase();
nsCOMPtr<nsITCPServerSocketInternal> mServerSocket; nsRefPtr<TCPServerSocket> mServerSocket;
bool mIPCOpen; bool mIPCOpen;
}; };
@ -37,18 +41,18 @@ class TCPServerSocketChild : public mozilla::net::PTCPServerSocketChild
, public TCPServerSocketChildBase , public TCPServerSocketChildBase
{ {
public: public:
NS_DECL_NSITCPSERVERSOCKETCHILD
NS_IMETHOD_(MozExternalRefCountType) Release() override; NS_IMETHOD_(MozExternalRefCountType) Release() override;
TCPServerSocketChild(); TCPServerSocketChild(TCPServerSocket* aServerSocket, uint16_t aLocalPort,
uint16_t aBacklog, bool aUseArrayBuffers);
~TCPServerSocketChild(); ~TCPServerSocketChild();
void Close();
virtual bool RecvCallbackAccept(PTCPSocketChild *socket) override; virtual bool RecvCallbackAccept(PTCPSocketChild *socket) override;
virtual bool RecvCallbackError(const nsString& aMessage,
const nsString& aFilename,
const uint32_t& aLineNumber,
const uint32_t& aColumnNumber) override;
}; };
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla
#endif // mozilla_dom_TCPServerSocketChild_h

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