mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central to b2g-inbound
This commit is contained in:
commit
64ebf55ec9
@ -2058,7 +2058,7 @@ DocAccessible::ValidateARIAOwned()
|
||||
for (uint32_t idx = 0; idx < childEls->Length(); idx++) {
|
||||
nsIContent* childEl = childEls->ElementAt(idx);
|
||||
Accessible* child = GetAccessible(childEl);
|
||||
if (child && !child->GetFrame()) {
|
||||
if (child && child->IsInDocument() && !child->GetFrame()) {
|
||||
UpdateTreeOnRemoval(child->Parent(), childEl);
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,6 @@ pref("browser.cache.memory_limit", 2048); // 2 MB
|
||||
|
||||
/* image cache prefs */
|
||||
pref("image.cache.size", 1048576); // bytes
|
||||
pref("image.high_quality_downscaling.enabled", false);
|
||||
pref("canvas.image.cache.limit", 20971520); // 20 MB
|
||||
|
||||
/* offline cache prefs */
|
||||
|
@ -679,11 +679,6 @@
|
||||
@RESPATH@/components/ActivityWrapper.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/PaymentFlowInfo.js
|
||||
@RESPATH@/components/PaymentProvider.js
|
||||
|
@ -1693,6 +1693,7 @@ pref("image.mem.max_decoded_image_kb", 256000);
|
||||
pref("loop.enabled", true);
|
||||
pref("loop.textChat.enabled", true);
|
||||
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.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
|
||||
pref("loop.gettingStarted.resumeOnFirstJoin", false);
|
||||
|
@ -214,10 +214,6 @@ var gFxAccounts = {
|
||||
this.showDoorhanger("sync-start-panel");
|
||||
},
|
||||
|
||||
showSyncFailedDoorhanger: function () {
|
||||
this.showDoorhanger("sync-error-panel");
|
||||
},
|
||||
|
||||
updateUI: function () {
|
||||
this.updateAppMenuItem();
|
||||
this.updateMigrationNotification();
|
||||
|
@ -449,28 +449,6 @@
|
||||
</hbox>
|
||||
</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 -->
|
||||
<tooltip id="bhTooltip"/>
|
||||
|
||||
|
@ -50,6 +50,7 @@
|
||||
"SocialShare": false,
|
||||
"Task": false,
|
||||
"UITour": false,
|
||||
"WebChannel": false,
|
||||
"XPCOMUtils": false,
|
||||
"uuidgen": true,
|
||||
// Test Related
|
||||
|
@ -607,89 +607,6 @@ html[dir="rtl"] .room-entry-context-actions > .dropdown-menu {
|
||||
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 {
|
||||
background-color: transparent;
|
||||
background-image: url(../shared/img/icons-10x10.svg#close);
|
||||
|
@ -719,6 +719,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||
mozLoop: this.props.mozLoop,
|
||||
publishStream: this.publishStream,
|
||||
settingsMenuItems: settingsMenuItems,
|
||||
show: true,
|
||||
video: this.props.video})
|
||||
)
|
||||
)
|
||||
|
@ -719,6 +719,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||
mozLoop={this.props.mozLoop}
|
||||
publishStream={this.publishStream}
|
||||
settingsMenuItems={settingsMenuItems}
|
||||
show={true}
|
||||
video={this.props.video} />
|
||||
</sharedViews.MediaLayoutView>
|
||||
</div>
|
||||
|
@ -975,7 +975,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
React.createElement("div", {className: "new-room-view"},
|
||||
React.createElement("div", {className: contextClasses},
|
||||
React.createElement(Checkbox, {checked: this.state.checked,
|
||||
label: mozL10n.get("context_inroom_label"),
|
||||
label: mozL10n.get("context_inroom_label2"),
|
||||
onChange: this.onCheckboxChange}),
|
||||
React.createElement(sharedViews.ContextUrlView, {
|
||||
allowClick: false,
|
||||
|
@ -975,7 +975,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
<div className="new-room-view">
|
||||
<div className={contextClasses}>
|
||||
<Checkbox checked={this.state.checked}
|
||||
label={mozL10n.get("context_inroom_label")}
|
||||
label={mozL10n.get("context_inroom_label2")}
|
||||
onChange={this.onCheckboxChange} />
|
||||
<sharedViews.ContextUrlView
|
||||
allowClick={false}
|
||||
|
@ -449,25 +449,6 @@ loop.roomViews = (function(mozL10n) {
|
||||
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) {
|
||||
event && event.preventDefault();
|
||||
|
||||
@ -516,27 +497,13 @@ loop.roomViews = (function(mozL10n) {
|
||||
|
||||
var cx = React.addons.classSet;
|
||||
var availableContext = this.state.availableContext;
|
||||
// The checkbox shows as checked when there's already context data
|
||||
// attached to this room.
|
||||
var checked = !!urlDescription;
|
||||
var checkboxLabel = urlDescription || (availableContext && availableContext.url ?
|
||||
availableContext.description : "");
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: "room-context"},
|
||||
React.createElement("p", {className: cx({"error": !!this.props.error,
|
||||
"error-display-area": true})},
|
||||
mozL10n.get("rooms_change_failed_label")
|
||||
),
|
||||
React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")),
|
||||
React.createElement(sharedViews.Checkbox, {
|
||||
additionalClass: cx({ hide: !checkboxLabel }),
|
||||
checked: checked,
|
||||
disabled: checked,
|
||||
label: checkboxLabel,
|
||||
onChange: this.handleCheckboxChange,
|
||||
useEllipsis: true,
|
||||
value: location}),
|
||||
React.createElement("h2", {className: "room-context-header"}, mozL10n.get("context_inroom_header")),
|
||||
React.createElement("form", {onSubmit: this.handleFormSubmit},
|
||||
React.createElement("input", {className: "room-context-name",
|
||||
maxLength: this.maxRoomNameLength,
|
||||
@ -554,16 +521,17 @@ loop.roomViews = (function(mozL10n) {
|
||||
onKeyDown: this.handleTextareaKeyDown,
|
||||
placeholder: mozL10n.get("context_edit_comments_placeholder"),
|
||||
rows: "2", type: "text",
|
||||
valueLink: this.linkState("newRoomDescription")})
|
||||
),
|
||||
React.createElement("button", {className: "btn btn-info",
|
||||
disabled: this.props.savingContext,
|
||||
onClick: this.handleFormSubmit},
|
||||
mozL10n.get("context_save_label2")
|
||||
),
|
||||
React.createElement("button", {className: "room-context-btn-close",
|
||||
onClick: this.handleCloseClick,
|
||||
title: mozL10n.get("cancel_button")})
|
||||
valueLink: this.linkState("newRoomDescription")}),
|
||||
React.createElement(sharedViews.ButtonGroup, null,
|
||||
React.createElement(sharedViews.Button, {additionalClass: "button-cancel",
|
||||
caption: mozL10n.get("context_cancel_label"),
|
||||
onClick: this.handleCloseClick}),
|
||||
React.createElement(sharedViews.Button, {additionalClass: "button-accept",
|
||||
caption: mozL10n.get("context_done_label"),
|
||||
disabled: this.props.savingContext,
|
||||
onClick: this.handleFormSubmit})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -819,6 +787,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
publishStream: this.publishStream,
|
||||
screenShare: screenShareData,
|
||||
settingsMenuItems: settingsMenuItems,
|
||||
show: !shouldRenderEditContextView,
|
||||
video: {enabled: !this.state.videoMuted, visible: true}}),
|
||||
React.createElement(DesktopRoomInvitationView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
|
@ -449,25 +449,6 @@ loop.roomViews = (function(mozL10n) {
|
||||
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) {
|
||||
event && event.preventDefault();
|
||||
|
||||
@ -516,27 +497,13 @@ loop.roomViews = (function(mozL10n) {
|
||||
|
||||
var cx = React.addons.classSet;
|
||||
var availableContext = this.state.availableContext;
|
||||
// The checkbox shows as checked when there's already context data
|
||||
// attached to this room.
|
||||
var checked = !!urlDescription;
|
||||
var checkboxLabel = urlDescription || (availableContext && availableContext.url ?
|
||||
availableContext.description : "");
|
||||
|
||||
return (
|
||||
<div className="room-context">
|
||||
<p className={cx({"error": !!this.props.error,
|
||||
"error-display-area": true})}>
|
||||
{mozL10n.get("rooms_change_failed_label")}
|
||||
</p>
|
||||
<div className="room-context-label">{mozL10n.get("context_inroom_label")}</div>
|
||||
<sharedViews.Checkbox
|
||||
additionalClass={cx({ hide: !checkboxLabel })}
|
||||
checked={checked}
|
||||
disabled={checked}
|
||||
label={checkboxLabel}
|
||||
onChange={this.handleCheckboxChange}
|
||||
useEllipsis={true}
|
||||
value={location} />
|
||||
<h2 className="room-context-header">{mozL10n.get("context_inroom_header")}</h2>
|
||||
<form onSubmit={this.handleFormSubmit}>
|
||||
<input className="room-context-name"
|
||||
maxLength={this.maxRoomNameLength}
|
||||
@ -555,15 +522,16 @@ loop.roomViews = (function(mozL10n) {
|
||||
placeholder={mozL10n.get("context_edit_comments_placeholder")}
|
||||
rows="2" type="text"
|
||||
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>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@ -819,6 +787,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
publishStream={this.publishStream}
|
||||
screenShare={screenShareData}
|
||||
settingsMenuItems={settingsMenuItems}
|
||||
show={!shouldRenderEditContextView}
|
||||
video={{enabled: !this.state.videoMuted, visible: true}} />
|
||||
<DesktopRoomInvitationView
|
||||
dispatcher={this.props.dispatcher}
|
||||
|
@ -75,6 +75,87 @@ p {
|
||||
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
|
||||
* denominator of the needed rules. Intended to be used as a base class
|
||||
* together with .btn-*
|
||||
|
@ -915,13 +915,6 @@ html[dir="rtl"] .room-conversation-wrapper header a {
|
||||
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 {
|
||||
padding: 0 0 5rem 0;
|
||||
}
|
||||
@ -999,10 +992,9 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
||||
}
|
||||
|
||||
.room-context {
|
||||
background: rgba(0,0,0,.8);
|
||||
background: #fff;
|
||||
border-top: 2px solid #444;
|
||||
border-bottom: 2px solid #444;
|
||||
padding: .5rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
@ -1049,13 +1041,11 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.room-context-label {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.room-context-label,
|
||||
.room-context > .checkbox-wrapper > label {
|
||||
color: #fff;
|
||||
.room-context-header {
|
||||
color: #333;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
.room-context-comment {
|
||||
@ -1079,71 +1069,40 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
||||
}
|
||||
|
||||
.room-context > form {
|
||||
margin-bottom: 1rem;
|
||||
padding: .5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.room-context > form > textarea,
|
||||
.room-context > form > input[type="text"] {
|
||||
display: block;
|
||||
background: rgba(0,0,0,.5);
|
||||
font-family: "Helvetica Neue", Arial, sans;
|
||||
border: 1px solid rgba(255,255,255,.2);
|
||||
width: 100%;
|
||||
padding: .5em;
|
||||
border-radius: 3px;
|
||||
resize: none;
|
||||
color: #fff;
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #c3c3c3;
|
||||
height: 2.6rem;
|
||||
padding: 6px;
|
||||
font-size: 1.1rem;
|
||||
color: #4a4a4a;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.room-context > form > textarea {
|
||||
font-size: 1em;
|
||||
font-family: inherit;
|
||||
height: 5.2rem;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.room-context > form > input:not([disabled]).room-context-url {
|
||||
color: #0095dd;
|
||||
.room-context > form > textarea::-moz-placeholder,
|
||||
.room-context > form > input::-moz-placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.room-context > form > input[disabled] {
|
||||
background-color: rgba(255,255,255,.2);
|
||||
color: rgba(255,255,255,.4);
|
||||
}
|
||||
|
||||
.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;
|
||||
.room-context > form > textarea:focus,
|
||||
.room-context > form > input:focus {
|
||||
border: 0.1rem solid #5cccee;
|
||||
}
|
||||
|
||||
.media-layout {
|
||||
|
@ -359,6 +359,7 @@ loop.shared.views = (function(_, mozL10n) {
|
||||
publishStream: React.PropTypes.func.isRequired,
|
||||
screenShare: React.PropTypes.object,
|
||||
settingsMenuItems: React.PropTypes.array,
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
video: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
@ -440,6 +441,10 @@ loop.shared.views = (function(_, mozL10n) {
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.props.show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var cx = React.addons.classSet;
|
||||
var conversationToolbarCssClasses = cx({
|
||||
"conversation-toolbar": true,
|
||||
@ -798,7 +803,7 @@ loop.shared.views = (function(_, mozL10n) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return React.createElement("p", null, mozL10n.get("context_inroom_label"));
|
||||
return React.createElement("p", null, mozL10n.get("context_inroom_label2"));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -359,6 +359,7 @@ loop.shared.views = (function(_, mozL10n) {
|
||||
publishStream: React.PropTypes.func.isRequired,
|
||||
screenShare: React.PropTypes.object,
|
||||
settingsMenuItems: React.PropTypes.array,
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
video: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
@ -440,6 +441,10 @@ loop.shared.views = (function(_, mozL10n) {
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.props.show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var cx = React.addons.classSet;
|
||||
var conversationToolbarCssClasses = cx({
|
||||
"conversation-toolbar": true,
|
||||
@ -798,7 +803,7 @@ loop.shared.views = (function(_, mozL10n) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <p>{mozL10n.get("context_inroom_label")}</p>;
|
||||
return <p>{mozL10n.get("context_inroom_label2")}</p>;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -15,6 +15,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
|
||||
"resource://services-common/utils.js");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
|
||||
"resource://gre/modules/WebChannel.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
|
||||
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
|
||||
return new EventEmitter();
|
||||
@ -45,6 +48,9 @@ const MAX_TIME_BEFORE_ENCRYPTION = 30 * 60 * 1000;
|
||||
// Wait time between individual re-encryption cycles (1 second).
|
||||
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) {
|
||||
return LoopRoomsInternal.onNotification(version, channelID);
|
||||
};
|
||||
@ -58,6 +64,8 @@ var gDirty = true;
|
||||
var gCurrentUser = null;
|
||||
// Global variable that keeps track of the room cache.
|
||||
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`.
|
||||
@ -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'.
|
||||
*/
|
||||
@ -905,6 +947,42 @@ var LoopRoomsInternal = {
|
||||
eventEmitter.emit("refresh");
|
||||
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);
|
||||
@ -926,6 +1004,10 @@ Object.freeze(LoopRoomsInternal);
|
||||
* See the internal code for the API documentation.
|
||||
*/
|
||||
this.LoopRooms = {
|
||||
init: function() {
|
||||
LoopRoomsInternal.init();
|
||||
},
|
||||
|
||||
get participantsCount() {
|
||||
return LoopRoomsInternal.participantsCount;
|
||||
},
|
||||
@ -1016,6 +1098,24 @@ this.LoopRooms = {
|
||||
|
||||
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);
|
||||
|
@ -1211,6 +1211,9 @@ this.MozLoopService = {
|
||||
// stub out API functions for unit testing
|
||||
Object.freeze(this);
|
||||
|
||||
// Initialise anything that needs it in rooms.
|
||||
LoopRooms.init();
|
||||
|
||||
// Don't do anything if loop is not enabled.
|
||||
if (!Services.prefs.getBoolPref("loop.enabled")) {
|
||||
return Promise.reject(new Error("loop is not enabled"));
|
||||
|
@ -46,6 +46,7 @@ TESTS="
|
||||
${LOOPDIR}/test/mochitest
|
||||
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_parsable_css.js
|
||||
"
|
||||
|
||||
./mach mochitest $TESTS
|
||||
@ -53,9 +54,3 @@ TESTS="
|
||||
if [ "$1" != "--skip-e10s" ]; then
|
||||
./mach mochitest --e10s $TESTS
|
||||
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 \
|
||||
|
@ -582,6 +582,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
hangup: this.leaveRoom,
|
||||
hangupButtonLabel: mozL10n.get("rooms_leave_button_label"),
|
||||
publishStream: this.publishStream,
|
||||
show: true,
|
||||
video: {enabled: !this.state.videoMuted,
|
||||
visible: this._roomIsActive()}})
|
||||
),
|
||||
|
@ -582,6 +582,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
hangup={this.leaveRoom}
|
||||
hangupButtonLabel={mozL10n.get("rooms_leave_button_label")}
|
||||
publishStream={this.publishStream}
|
||||
show={true}
|
||||
video={{enabled: !this.state.videoMuted,
|
||||
visible: this._roomIsActive()}} />
|
||||
</sharedViews.MediaLayoutView>
|
||||
|
@ -84,9 +84,9 @@ status_error=Something went wrong
|
||||
# Text chat strings
|
||||
|
||||
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
|
||||
# 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:
|
||||
# https://bug1084991.bugzilla.mozilla.org/attachment.cgi?id=8614721
|
||||
context_inroom_label=Let's talk about:
|
||||
context_inroom_label2=Let's Talk About:
|
||||
|
@ -708,16 +708,13 @@ describe("loop.roomViews", function () {
|
||||
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();
|
||||
|
||||
var editButton = view.getDOMNode().querySelector(".settings-menu > li.entry-settings-edit");
|
||||
React.addons.TestUtils.Simulate.click(editButton);
|
||||
|
||||
// Click again.
|
||||
React.addons.TestUtils.Simulate.click(editButton);
|
||||
|
||||
expect(view.getDOMNode().querySelector(".room-context")).to.eql(null);
|
||||
expect(view.getDOMNode().querySelector(".settings-menu")).to.eql(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -840,12 +837,12 @@ describe("loop.roomViews", function () {
|
||||
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({
|
||||
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);
|
||||
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-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() {
|
||||
@ -919,7 +887,7 @@ describe("loop.roomViews", function () {
|
||||
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.calledWithExactly(dispatcher.dispatch,
|
||||
@ -955,7 +923,7 @@ describe("loop.roomViews", function () {
|
||||
view.setProps({ savingContext: true }, function() {
|
||||
var node = view.getDOMNode();
|
||||
// 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.
|
||||
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() {
|
||||
var fakeEvent;
|
||||
|
||||
|
@ -32,5 +32,8 @@
|
||||
"MozLoopServiceInternal": true,
|
||||
"LoopRoomsInternal": true,
|
||||
"LoopUI": false,
|
||||
// Other items
|
||||
"Chat": true,
|
||||
"WebChannel": true
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ support-files =
|
||||
google_service.sjs
|
||||
head.js
|
||||
loop_fxa.sjs
|
||||
test_loopLinkClicker_channel.html
|
||||
../../../../base/content/test/general/browser_fxa_oauth_with_keys.html
|
||||
|
||||
[browser_CardDavImporter.js]
|
||||
@ -15,6 +16,8 @@ support-files =
|
||||
skip-if = e10s
|
||||
[browser_loop_fxa_server.js]
|
||||
[browser_LoopContacts.js]
|
||||
[browser_LoopRooms_channel.js]
|
||||
skip-if = asan && e10s # Bug 1206457
|
||||
[browser_mozLoop_appVersionInfo.js]
|
||||
[browser_mozLoop_context.js]
|
||||
[browser_mozLoop_prefs.js]
|
||||
|
@ -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");
|
||||
});
|
@ -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>
|
@ -429,7 +429,8 @@ describe("loop.shared.views", function() {
|
||||
function mountTestComponent(props) {
|
||||
props = _.extend({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: {}
|
||||
mozLoop: {},
|
||||
show: true
|
||||
}, props || {});
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(sharedViews.ConversationToolbar, props));
|
||||
@ -445,6 +446,16 @@ describe("loop.shared.views", function() {
|
||||
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() {
|
||||
var comp = mountTestComponent({
|
||||
hangupButtonLabel: "foo",
|
||||
|
@ -1118,6 +1118,7 @@
|
||||
publishStream: noop,
|
||||
screenShare: { state: SCREEN_SHARE_STATES.INACTIVE, visible: true},
|
||||
settingsMenuItems: [{ id: "feedback" }],
|
||||
show: true,
|
||||
video: { enabled: true, visible: true}})
|
||||
)
|
||||
),
|
||||
@ -1132,6 +1133,7 @@
|
||||
publishStream: noop,
|
||||
screenShare: { state: SCREEN_SHARE_STATES.PENDING, visible: true},
|
||||
settingsMenuItems: [{ id: "feedback" }],
|
||||
show: true,
|
||||
video: { enabled: false, visible: true}})
|
||||
)
|
||||
),
|
||||
@ -1146,6 +1148,7 @@
|
||||
publishStream: noop,
|
||||
screenShare: { state: SCREEN_SHARE_STATES.ACTIVE, visible: true},
|
||||
settingsMenuItems: [{ id: "feedback" }],
|
||||
show: true,
|
||||
video: { enabled: true, visible: true}})
|
||||
)
|
||||
)
|
||||
|
@ -1118,6 +1118,7 @@
|
||||
publishStream={noop}
|
||||
screenShare={{ state: SCREEN_SHARE_STATES.INACTIVE, visible: true }}
|
||||
settingsMenuItems={[{ id: "feedback" }]}
|
||||
show={true}
|
||||
video={{ enabled: true, visible: true }} />
|
||||
</div>
|
||||
</FramedExample>
|
||||
@ -1132,6 +1133,7 @@
|
||||
publishStream={noop}
|
||||
screenShare={{ state: SCREEN_SHARE_STATES.PENDING, visible: true }}
|
||||
settingsMenuItems={[{ id: "feedback" }]}
|
||||
show={true}
|
||||
video={{ enabled: false, visible: true }} />
|
||||
</div>
|
||||
</FramedExample>
|
||||
@ -1146,6 +1148,7 @@
|
||||
publishStream={noop}
|
||||
screenShare={{ state: SCREEN_SHARE_STATES.ACTIVE, visible: true }}
|
||||
settingsMenuItems={[{ id: "feedback" }]}
|
||||
show={true}
|
||||
video={{ enabled: true, visible: true }} />
|
||||
</div>
|
||||
</FramedExample>
|
||||
|
@ -70,11 +70,11 @@
|
||||
|
||||
<!-- Passwords -->
|
||||
<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">
|
||||
<checkbox id="savePasswords"
|
||||
label="&rememberPasswords.label;" accesskey="&rememberPasswords.accesskey;"
|
||||
label="&rememberLogins.label;" accesskey="&rememberLogins.accesskey;"
|
||||
preference="signon.rememberSignons"
|
||||
onsyncfrompreference="return gSecurityPane.readSavePasswords();"/>
|
||||
<spacer flex="1"/>
|
||||
@ -103,7 +103,7 @@
|
||||
<row id="showPasswordRow">
|
||||
<hbox id="showPasswordsBox"/>
|
||||
<button id="showPasswords"
|
||||
label="&savedPasswords.label;" accesskey="&savedPasswords.accesskey;"
|
||||
label="&savedLogins.label;" accesskey="&savedLogins.accesskey;"
|
||||
preference="pref.privacy.disable_button.view_passwords"/>
|
||||
</row>
|
||||
</rows>
|
||||
|
@ -22,7 +22,3 @@ BROWSER_CHROME_MANIFESTS += [
|
||||
XPCSHELL_TESTS_MANIFESTS += [
|
||||
'test/unit/xpcshell.ini'
|
||||
]
|
||||
|
||||
EXTRA_PP_COMPONENTS += [
|
||||
'translation.manifest',
|
||||
]
|
||||
|
@ -1,3 +0,0 @@
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
category healthreport-js-provider-default TranslationProvider resource:///modules/translation/Translation.jsm
|
||||
#endif
|
@ -684,8 +684,11 @@ AnimationsTimeline.prototype = {
|
||||
let getTime = time => L10N.getFormatStr("player.timeLabel",
|
||||
L10N.numberWithDecimals(time / 1000, 2));
|
||||
|
||||
let title = L10N.getFormatStr("timeline." + state.type + ".nameLabel",
|
||||
state.name);
|
||||
// The type isn't always available, older servers don't send it.
|
||||
let title =
|
||||
state.type
|
||||
? L10N.getFormatStr("timeline." + state.type + ".nameLabel", state.name)
|
||||
: state.name;
|
||||
let delay = L10N.getStr("player.animationDelayLabel") + " " +
|
||||
getTime(state.delay);
|
||||
let duration = L10N.getStr("player.animationDurationLabel") + " " +
|
||||
|
@ -43,7 +43,7 @@ label,
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.panel-header[hidden] {
|
||||
.panel-header[hidden], .panel-item[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -386,7 +386,6 @@
|
||||
@RESPATH@/browser/components/webideComponents.manifest
|
||||
@RESPATH@/browser/components/Experiments.manifest
|
||||
@RESPATH@/browser/components/ExperimentsService.js
|
||||
@RESPATH@/browser/components/translation.manifest
|
||||
@RESPATH@/components/Downloads.manifest
|
||||
@RESPATH@/components/DownloadLegacy.js
|
||||
@RESPATH@/components/BrowserPageThumbs.manifest
|
||||
@ -567,11 +566,6 @@
|
||||
@RESPATH@/components/InterAppMessagePort.js
|
||||
#endif
|
||||
|
||||
@RESPATH@/components/TCPSocket.js
|
||||
@RESPATH@/components/TCPServerSocket.js
|
||||
@RESPATH@/components/TCPSocketParentIntermediary.js
|
||||
@RESPATH@/components/TCPSocket.manifest
|
||||
|
||||
#ifdef MOZ_ACTIVITIES
|
||||
@RESPATH@/components/SystemMessageCache.js
|
||||
@RESPATH@/components/SystemMessageInternal.js
|
||||
|
@ -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 syncStartPanel2.heading "&syncBrand.shortName.label; enabled">
|
||||
<!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">
|
||||
|
@ -31,6 +31,15 @@ first_time_experience_button_label=Get Started
|
||||
first_time_experience_subheading=Join the conversation
|
||||
|
||||
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
|
||||
display_name_guest=Guest
|
||||
@ -343,12 +352,16 @@ infobar_menuitem_dontshowagain_accesskey=D
|
||||
|
||||
# Context in conversation strings
|
||||
|
||||
# LOCALIZATION NOTE (context_inroom_label): this string is followed by the
|
||||
# title/URL of the website you are having a conversation about, displayed on a
|
||||
# LOCALIZATION NOTE (context_inroom_header): this string is displayed in the
|
||||
# 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
|
||||
# to consider this as a stand-alone title. See example screenshot:
|
||||
# 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
|
||||
## by the title of the active tab, also known as the title of an HTML document.
|
||||
## The quotes around the title are intentional.
|
||||
@ -357,7 +370,8 @@ context_edit_name_placeholder=Conversation Name
|
||||
context_edit_comments_placeholder=Comments
|
||||
context_add_some_label=Add some 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_learn_more_link_label=Learn more.
|
||||
conversation_settings_menu_edit_context=Edit Context
|
||||
|
@ -24,10 +24,10 @@
|
||||
<!ENTITY addonExceptions.accesskey "E">
|
||||
|
||||
|
||||
<!ENTITY passwords.label "Passwords">
|
||||
<!ENTITY logins.label "Logins">
|
||||
|
||||
<!ENTITY rememberPasswords.label "Remember passwords for sites">
|
||||
<!ENTITY rememberPasswords.accesskey "R">
|
||||
<!ENTITY rememberLogins.label "Remember logins for sites">
|
||||
<!ENTITY rememberLogins.accesskey "R">
|
||||
<!ENTITY passwordExceptions.label "Exceptions…">
|
||||
<!ENTITY passwordExceptions.accesskey "x">
|
||||
|
||||
@ -36,5 +36,5 @@
|
||||
<!ENTITY changeMasterPassword.label "Change Master Password…">
|
||||
<!ENTITY changeMasterPassword.accesskey "M">
|
||||
|
||||
<!ENTITY savedPasswords.label "Saved Passwords…">
|
||||
<!ENTITY savedPasswords.accesskey "P">
|
||||
<!ENTITY savedLogins.label "Saved Logins…">
|
||||
<!ENTITY savedLogins.accesskey "L">
|
||||
|
@ -1725,15 +1725,13 @@ toolbarbutton.chevron > .toolbarbutton-icon {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#sync-error-panel-title,
|
||||
#sync-start-panel-title {
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#sync-start-panel-subtitle,
|
||||
#sync-error-panel-subtitle {
|
||||
#sync-start-panel-subtitle {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
@ -3275,15 +3275,13 @@ notification[value="loop-sharing-notification"] .messageImage {
|
||||
@hudButtonFocused@
|
||||
}
|
||||
|
||||
#sync-error-panel-title,
|
||||
#sync-start-panel-title {
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#sync-start-panel-subtitle,
|
||||
#sync-error-panel-subtitle {
|
||||
#sync-start-panel-subtitle {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
|
||||
border-bottom: 1px solid hsla(210, 4%, 10%, 0.14);
|
||||
color: -moz-FieldText;
|
||||
background-color: hsla(210, 4%, 10%, 0.07);
|
||||
padding: 6px 0;
|
||||
-moz-padding-start: 44px;
|
||||
|
@ -2473,15 +2473,13 @@ notification[value="loop-sharing-notification"] .messageImage {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#sync-error-panel-title,
|
||||
#sync-start-panel-title {
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#sync-start-panel-subtitle,
|
||||
#sync-error-panel-subtitle {
|
||||
#sync-start-panel-subtitle {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
@ -173,6 +173,9 @@ nsNullPrincipal::Read(nsIObjectInputStream* aStream)
|
||||
NS_IMETHODIMP
|
||||
nsNullPrincipal::Write(nsIObjectOutputStream* aStream)
|
||||
{
|
||||
NS_ENSURE_TRUE(mOriginAttributes.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
|
||||
NS_ERROR_INVALID_ARG);
|
||||
|
||||
nsAutoCString suffix;
|
||||
OriginAttributesRef().CreateSuffix(suffix);
|
||||
|
||||
|
@ -427,6 +427,8 @@ NS_IMETHODIMP
|
||||
nsPrincipal::Write(nsIObjectOutputStream* aStream)
|
||||
{
|
||||
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),
|
||||
true);
|
||||
|
@ -103,6 +103,20 @@ function run_test() {
|
||||
var simplePrin = ssm.getSimpleCodebasePrincipal(makeURI('http://example.com'));
|
||||
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.
|
||||
var exampleOrg_userContext = ssm.createCodebasePrincipal(makeURI('http://example.org'), {userContextId: 42});
|
||||
checkOriginAttributes(exampleOrg_userContext, { userContextId: 42 }, '^userContextId=42');
|
||||
|
@ -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 "networking", &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 },
|
||||
#endif
|
||||
{ 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 "srcdoc", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include "mozilla/dom/Permissions.h"
|
||||
#include "mozilla/dom/Presentation.h"
|
||||
#include "mozilla/dom/ServiceWorkerContainer.h"
|
||||
#include "mozilla/dom/TCPSocket.h"
|
||||
#include "mozilla/dom/Telephony.h"
|
||||
#include "mozilla/dom/Voicemail.h"
|
||||
#include "mozilla/dom/TVManager.h"
|
||||
@ -1805,6 +1806,13 @@ Navigator::GetInputPortManager(ErrorResult& aRv)
|
||||
return mInputPortManager;
|
||||
}
|
||||
|
||||
already_AddRefed<LegacyMozTCPSocket>
|
||||
Navigator::MozTCPSocket()
|
||||
{
|
||||
nsRefPtr<LegacyMozTCPSocket> socket = new LegacyMozTCPSocket(GetWindow());
|
||||
return socket.forget();
|
||||
}
|
||||
|
||||
#ifdef MOZ_B2G
|
||||
already_AddRefed<Promise>
|
||||
Navigator::GetMobileIdAssertion(const MobileIdOptions& aOptions,
|
||||
|
@ -99,6 +99,7 @@ class TVManager;
|
||||
class InputPortManager;
|
||||
class DeviceStorageAreaListener;
|
||||
class Presentation;
|
||||
class LegacyMozTCPSocket;
|
||||
|
||||
namespace time {
|
||||
class TimeManager;
|
||||
@ -242,6 +243,7 @@ public:
|
||||
Voicemail* GetMozVoicemail(ErrorResult& aRv);
|
||||
TVManager* GetTv();
|
||||
InputPortManager* GetInputPortManager(ErrorResult& aRv);
|
||||
already_AddRefed<LegacyMozTCPSocket> MozTCPSocket();
|
||||
network::Connection* GetConnection(ErrorResult& aRv);
|
||||
nsDOMCameraManager* GetMozCameras(ErrorResult& aRv);
|
||||
MediaDevices* GetMediaDevices(ErrorResult& aRv);
|
||||
|
@ -753,6 +753,7 @@ GK_ATOM(onDOMNodeInsertedIntoDocument, "onDOMNodeInsertedIntoDocument")
|
||||
GK_ATOM(onDOMNodeRemoved, "onDOMNodeRemoved")
|
||||
GK_ATOM(onDOMNodeRemovedFromDocument, "onDOMNodeRemovedFromDocument")
|
||||
GK_ATOM(onDOMSubtreeModified, "onDOMSubtreeModified")
|
||||
GK_ATOM(ondata, "ondata")
|
||||
GK_ATOM(ondrag, "ondrag")
|
||||
GK_ATOM(ondragdrop, "ondragdrop")
|
||||
GK_ATOM(ondragend, "ondragend")
|
||||
@ -762,6 +763,7 @@ GK_ATOM(ondraggesture, "ondraggesture")
|
||||
GK_ATOM(ondragleave, "ondragleave")
|
||||
GK_ATOM(ondragover, "ondragover")
|
||||
GK_ATOM(ondragstart, "ondragstart")
|
||||
GK_ATOM(ondrain, "ondrain")
|
||||
GK_ATOM(ondrop, "ondrop")
|
||||
GK_ATOM(oneitbroadcasted, "oneitbroadcasted")
|
||||
GK_ATOM(onenabled, "onenabled")
|
||||
|
@ -696,6 +696,11 @@ DOMInterfaces = {
|
||||
'concrete': False
|
||||
},
|
||||
|
||||
'LegacyMozTCPSocket': {
|
||||
'headerFile': 'TCPSocket.h',
|
||||
'wrapperCache': False,
|
||||
},
|
||||
|
||||
'LocalMediaStream': {
|
||||
'headerFile': 'DOMMediaStream.h',
|
||||
'nativeType': 'mozilla::DOMLocalMediaStream'
|
||||
@ -1308,6 +1313,10 @@ DOMInterfaces = {
|
||||
'wrapperCache': False
|
||||
},
|
||||
|
||||
'TCPSocket': {
|
||||
'implicitJSContext': ['send']
|
||||
},
|
||||
|
||||
'ThreadSafeChromeUtils': {
|
||||
# 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.
|
||||
|
@ -205,11 +205,7 @@ function beginTest() {
|
||||
}
|
||||
}
|
||||
|
||||
var prefs = [
|
||||
[ "canvas.capturestream.enabled", true ],
|
||||
];
|
||||
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
|
||||
|
||||
beginTest();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -211,13 +211,8 @@ function beginTest() {
|
||||
document.manager.runTests(corsTests, startTest);
|
||||
}
|
||||
|
||||
var prefs = [
|
||||
[ "canvas.capturestream.enabled", true ],
|
||||
];
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
|
||||
|
||||
beginTest();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -20,7 +20,7 @@ pref(webgl.force-layers-readback,true) == webgl-clear-test.html?readback wrappe
|
||||
== webgl-resize-test.html wrapper.html?green.png
|
||||
|
||||
# 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
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
@ -107,9 +107,6 @@ function beginTest() {
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var prefs = [
|
||||
[ "canvas.capturestream.enabled", true ],
|
||||
];
|
||||
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
|
||||
beginTest();
|
||||
</script>
|
||||
|
||||
|
@ -171,9 +171,6 @@ function beginTest() {
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var prefs = [
|
||||
[ "canvas.capturestream.enabled", true ],
|
||||
];
|
||||
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
|
||||
beginTest();
|
||||
</script>
|
||||
|
||||
|
@ -249,6 +249,7 @@ public:
|
||||
bool CanBeFormatted();
|
||||
bool CanBeShared();
|
||||
bool IsRemovable();
|
||||
bool LowDiskSpace();
|
||||
bool Default();
|
||||
void GetStorageName(nsAString& aStorageName);
|
||||
|
||||
|
@ -72,6 +72,7 @@ DeviceStorageStatics::InitializeDirs()
|
||||
DeviceStorageStatics::DeviceStorageStatics()
|
||||
: mInitialized(false)
|
||||
, mPromptTesting(false)
|
||||
, mLowDiskSpace(false)
|
||||
{
|
||||
DS_LOG_INFO("");
|
||||
}
|
||||
@ -358,6 +359,16 @@ DeviceStorageStatics::IsPromptTesting()
|
||||
return sInstance->mPromptTesting;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
DeviceStorageStatics::LowDiskSpace()
|
||||
{
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
if (NS_WARN_IF(!sInstance)) {
|
||||
return false;
|
||||
}
|
||||
return sInstance->mLowDiskSpace;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
DeviceStorageStatics::GetWritableName(nsString& aName)
|
||||
{
|
||||
@ -605,27 +616,29 @@ DeviceStorageStatics::Observe(nsISupports* aSubject,
|
||||
}
|
||||
|
||||
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);
|
||||
if (NS_WARN_IF(!sInstance)) {
|
||||
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();
|
||||
DS_LOG_INFO("disk space %d (%u)", lowDiskSpace, i);
|
||||
DS_LOG_INFO("disk space %d (%u)", sInstance->mLowDiskSpace, i);
|
||||
while (i > 0) {
|
||||
--i;
|
||||
mListeners[i]->OnDiskSpaceWatcher(lowDiskSpace);
|
||||
mListeners[i]->OnDiskSpaceWatcher(sInstance->mLowDiskSpace);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ public:
|
||||
static void AddListener(nsDOMDeviceStorage* aListener);
|
||||
static void RemoveListener(nsDOMDeviceStorage* aListener);
|
||||
|
||||
static bool LowDiskSpace();
|
||||
static bool IsPromptTesting();
|
||||
static void GetWritableName(nsString& aName);
|
||||
static void SetWritableName(const nsAString& aName);
|
||||
@ -92,6 +93,7 @@ private:
|
||||
|
||||
bool mInitialized;
|
||||
bool mPromptTesting;
|
||||
bool mLowDiskSpace;
|
||||
nsString mWritableName;
|
||||
|
||||
static StaticRefPtr<DeviceStorageStatics> sInstance;
|
||||
|
@ -3374,6 +3374,12 @@ nsDOMDeviceStorage::IsRemovable()
|
||||
return mIsRemovable;
|
||||
}
|
||||
|
||||
bool
|
||||
nsDOMDeviceStorage::LowDiskSpace()
|
||||
{
|
||||
return DeviceStorageStatics::LowDiskSpace();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
nsDOMDeviceStorage::GetRoot(ErrorResult& aRv)
|
||||
{
|
||||
|
@ -484,6 +484,18 @@ const kEventConstructors = {
|
||||
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) {
|
||||
var e = document.createEvent("timeevent");
|
||||
e.initTimeEvent(aName, aProps.view, aProps.detail);
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include "WorkerRunnable.h"
|
||||
#include "WorkerScope.h"
|
||||
#include "Workers.h"
|
||||
#include "FetchUtil.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -909,53 +910,6 @@ ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUS
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
@ -1436,128 +1390,81 @@ FetchBody<Derived>::ContinueConsumeBody(nsresult aStatus, uint32_t aResultLength
|
||||
jsapi.Init(DerivedClass()->GetParentObject());
|
||||
JSContext* cx = jsapi.cx();
|
||||
|
||||
ErrorResult error;
|
||||
|
||||
switch (mConsumeType) {
|
||||
case CONSUME_ARRAYBUFFER: {
|
||||
JS::Rooted<JSObject*> arrayBuffer(cx);
|
||||
arrayBuffer = JS_NewArrayBufferWithContents(cx, aResultLength, reinterpret_cast<void *>(aResult));
|
||||
if (!arrayBuffer) {
|
||||
JS_ClearPendingException(cx);
|
||||
localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
NS_WARNING("OUT OF MEMORY");
|
||||
return;
|
||||
}
|
||||
FetchUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult,
|
||||
error);
|
||||
|
||||
JS::Rooted<JS::Value> val(cx);
|
||||
val.setObjectOrNull(arrayBuffer);
|
||||
localPromise->MaybeResolve(cx, val);
|
||||
// ArrayBuffer takes over ownership.
|
||||
autoFree.Reset();
|
||||
return;
|
||||
error.WouldReportJSException();
|
||||
if (!error.Failed()) {
|
||||
JS::Rooted<JS::Value> val(cx);
|
||||
val.setObjectOrNull(arrayBuffer);
|
||||
|
||||
localPromise->MaybeResolve(cx, val);
|
||||
// ArrayBuffer takes over ownership.
|
||||
autoFree.Reset();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONSUME_BLOB: {
|
||||
nsRefPtr<dom::Blob> blob =
|
||||
Blob::CreateMemoryBlob(DerivedClass()->GetParentObject(),
|
||||
reinterpret_cast<void *>(aResult), aResultLength,
|
||||
NS_ConvertUTF8toUTF16(mMimeType));
|
||||
|
||||
if (!blob) {
|
||||
localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
return;
|
||||
nsRefPtr<dom::Blob> blob = FetchUtil::ConsumeBlob(
|
||||
DerivedClass()->GetParentObject(), NS_ConvertUTF8toUTF16(mMimeType),
|
||||
aResultLength, aResult, error);
|
||||
error.WouldReportJSException();
|
||||
if (!error.Failed()) {
|
||||
localPromise->MaybeResolve(blob);
|
||||
// File takes over ownership.
|
||||
autoFree.Reset();
|
||||
}
|
||||
|
||||
localPromise->MaybeResolve(blob);
|
||||
// File takes over ownership.
|
||||
autoFree.Reset();
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case CONSUME_FORMDATA: {
|
||||
nsCString data;
|
||||
data.Adopt(reinterpret_cast<char*>(aResult), aResultLength);
|
||||
autoFree.Reset();
|
||||
|
||||
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(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);
|
||||
nsRefPtr<nsFormData> fd = FetchUtil::ConsumeFormData(
|
||||
DerivedClass()->GetParentObject(),
|
||||
mMimeType, data, error);
|
||||
if (!error.Failed()) {
|
||||
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:
|
||||
// fall through handles early exit.
|
||||
case CONSUME_JSON: {
|
||||
StreamDecoder decoder;
|
||||
decoder.AppendText(reinterpret_cast<char*>(aResult), aResultLength);
|
||||
|
||||
nsString& decoded = decoder.GetText();
|
||||
if (mConsumeType == CONSUME_TEXT) {
|
||||
localPromise->MaybeResolve(decoded);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
nsString decoded;
|
||||
if (NS_SUCCEEDED(FetchUtil::ConsumeText(aResultLength, aResult, decoded))) {
|
||||
if (mConsumeType == CONSUME_TEXT) {
|
||||
localPromise->MaybeResolve(decoded);
|
||||
} else {
|
||||
JS::Rooted<JS::Value> json(cx);
|
||||
FetchUtil::ConsumeJson(cx, &json, decoded, error);
|
||||
if (!error.Failed()) {
|
||||
localPromise->MaybeResolve(cx, json);
|
||||
}
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> exn(cx);
|
||||
DebugOnly<bool> gotException = JS_GetPendingException(cx, &exn);
|
||||
MOZ_ASSERT(gotException);
|
||||
|
||||
JS_ClearPendingException(cx);
|
||||
localPromise->MaybeReject(cx, exn);
|
||||
return;
|
||||
}
|
||||
|
||||
localPromise->MaybeResolve(cx, json);
|
||||
return;
|
||||
};
|
||||
break;
|
||||
}
|
||||
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>
|
||||
@ -1622,5 +1529,6 @@ FetchBody<Request>::SetMimeType();
|
||||
template
|
||||
void
|
||||
FetchBody<Response>::SetMimeType();
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
@ -1,10 +1,63 @@
|
||||
#include "FetchUtil.h"
|
||||
|
||||
#include "nsError.h"
|
||||
#include "nsIUnicodeDecoder.h"
|
||||
#include "nsString.h"
|
||||
|
||||
#include "mozilla/dom/EncodingUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
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
|
||||
nsresult
|
||||
FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod)
|
||||
@ -33,5 +86,133 @@ FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod
|
||||
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 mozilla
|
||||
|
@ -3,6 +3,10 @@
|
||||
|
||||
#include "nsString.h"
|
||||
#include "nsError.h"
|
||||
#include "nsFormData.h"
|
||||
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -21,6 +25,46 @@ public:
|
||||
*/
|
||||
static nsresult
|
||||
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
|
||||
|
@ -82,6 +82,16 @@ public:
|
||||
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
|
||||
void
|
||||
SetUrl(const nsACString& aURL)
|
||||
|
@ -34,7 +34,7 @@ interface nsIServiceWorkerInfo : nsISupports
|
||||
readonly attribute DOMString waitingCacheName;
|
||||
};
|
||||
|
||||
[scriptable, builtinclass, uuid(8d80dd18-597b-4378-b41e-768bfe48dd4f)]
|
||||
[scriptable, builtinclass, uuid(471b2d5d-64c3-4dea-bde1-219853dcaac8)]
|
||||
interface nsIServiceWorkerManager : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -141,9 +141,10 @@ interface nsIServiceWorkerManager : nsISupports
|
||||
in AString aIcon,
|
||||
in AString aData,
|
||||
in AString aBehavior);
|
||||
void sendPushEvent(in ACString aOriginAttributes,
|
||||
in ACString aScope,
|
||||
in DOMString aData);
|
||||
[optional_argc] void sendPushEvent(in ACString aOriginAttributes,
|
||||
in ACString aScope,
|
||||
[optional] in uint32_t aDataLength,
|
||||
[optional, array, size_is(aDataLength)] in uint8_t aDataBytes);
|
||||
void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes,
|
||||
in ACString scope);
|
||||
|
||||
|
@ -33,6 +33,7 @@
|
||||
29 = Unbalanced curly brace.
|
||||
30 = Creating an element with an invalid QName.
|
||||
31 = Variable binding shadows variable binding within the same template.
|
||||
32 = Call to the key function not allowed.
|
||||
|
||||
LoadingError = Error loading stylesheet: %S
|
||||
TransformError = Error during XSLT transformation: %S
|
||||
|
@ -358,6 +358,14 @@ DOMMediaStream::StopTrack(TrackID aTrackID)
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
DOMMediaStream::ApplyConstraintsToTrack(TrackID aTrackID,
|
||||
const MediaTrackConstraints& aConstraints,
|
||||
ErrorResult &aRv)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
DOMMediaStream::CombineWithPrincipal(nsIPrincipal* aPrincipal)
|
||||
{
|
||||
|
@ -48,6 +48,7 @@ class VideoTrack;
|
||||
class AudioTrackList;
|
||||
class VideoTrackList;
|
||||
class MediaTrackListListener;
|
||||
struct MediaTrackConstraints;
|
||||
} // namespace dom
|
||||
|
||||
namespace layers {
|
||||
@ -77,6 +78,7 @@ class DOMMediaStream : public DOMEventTargetHelper
|
||||
typedef dom::MediaTrackListListener MediaTrackListListener;
|
||||
|
||||
public:
|
||||
typedef dom::MediaTrackConstraints MediaTrackConstraints;
|
||||
typedef uint8_t TrackTypeHints;
|
||||
|
||||
DOMMediaStream();
|
||||
@ -121,6 +123,11 @@ public:
|
||||
|
||||
virtual void StopTrack(TrackID aTrackID);
|
||||
|
||||
virtual already_AddRefed<dom::Promise>
|
||||
ApplyConstraintsToTrack(TrackID aTrackID,
|
||||
const MediaTrackConstraints& aConstraints,
|
||||
ErrorResult &aRv);
|
||||
|
||||
virtual DOMLocalMediaStream* AsDOMLocalMediaStream() { return nullptr; }
|
||||
virtual DOMHwMediaStream* AsDOMHwMediaStream() { return nullptr; }
|
||||
|
||||
|
@ -79,8 +79,6 @@
|
||||
#include "mozilla/WindowsVersion.h"
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
|
||||
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
|
||||
// GetTickCount() and conflicts with MediaStream::GetCurrentTime.
|
||||
#ifdef GetCurrentTime
|
||||
@ -119,6 +117,7 @@ using dom::File;
|
||||
using dom::MediaStreamConstraints;
|
||||
using dom::MediaTrackConstraintSet;
|
||||
using dom::MediaTrackConstraints;
|
||||
using dom::MediaStreamTrack;
|
||||
using dom::MediaStreamError;
|
||||
using dom::GetUserMediaRequest;
|
||||
using dom::Sequence;
|
||||
@ -214,6 +213,154 @@ HostHasPermission(nsIURI &docURI)
|
||||
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.
|
||||
* 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);
|
||||
}
|
||||
|
||||
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
|
||||
* that need to be cleaned up.
|
||||
@ -480,23 +637,23 @@ public:
|
||||
static already_AddRefed<nsDOMUserMediaStream>
|
||||
CreateTrackUnionStream(nsIDOMWindow* aWindow,
|
||||
GetUserMediaCallbackMediaStreamListener* aListener,
|
||||
MediaEngineSource* aAudioSource,
|
||||
MediaEngineSource* aVideoSource,
|
||||
AudioDevice* aAudioDevice,
|
||||
VideoDevice* aVideoDevice,
|
||||
MediaStreamGraph* aMSG)
|
||||
{
|
||||
nsRefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aListener,
|
||||
aAudioSource,
|
||||
aVideoSource);
|
||||
aAudioDevice,
|
||||
aVideoDevice);
|
||||
stream->InitTrackUnionStream(aWindow, aMSG);
|
||||
return stream.forget();
|
||||
}
|
||||
|
||||
nsDOMUserMediaStream(GetUserMediaCallbackMediaStreamListener* aListener,
|
||||
MediaEngineSource *aAudioSource,
|
||||
MediaEngineSource *aVideoSource) :
|
||||
AudioDevice *aAudioDevice,
|
||||
VideoDevice *aVideoDevice) :
|
||||
mListener(aListener),
|
||||
mAudioSource(aAudioSource),
|
||||
mVideoSource(aVideoSource),
|
||||
mAudioDevice(aAudioDevice),
|
||||
mVideoDevice(aVideoDevice),
|
||||
mEchoOn(true),
|
||||
mAgcOn(false),
|
||||
mNoiseOn(true),
|
||||
@ -543,13 +700,60 @@ public:
|
||||
// risky to do late in a release since that will affect all track ends, and not
|
||||
// just StopTrack()s.
|
||||
if (GetDOMTrackFor(aTrackID)) {
|
||||
mListener->StopTrack(aTrackID, !!GetDOMTrackFor(aTrackID)->AsAudioStreamTrack());
|
||||
mListener->StopTrack(aTrackID,
|
||||
!!GetDOMTrackFor(aTrackID)->AsAudioStreamTrack());
|
||||
} 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
|
||||
virtual void NotifyMediaStreamTrackEnded(dom::MediaStreamTrack* aTrack)
|
||||
{
|
||||
@ -598,14 +802,14 @@ public:
|
||||
}
|
||||
|
||||
// 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 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
|
||||
GetStream()->AsProcessedStream()->ForwardTrackEnabled(aID, aEnabled);
|
||||
GetStream()->AsProcessedStream()->ForwardTrackEnabled(aTrackID, aEnabled);
|
||||
}
|
||||
|
||||
virtual DOMLocalMediaStream* AsDOMLocalMediaStream() override
|
||||
@ -618,10 +822,10 @@ public:
|
||||
// MediaEngine supports only one video and on video track now and TrackID is
|
||||
// fixed in MediaEngine.
|
||||
if (aTrackID == kVideoTrack) {
|
||||
return mVideoSource;
|
||||
return mVideoDevice ? mVideoDevice->GetSource() : nullptr;
|
||||
}
|
||||
else if (aTrackID == kAudioTrack) {
|
||||
return mAudioSource;
|
||||
return mAudioDevice ? mAudioDevice->GetSource() : nullptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@ -632,8 +836,8 @@ public:
|
||||
nsRefPtr<SourceMediaStream> mSourceStream;
|
||||
nsRefPtr<MediaInputPort> mPort;
|
||||
nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
|
||||
nsRefPtr<MediaEngineSource> mAudioSource; // so we can turn on AEC
|
||||
nsRefPtr<MediaEngineSource> mVideoSource;
|
||||
nsRefPtr<AudioDevice> mAudioDevice; // so we can turn on AEC
|
||||
nsRefPtr<VideoDevice> mVideoDevice;
|
||||
bool mEchoOn;
|
||||
bool mAgcOn;
|
||||
bool mNoiseOn;
|
||||
@ -686,11 +890,11 @@ public:
|
||||
uint64_t aWindowID,
|
||||
GetUserMediaCallbackMediaStreamListener* aListener,
|
||||
const nsCString& aOrigin,
|
||||
MediaEngineSource* aAudioSource,
|
||||
MediaEngineSource* aVideoSource,
|
||||
AudioDevice* aAudioDevice,
|
||||
VideoDevice* aVideoDevice,
|
||||
PeerIdentity* aPeerIdentity)
|
||||
: mAudioSource(aAudioSource)
|
||||
, mVideoSource(aVideoSource)
|
||||
: mAudioDevice(aAudioDevice)
|
||||
, mVideoDevice(aVideoDevice)
|
||||
, mWindowID(aWindowID)
|
||||
, mListener(aListener)
|
||||
, mOrigin(aOrigin)
|
||||
@ -787,7 +991,7 @@ public:
|
||||
#endif
|
||||
|
||||
MediaStreamGraph::GraphDriverType graphDriverType =
|
||||
mAudioSource ? MediaStreamGraph::AUDIO_THREAD_DRIVER
|
||||
mAudioDevice ? MediaStreamGraph::AUDIO_THREAD_DRIVER
|
||||
: MediaStreamGraph::SYSTEM_THREAD_DRIVER;
|
||||
MediaStreamGraph* msg =
|
||||
MediaStreamGraph::GetInstance(graphDriverType,
|
||||
@ -800,8 +1004,8 @@ public:
|
||||
// using the audio source and the SourceMediaStream, which acts as
|
||||
// placeholders. We re-route a number of stream internaly in the MSG and mix
|
||||
// them down instead.
|
||||
if (mAudioSource &&
|
||||
mAudioSource->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
|
||||
if (mAudioDevice &&
|
||||
mAudioDevice->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
|
||||
domStream = DOMLocalMediaStream::CreateAudioCaptureStream(window, msg);
|
||||
// It should be possible to pipe the capture stream to anything. CORS is
|
||||
// not a problem here, we got explicit user content.
|
||||
@ -814,7 +1018,7 @@ public:
|
||||
// avoid us blocking
|
||||
nsRefPtr<nsDOMUserMediaStream> trackunion =
|
||||
nsDOMUserMediaStream::CreateTrackUnionStream(window, mListener,
|
||||
mAudioSource, mVideoSource,
|
||||
mAudioDevice, mVideoDevice,
|
||||
msg);
|
||||
trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true);
|
||||
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
|
||||
// that the MediaStream has started consuming. The listener is freed
|
||||
// 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
|
||||
TracksAvailableCallback* tracksAvailableCallback =
|
||||
@ -874,11 +1078,11 @@ public:
|
||||
// because that can take a while.
|
||||
// Pass ownership of trackunion to the MediaOperationTask
|
||||
// to ensure it's kept alive until the MediaOperationTask runs (at least).
|
||||
MediaManager::PostTask(
|
||||
FROM_HERE, new MediaOperationTask(MEDIA_START, mListener, domStream,
|
||||
tracksAvailableCallback, mAudioSource,
|
||||
mVideoSource, false, mWindowID,
|
||||
mOnFailure.forget()));
|
||||
MediaManager::PostTask(FROM_HERE,
|
||||
new MediaOperationTask(MEDIA_START, mListener, domStream,
|
||||
tracksAvailableCallback,
|
||||
mAudioDevice, mVideoDevice,
|
||||
false, mWindowID, mOnFailure.forget()));
|
||||
// We won't need mOnFailure now.
|
||||
mOnFailure = nullptr;
|
||||
|
||||
@ -893,8 +1097,8 @@ public:
|
||||
private:
|
||||
nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
|
||||
nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
|
||||
nsRefPtr<MediaEngineSource> mAudioSource;
|
||||
nsRefPtr<MediaEngineSource> mVideoSource;
|
||||
nsRefPtr<AudioDevice> mAudioDevice;
|
||||
nsRefPtr<VideoDevice> mVideoDevice;
|
||||
uint64_t mWindowID;
|
||||
nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
|
||||
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)
|
||||
|
||||
template<class DeviceType>
|
||||
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)
|
||||
static const char*
|
||||
SelectSettings(MediaStreamConstraints &aConstraints,
|
||||
nsTArray<nsRefPtr<MediaDevice>>& aSources)
|
||||
{
|
||||
// Since the advanced part of the constraints algorithm needs to know when
|
||||
// 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.
|
||||
|
||||
bool overconstrained = false;
|
||||
nsTArray<nsRefPtr<VideoDevice>> videos;
|
||||
nsTArray<nsRefPtr<AudioDevice>> audios;
|
||||
|
||||
@ -1032,25 +1176,23 @@ ApplyConstraints(MediaStreamConstraints &aConstraints,
|
||||
aSources.Clear();
|
||||
MOZ_ASSERT(!aSources.Length());
|
||||
|
||||
const char* badConstraint = nullptr;
|
||||
|
||||
if (IsOn(aConstraints.mVideo)) {
|
||||
ApplyConstraints(GetInvariant(aConstraints.mVideo), videos);
|
||||
if (!videos.Length()) {
|
||||
overconstrained = true;
|
||||
}
|
||||
badConstraint = MediaConstraintsHelper::SelectSettings(
|
||||
GetInvariant(aConstraints.mVideo), videos);
|
||||
for (auto& video : videos) {
|
||||
aSources.AppendElement(video);
|
||||
}
|
||||
}
|
||||
if (IsOn(aConstraints.mAudio)) {
|
||||
ApplyConstraints(GetInvariant(aConstraints.mAudio), audios);
|
||||
if (!audios.Length()) {
|
||||
overconstrained = true;
|
||||
}
|
||||
if (audios.Length() && IsOn(aConstraints.mAudio)) {
|
||||
badConstraint = MediaConstraintsHelper::SelectSettings(
|
||||
GetInvariant(aConstraints.mAudio), audios);
|
||||
for (auto& audio : audios) {
|
||||
aSources.AppendElement(audio);
|
||||
}
|
||||
}
|
||||
return !overconstrained;
|
||||
return badConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1143,13 +1285,10 @@ public:
|
||||
peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
|
||||
}
|
||||
|
||||
NS_DispatchToMainThread(do_AddRef(new GetUserMediaStreamRunnable(
|
||||
mOnSuccess, mOnFailure, mWindowID, mListener, mOrigin,
|
||||
(mAudioDevice? mAudioDevice->GetSource() : nullptr),
|
||||
(mVideoDevice? mVideoDevice->GetSource() : nullptr),
|
||||
peerIdentity
|
||||
)));
|
||||
|
||||
NS_DispatchToMainThread(do_AddRef(
|
||||
new GetUserMediaStreamRunnable(mOnSuccess, mOnFailure, mWindowID,
|
||||
mListener, mOrigin, mAudioDevice,
|
||||
mVideoDevice, peerIdentity)));
|
||||
MOZ_ASSERT(!mOnSuccess);
|
||||
MOZ_ASSERT(!mOnFailure);
|
||||
}
|
||||
@ -1697,7 +1836,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
|
||||
auto& vc = c.mVideo.GetAsMediaTrackConstraints();
|
||||
videoType = StringToEnum(dom::MediaSourceEnumValues::strings,
|
||||
vc.mMediaSource,
|
||||
videoType);
|
||||
dom::MediaSourceEnum::Other);
|
||||
switch (videoType) {
|
||||
case dom::MediaSourceEnum::Camera:
|
||||
break;
|
||||
@ -1740,7 +1879,10 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
|
||||
case dom::MediaSourceEnum::Other:
|
||||
default: {
|
||||
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);
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1785,7 +1927,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
|
||||
auto& ac = c.mAudio.GetAsMediaTrackConstraints();
|
||||
audioType = StringToEnum(dom::MediaSourceEnumValues::strings,
|
||||
ac.mMediaSource,
|
||||
audioType);
|
||||
dom::MediaSourceEnum::Other);
|
||||
// Work around WebIDL default since spec uses same dictionary w/audio & video.
|
||||
if (audioType == dom::MediaSourceEnum::Camera) {
|
||||
audioType = dom::MediaSourceEnum::Microphone;
|
||||
@ -1812,7 +1954,10 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
|
||||
case dom::MediaSourceEnum::Other:
|
||||
default: {
|
||||
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);
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1905,8 +2050,19 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
|
||||
}
|
||||
|
||||
// Apply any constraints. This modifies the list.
|
||||
|
||||
if (!ApplyConstraints(c, *devices)) {
|
||||
const char* badConstraint = SelectSettings(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 =
|
||||
new MediaStreamError(window, NS_LITERAL_STRING("NotFoundError"));
|
||||
onFailure->OnError(error);
|
||||
@ -1957,8 +2113,8 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
|
||||
#ifdef MOZ_WEBRTC
|
||||
EnableWebRtcLog();
|
||||
#endif
|
||||
}, [onFailure](MediaStreamError& reason) mutable {
|
||||
onFailure->OnError(&reason);
|
||||
}, [onFailure](MediaStreamError*& reason) mutable {
|
||||
onFailure->OnError(reason);
|
||||
});
|
||||
return NS_OK;
|
||||
}
|
||||
@ -2138,10 +2294,10 @@ MediaManager::EnumerateDevices(nsPIDOMWindow* aWindow,
|
||||
mgr->RemoveFromWindowList(windowId, listener);
|
||||
nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
|
||||
onSuccess->OnSuccess(array);
|
||||
}, [onFailure, windowId, listener](MediaStreamError& reason) mutable {
|
||||
}, [onFailure, windowId, listener](MediaStreamError*& reason) mutable {
|
||||
nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
|
||||
mgr->RemoveFromWindowList(windowId, listener);
|
||||
onFailure->OnError(&reason);
|
||||
onFailure->OnError(reason);
|
||||
});
|
||||
return NS_OK;
|
||||
}
|
||||
@ -2853,10 +3009,10 @@ GetUserMediaCallbackMediaStreamListener::AudioConfig(bool aEchoOn,
|
||||
bool aNoiseOn, uint32_t aNoise,
|
||||
int32_t aPlayoutDelay)
|
||||
{
|
||||
if (mAudioSource) {
|
||||
if (mAudioDevice) {
|
||||
#ifdef MOZ_WEBRTC
|
||||
MediaManager::PostTask(FROM_HERE,
|
||||
NewRunnableMethod(mAudioSource.get(), &MediaEngineSource::Config,
|
||||
NewRunnableMethod(mAudioDevice->GetSource(), &MediaEngineSource::Config,
|
||||
aEchoOn, aEcho, aAgcOn, aAGC, aNoiseOn,
|
||||
aNoise, aPlayoutDelay));
|
||||
#endif
|
||||
@ -2874,7 +3030,7 @@ GetUserMediaCallbackMediaStreamListener::Invalidate()
|
||||
MediaManager::PostTask(FROM_HERE,
|
||||
new MediaOperationTask(MEDIA_STOP,
|
||||
this, nullptr, nullptr,
|
||||
mAudioSource, mVideoSource,
|
||||
mAudioDevice, mVideoDevice,
|
||||
mFinished, mWindowID, nullptr));
|
||||
}
|
||||
|
||||
@ -2884,18 +3040,18 @@ void
|
||||
GetUserMediaCallbackMediaStreamListener::StopSharing()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
if (mVideoSource && !mStopped &&
|
||||
(mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Screen ||
|
||||
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Application ||
|
||||
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Window)) {
|
||||
if (mVideoDevice && !mStopped &&
|
||||
(mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen ||
|
||||
mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application ||
|
||||
mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window)) {
|
||||
// Stop the whole stream if there's no audio; just the video track if we have both
|
||||
MediaManager::PostTask(FROM_HERE,
|
||||
new MediaOperationTask(mAudioSource ? MEDIA_STOP_TRACK : MEDIA_STOP,
|
||||
new MediaOperationTask(mAudioDevice ? MEDIA_STOP_TRACK : MEDIA_STOP,
|
||||
this, nullptr, nullptr,
|
||||
nullptr, mVideoSource,
|
||||
nullptr, mVideoDevice,
|
||||
mFinished, mWindowID, nullptr));
|
||||
} else if (mAudioSource &&
|
||||
mAudioSource->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
|
||||
} else if (mAudioDevice &&
|
||||
mAudioDevice->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
|
||||
nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
|
||||
MOZ_ASSERT(window);
|
||||
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
|
||||
|
||||
void
|
||||
GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aID, bool aIsAudio)
|
||||
GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID, bool aIsAudio)
|
||||
{
|
||||
if (((aIsAudio && mAudioSource) ||
|
||||
(!aIsAudio && mVideoSource)) && !mStopped)
|
||||
if (((aIsAudio && mAudioDevice) ||
|
||||
(!aIsAudio && mVideoDevice)) && !mStopped)
|
||||
{
|
||||
// XXX to support multiple tracks of a type in a stream, this should key off
|
||||
// the TrackID and not just the type
|
||||
MediaManager::PostTask(FROM_HERE,
|
||||
new MediaOperationTask(MEDIA_STOP_TRACK,
|
||||
this, nullptr, nullptr,
|
||||
aIsAudio ? mAudioSource.get() : nullptr,
|
||||
!aIsAudio ? mVideoSource.get() : nullptr,
|
||||
aIsAudio ? mAudioDevice.get() : nullptr,
|
||||
!aIsAudio ? mVideoDevice.get() : nullptr,
|
||||
mFinished, mWindowID, nullptr));
|
||||
} else {
|
||||
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,
|
||||
new MediaOperationTask(MEDIA_DIRECT_LISTENERS,
|
||||
this, nullptr, nullptr,
|
||||
mAudioSource, mVideoSource,
|
||||
mAudioDevice, mVideoDevice,
|
||||
aHasListeners, mWindowID, nullptr));
|
||||
}
|
||||
|
||||
|
@ -47,12 +47,72 @@
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
struct MediaStreamConstraints;
|
||||
struct MediaTrackConstraints;
|
||||
struct MediaTrackConstraintSet;
|
||||
} // namespace dom
|
||||
|
||||
extern PRLogModuleInfo* GetMediaManagerLog();
|
||||
#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
|
||||
* to Start() and Stop() the underlying MediaEngineSource when MediaStreams
|
||||
@ -79,13 +139,13 @@ public:
|
||||
}
|
||||
|
||||
void Activate(already_AddRefed<SourceMediaStream> aStream,
|
||||
MediaEngineSource* aAudioSource,
|
||||
MediaEngineSource* aVideoSource)
|
||||
AudioDevice* aAudioDevice,
|
||||
VideoDevice* aVideoDevice)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
mStream = aStream;
|
||||
mAudioSource = aAudioSource;
|
||||
mVideoSource = aVideoSource;
|
||||
mAudioDevice = aAudioDevice;
|
||||
mVideoDevice = aVideoDevice;
|
||||
|
||||
mStream->AddListener(this);
|
||||
}
|
||||
@ -107,46 +167,57 @@ public:
|
||||
|
||||
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.
|
||||
bool CapturingVideo()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
return mVideoSource && !mStopped &&
|
||||
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Camera &&
|
||||
(!mVideoSource->IsFake() ||
|
||||
return mVideoDevice && !mStopped &&
|
||||
mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
|
||||
(!mVideoDevice->GetSource()->IsFake() ||
|
||||
Preferences::GetBool("media.navigator.permission.fake"));
|
||||
}
|
||||
bool CapturingAudio()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
return mAudioSource && !mStopped &&
|
||||
(!mAudioSource->IsFake() ||
|
||||
return mAudioDevice && !mStopped &&
|
||||
(!mAudioDevice->GetSource()->IsFake() ||
|
||||
Preferences::GetBool("media.navigator.permission.fake"));
|
||||
}
|
||||
bool CapturingScreen()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
return mVideoSource && !mStopped && !mVideoSource->IsAvailable() &&
|
||||
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Screen;
|
||||
return mVideoDevice && !mStopped &&
|
||||
!mVideoDevice->GetSource()->IsAvailable() &&
|
||||
mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen;
|
||||
}
|
||||
bool CapturingWindow()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
return mVideoSource && !mStopped && !mVideoSource->IsAvailable() &&
|
||||
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Window;
|
||||
return mVideoDevice && !mStopped &&
|
||||
!mVideoDevice->GetSource()->IsAvailable() &&
|
||||
mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window;
|
||||
}
|
||||
bool CapturingApplication()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
return mVideoSource && !mStopped && !mVideoSource->IsAvailable() &&
|
||||
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Application;
|
||||
return mVideoDevice && !mStopped &&
|
||||
!mVideoDevice->GetSource()->IsAvailable() &&
|
||||
mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application;
|
||||
}
|
||||
bool CapturingBrowser()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
return mVideoSource && !mStopped && mVideoSource->IsAvailable() &&
|
||||
mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Browser;
|
||||
return mVideoDevice && !mStopped &&
|
||||
mVideoDevice->GetSource()->IsAvailable() &&
|
||||
mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Browser;
|
||||
}
|
||||
|
||||
void SetStopped()
|
||||
@ -187,11 +258,13 @@ public:
|
||||
{
|
||||
// Currently audio sources ignore NotifyPull, but they could
|
||||
// watch it especially for fake audio.
|
||||
if (mAudioSource) {
|
||||
mAudioSource->NotifyPull(aGraph, mStream, kAudioTrack, aDesiredTime);
|
||||
if (mAudioDevice) {
|
||||
mAudioDevice->GetSource()->NotifyPull(aGraph, mStream, kAudioTrack,
|
||||
aDesiredTime);
|
||||
}
|
||||
if (mVideoSource) {
|
||||
mVideoSource->NotifyPull(aGraph, mStream, kVideoTrack, aDesiredTime);
|
||||
if (mVideoDevice) {
|
||||
mVideoDevice->GetSource()->NotifyPull(aGraph, mStream, kVideoTrack,
|
||||
aDesiredTime);
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,8 +310,8 @@ private:
|
||||
|
||||
// Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
|
||||
// No locking needed as they're only addrefed except on the MediaManager thread
|
||||
nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe refcnt
|
||||
nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe refcnt
|
||||
nsRefPtr<AudioDevice> mAudioDevice; // threadsafe refcnt
|
||||
nsRefPtr<VideoDevice> mVideoDevice; // threadsafe refcnt
|
||||
nsRefPtr<SourceMediaStream> mStream; // threadsafe refcnt
|
||||
bool mFinished;
|
||||
|
||||
@ -253,7 +326,7 @@ class GetUserMediaNotificationEvent: public nsRunnable
|
||||
enum GetUserMediaStatus {
|
||||
STARTING,
|
||||
STOPPING,
|
||||
STOPPED_TRACK
|
||||
STOPPED_TRACK,
|
||||
};
|
||||
GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener,
|
||||
GetUserMediaStatus aStatus,
|
||||
@ -291,7 +364,7 @@ typedef enum {
|
||||
MEDIA_START,
|
||||
MEDIA_STOP,
|
||||
MEDIA_STOP_TRACK,
|
||||
MEDIA_DIRECT_LISTENERS
|
||||
MEDIA_DIRECT_LISTENERS,
|
||||
} MediaOperation;
|
||||
|
||||
class MediaManager;
|
||||
@ -310,206 +383,9 @@ private:
|
||||
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 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
|
||||
typedef void (*WindowListenerCallback)(MediaManager *aThis,
|
||||
uint64_t aWindowID,
|
||||
@ -519,6 +395,7 @@ typedef void (*WindowListenerCallback)(MediaManager *aThis,
|
||||
class MediaManager final : public nsIMediaManagerService,
|
||||
public nsIObserver
|
||||
{
|
||||
friend GetUserMediaCallbackMediaStreamListener;
|
||||
public:
|
||||
static already_AddRefed<MediaManager> GetInstance();
|
||||
|
||||
@ -586,7 +463,7 @@ public:
|
||||
typedef nsTArray<nsRefPtr<MediaDevice>> SourceSet;
|
||||
static bool IsPrivateBrowsing(nsPIDOMWindow *window);
|
||||
private:
|
||||
typedef media::Pledge<SourceSet*, dom::MediaStreamError> PledgeSourceSet;
|
||||
typedef media::Pledge<SourceSet*, dom::MediaStreamError*> PledgeSourceSet;
|
||||
|
||||
static bool IsPrivileged();
|
||||
static bool IsLoop(nsIURI* aDocURI);
|
||||
@ -646,6 +523,7 @@ private:
|
||||
static StaticRefPtr<MediaManager> sSingleton;
|
||||
|
||||
media::CoatCheck<PledgeSourceSet> mOutstandingPledges;
|
||||
media::CoatCheck<GetUserMediaCallbackMediaStreamListener::PledgeVoid> mOutstandingVoidPledges;
|
||||
#if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK)
|
||||
nsRefPtr<nsDOMCameraManager> mCameraManager;
|
||||
#endif
|
||||
|
@ -12,10 +12,10 @@ namespace mozilla {
|
||||
|
||||
BaseMediaMgrError::BaseMediaMgrError(const nsAString& aName,
|
||||
const nsAString& aMessage,
|
||||
const nsAString& aConstraintName)
|
||||
const nsAString& aConstraint)
|
||||
: mName(aName)
|
||||
, mMessage(aMessage)
|
||||
, mConstraintName(aConstraintName)
|
||||
, mConstraint(aConstraint)
|
||||
{
|
||||
if (mMessage.IsEmpty()) {
|
||||
if (mName.EqualsLiteral("NotFoundError")) {
|
||||
@ -28,8 +28,9 @@ BaseMediaMgrError::BaseMediaMgrError(const nsAString& aName,
|
||||
} else if (mName.EqualsLiteral("InternalError")) {
|
||||
mMessage.AssignLiteral("Internal error.");
|
||||
} else if (mName.EqualsLiteral("NotSupportedError")) {
|
||||
mMessage.AssignLiteral("Constraints with no audio or video in it are not "
|
||||
"supported");
|
||||
mMessage.AssignLiteral("The operation is not supported.");
|
||||
} else if (mName.EqualsLiteral("OverconstrainedError")) {
|
||||
mMessage.AssignLiteral("Constraints could be not satisfied.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,8 +44,8 @@ MediaStreamError::MediaStreamError(
|
||||
nsPIDOMWindow* aParent,
|
||||
const nsAString& aName,
|
||||
const nsAString& aMessage,
|
||||
const nsAString& aConstraintName)
|
||||
: BaseMediaMgrError(aName, aMessage, aConstraintName)
|
||||
const nsAString& aConstraint)
|
||||
: BaseMediaMgrError(aName, aMessage, aConstraint)
|
||||
, mParent(aParent) {}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaStreamError, mParent)
|
||||
@ -75,9 +76,9 @@ MediaStreamError::GetMessage(nsAString& aMessage) const
|
||||
}
|
||||
|
||||
void
|
||||
MediaStreamError::GetConstraintName(nsAString& aConstraintName) const
|
||||
MediaStreamError::GetConstraint(nsAString& aConstraint) const
|
||||
{
|
||||
aConstraintName = mConstraintName;
|
||||
aConstraint = mConstraint;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
|
@ -34,10 +34,10 @@ class BaseMediaMgrError
|
||||
protected:
|
||||
BaseMediaMgrError(const nsAString& aName,
|
||||
const nsAString& aMessage,
|
||||
const nsAString& aConstraintName);
|
||||
const nsAString& aConstraint);
|
||||
const nsString mName;
|
||||
nsString mMessage;
|
||||
const nsString mConstraintName;
|
||||
const nsString mConstraint;
|
||||
};
|
||||
|
||||
class MediaMgrError final : public nsISupports,
|
||||
@ -46,8 +46,8 @@ class MediaMgrError final : public nsISupports,
|
||||
public:
|
||||
explicit MediaMgrError(const nsAString& aName,
|
||||
const nsAString& aMessage = EmptyString(),
|
||||
const nsAString& aConstraintName = EmptyString())
|
||||
: BaseMediaMgrError(aName, aMessage, aConstraintName) {}
|
||||
const nsAString& aConstraint = EmptyString())
|
||||
: BaseMediaMgrError(aName, aMessage, aConstraint) {}
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
|
||||
@ -64,11 +64,11 @@ public:
|
||||
MediaStreamError(nsPIDOMWindow* aParent,
|
||||
const nsAString& aName,
|
||||
const nsAString& aMessage = EmptyString(),
|
||||
const nsAString& aConstraintName = EmptyString());
|
||||
const nsAString& aConstraint = EmptyString());
|
||||
|
||||
MediaStreamError(nsPIDOMWindow* aParent,
|
||||
const BaseMediaMgrError& aOther)
|
||||
: BaseMediaMgrError(aOther.mName, aOther.mMessage, aOther.mConstraintName)
|
||||
: BaseMediaMgrError(aOther.mName, aOther.mMessage, aOther.mConstraint)
|
||||
, mParent(aParent) {}
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
@ -83,7 +83,7 @@ public:
|
||||
}
|
||||
void GetName(nsAString& aName) const;
|
||||
void GetMessage(nsAString& aMessage) const;
|
||||
void GetConstraintName(nsAString& aConstraintName) const;
|
||||
void GetConstraint(nsAString& aConstraint) const;
|
||||
|
||||
private:
|
||||
virtual ~MediaStreamError() {}
|
||||
|
@ -62,5 +62,12 @@ MediaStreamTrack::Stop()
|
||||
mStream->StopTrack(mTrackID);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
MediaStreamTrack::ApplyConstraints(const MediaTrackConstraints& aConstraints,
|
||||
ErrorResult &aRv)
|
||||
{
|
||||
return mStream->ApplyConstraintsToTrack(mTrackID, aConstraints, aRv);
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "mozilla/DOMEventTargetHelper.h"
|
||||
#include "nsID.h"
|
||||
#include "StreamBuffer.h"
|
||||
#include "MediaTrackConstraints.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -49,6 +50,8 @@ public:
|
||||
bool Enabled() { return mEnabled; }
|
||||
void SetEnabled(bool aEnabled);
|
||||
void Stop();
|
||||
already_AddRefed<Promise>
|
||||
ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv);
|
||||
|
||||
// Notifications from the MediaStreamGraph
|
||||
void NotifyEnded() { mEnded = true; }
|
||||
|
@ -63,7 +63,7 @@ class Pledge : public PledgeBase
|
||||
};
|
||||
|
||||
public:
|
||||
explicit Pledge() : mDone(false), mError(nullptr) {}
|
||||
explicit Pledge() : mDone(false), mRejected(false) {}
|
||||
Pledge(const Pledge& aOther) = delete;
|
||||
Pledge& operator = (const Pledge&) = delete;
|
||||
|
||||
@ -97,10 +97,10 @@ public:
|
||||
mFunctors = new Functors(aOnSuccess, aOnFailure);
|
||||
|
||||
if (mDone) {
|
||||
if (!mError) {
|
||||
if (!mRejected) {
|
||||
mFunctors->Succeed(mValue);
|
||||
} else {
|
||||
mFunctors->Fail(*mError);
|
||||
mFunctors->Fail(mError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,22 +110,11 @@ public:
|
||||
mValue = aValue;
|
||||
Resolve();
|
||||
}
|
||||
protected:
|
||||
void Resolve()
|
||||
{
|
||||
if (!mDone) {
|
||||
mDone = true;
|
||||
MOZ_ASSERT(!mError);
|
||||
if (mFunctors) {
|
||||
mFunctors->Succeed(mValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Reject(ErrorType rv)
|
||||
{
|
||||
if (!mDone) {
|
||||
mDone = true;
|
||||
mDone = mRejected = true;
|
||||
mError = rv;
|
||||
if (mFunctors) {
|
||||
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:
|
||||
void Resolve()
|
||||
{
|
||||
if (!mDone) {
|
||||
mDone = true;
|
||||
MOZ_ASSERT(mError == NS_OK);
|
||||
MOZ_ASSERT(!mRejected);
|
||||
if (mFunctors) {
|
||||
mFunctors->Succeed(mValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Reject(nsresult error)
|
||||
{
|
||||
if (!mDone) {
|
||||
mDone = true;
|
||||
mError = error;
|
||||
if (mFunctors) {
|
||||
mFunctors->Fail(mError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ValueType mValue;
|
||||
private:
|
||||
~Pledge() {};
|
||||
bool mDone;
|
||||
nsresult mError;
|
||||
bool mRejected;
|
||||
ErrorType mError;
|
||||
ScopedDeletePtr<FunctorsBase> mFunctors;
|
||||
};
|
||||
|
||||
|
@ -234,7 +234,6 @@ function setupEnvironment() {
|
||||
window.finish = () => SimpleTest.finish();
|
||||
SpecialPowers.pushPrefEnv({
|
||||
'set': [
|
||||
['canvas.capturestream.enabled', true],
|
||||
['media.peerconnection.enabled', true],
|
||||
['media.peerconnection.identity.enabled', true],
|
||||
['media.peerconnection.identity.timeout', 120000],
|
||||
|
@ -16,9 +16,13 @@ function mustSucceed(msg, f) {
|
||||
e => is(e.name, null, msg + " must succeed: " + e.message));
|
||||
}
|
||||
|
||||
function mustFailWith(msg, reason, f) {
|
||||
return f().then(() => ok(false, msg + " must fail"),
|
||||
e => is(e.name, reason, msg + " must fail: " + e.message));
|
||||
function mustFailWith(msg, reason, constraint, f) {
|
||||
return f().then(() => ok(false, msg + " must fail"), e => {
|
||||
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));
|
||||
@ -49,13 +53,15 @@ runTest(() =>
|
||||
audio: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
|
||||
fake: true,
|
||||
})))
|
||||
.then(() => mustFailWith("unknown exact deviceId on video", "NotFoundError",
|
||||
() => navigator.mediaDevices.getUserMedia({
|
||||
.then(() => mustFailWith("unknown exact deviceId on video",
|
||||
"OverconstrainedError", "deviceId",
|
||||
() => navigator.mediaDevices.getUserMedia({
|
||||
video: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
|
||||
fake: true,
|
||||
})))
|
||||
.then(() => mustFailWith("unknown exact deviceId on audio", "NotFoundError",
|
||||
() => navigator.mediaDevices.getUserMedia({
|
||||
.then(() => mustFailWith("unknown exact deviceId on audio",
|
||||
"OverconstrainedError", "deviceId",
|
||||
() => navigator.mediaDevices.getUserMedia({
|
||||
audio: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
|
||||
fake: true,
|
||||
})))
|
||||
|
@ -39,9 +39,14 @@ var tests = [
|
||||
{ message: "browser screensharing requires permission",
|
||||
constraints: { video: { mediaSource: 'browser' } },
|
||||
error: "PermissionDeniedError" },
|
||||
{ message: "unknown mediaSource fails",
|
||||
{ message: "unknown mediaSource in video fails",
|
||||
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",
|
||||
constraints: { },
|
||||
error: "NotSupportedError" },
|
||||
@ -106,8 +111,18 @@ runTest(function() {
|
||||
|
||||
return tests.reduce((p, test) =>
|
||||
p.then(() => navigator.mediaDevices.getUserMedia(test.constraints))
|
||||
.then(() => is(null, test.error, test.message),
|
||||
e => is(e.name, test.error, test.message + ": " + e.message)), p);
|
||||
.then(() => is(null, test.error, test.message), e => {
|
||||
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>
|
||||
|
@ -111,6 +111,11 @@ public:
|
||||
/* Stop the device and release the corresponding MediaStream */
|
||||
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. */
|
||||
virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
|
||||
bool aAgcOn, uint32_t aAGC,
|
||||
|
@ -201,6 +201,14 @@ MediaEngineDefaultVideoSource::Stop(SourceMediaStream *aSource, TrackID aID)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineDefaultVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MediaEngineDefaultVideoSource::Notify(nsITimer* aTimer)
|
||||
{
|
||||
@ -470,6 +478,14 @@ MediaEngineDefaultAudioSource::Stop(SourceMediaStream *aSource, TrackID aID)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineDefaultAudioSource::Restart(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MediaEngineDefaultAudioSource::Notify(nsITimer* aTimer)
|
||||
{
|
||||
|
@ -49,6 +49,9 @@ public:
|
||||
virtual nsresult Deallocate() override;
|
||||
virtual nsresult Start(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 nsresult Config(bool aEchoOn, uint32_t aEcho,
|
||||
bool aAgcOn, uint32_t aAGC,
|
||||
@ -119,6 +122,9 @@ public:
|
||||
virtual nsresult Deallocate() override;
|
||||
virtual nsresult Start(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 nsresult Config(bool aEchoOn, uint32_t aEcho,
|
||||
bool aAgcOn, uint32_t aAGC,
|
||||
|
@ -328,6 +328,14 @@ MediaEngineGonkVideoSource::Stop(SourceMediaStream* aSource, TrackID aID)
|
||||
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
|
||||
* constructor and destructor respectively.
|
||||
|
@ -66,6 +66,9 @@ public:
|
||||
virtual nsresult Deallocate() override;
|
||||
virtual nsresult Start(SourceMediaStream* aStream, 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,
|
||||
SourceMediaStream* aSource,
|
||||
TrackID aId,
|
||||
|
@ -212,6 +212,31 @@ MediaEngineRemoteVideoSource::Stop(mozilla::SourceMediaStream* aSource,
|
||||
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
|
||||
MediaEngineRemoteVideoSource::NotifyPull(MediaStreamGraph* aGraph,
|
||||
SourceMediaStream* aSource,
|
||||
|
@ -71,6 +71,9 @@ public:
|
||||
virtual nsresult Deallocate() override;;
|
||||
virtual nsresult Start(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,
|
||||
SourceMediaStream* aSource,
|
||||
TrackID aId,
|
||||
|
@ -291,6 +291,15 @@ MediaEngineTabVideoSource::Stop(mozilla::SourceMediaStream*, mozilla::TrackID)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineTabVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
|
||||
const mozilla::MediaEnginePrefs& aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
// TODO
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineTabVideoSource::Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t)
|
||||
{
|
||||
|
@ -29,6 +29,9 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
|
||||
virtual void SetDirectListeners(bool aHasDirectListeners) override {};
|
||||
virtual void NotifyPull(mozilla::MediaStreamGraph*, mozilla::SourceMediaStream*, mozilla::TrackID, mozilla::StreamTime) 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 bool IsFake() override;
|
||||
virtual const dom::MediaSourceEnum GetMediaSource() override {
|
||||
|
@ -84,6 +84,9 @@ public:
|
||||
}
|
||||
nsresult Start(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
|
||||
{}
|
||||
nsresult Config(bool aEchoOn, uint32_t aEcho, bool aAgcOn,
|
||||
@ -155,6 +158,9 @@ public:
|
||||
virtual nsresult Deallocate() override;
|
||||
virtual nsresult Start(SourceMediaStream* aStream, 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 nsresult Config(bool aEchoOn, uint32_t aEcho,
|
||||
bool aAgcOn, uint32_t aAGC,
|
||||
|
@ -422,6 +422,14 @@ MediaEngineWebRTCMicrophoneSource::Stop(SourceMediaStream *aSource, TrackID aID)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineWebRTCMicrophoneSource::Restart(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
MediaEngineWebRTCMicrophoneSource::NotifyPull(MediaStreamGraph *aGraph,
|
||||
SourceMediaStream *aSource,
|
||||
@ -664,6 +672,15 @@ MediaEngineWebRTCAudioCaptureSource::Stop(SourceMediaStream *aMediaStream,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineWebRTCAudioCaptureSource::Restart(
|
||||
const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs &aPrefs,
|
||||
const nsString& aDeviceId)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
MediaEngineWebRTCAudioCaptureSource::GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include "mozilla/dom/MediaTrackConstraintSetBinding.h"
|
||||
#include "mozilla/dom/MediaTrackSupportedConstraintsBinding.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
template<class EnumValuesStrings, class Enum>
|
||||
@ -104,6 +106,128 @@ protected:
|
||||
GetMinimumFitnessDistance(const dom::MediaTrackConstraintSet &aConstraints,
|
||||
bool aAdvanced,
|
||||
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
|
||||
|
@ -24,8 +24,6 @@ parent:
|
||||
|
||||
child:
|
||||
CallbackAccept(PTCPSocket socket);
|
||||
CallbackError(nsString message, nsString filename,
|
||||
uint32_t lineNumber, uint32_t columnNumber);
|
||||
__delete__();
|
||||
};
|
||||
|
||||
|
@ -13,11 +13,12 @@ using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
|
||||
|
||||
struct TCPError {
|
||||
nsString name;
|
||||
nsString message;
|
||||
};
|
||||
|
||||
union SendableData {
|
||||
uint8_t[];
|
||||
nsString;
|
||||
nsCString;
|
||||
};
|
||||
|
||||
union CallbackData {
|
||||
@ -38,7 +39,13 @@ parent:
|
||||
// Forward calling to child's open() method to parent, expect TCPOptions
|
||||
// is expanded to |useSSL| (from TCPOptions.useSecureTransport) and
|
||||
// |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
|
||||
// data and update it's trackingNumber.
|
||||
@ -58,7 +65,7 @@ parent:
|
||||
|
||||
child:
|
||||
// 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.
|
||||
// trackingNumber is also passed back to child to ensure the bufferedAmount
|
||||
|
196
dom/network/TCPServerSocket.cpp
Normal file
196
dom/network/TCPServerSocket.cpp
Normal 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);
|
||||
}
|
83
dom/network/TCPServerSocket.h
Normal file
83
dom/network/TCPServerSocket.h
Normal 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
|
@ -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]);
|
@ -6,10 +6,10 @@
|
||||
|
||||
#include "TCPServerSocketChild.h"
|
||||
#include "TCPSocketChild.h"
|
||||
#include "TCPServerSocket.h"
|
||||
#include "mozilla/net/NeckoChild.h"
|
||||
#include "mozilla/dom/PBrowserChild.h"
|
||||
#include "mozilla/dom/TabChild.h"
|
||||
#include "nsIDOMTCPSocket.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "jsfriendapi.h"
|
||||
|
||||
@ -23,7 +23,6 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPServerSocketChildBase)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPServerSocketChildBase)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPServerSocketChildBase)
|
||||
NS_INTERFACE_MAP_ENTRY(nsITCPServerSocketChild)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
@ -46,18 +45,12 @@ NS_IMETHODIMP_(MozExternalRefCountType) TCPServerSocketChild::Release(void)
|
||||
return refcnt;
|
||||
}
|
||||
|
||||
TCPServerSocketChild::TCPServerSocketChild()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TCPServerSocketChild::Listen(nsITCPServerSocketInternal* aServerSocket, uint16_t aLocalPort,
|
||||
uint16_t aBacklog, const nsAString & aBinaryType, JSContext* aCx)
|
||||
TCPServerSocketChild::TCPServerSocketChild(TCPServerSocket* aServerSocket, uint16_t aLocalPort,
|
||||
uint16_t aBacklog, bool aUseArrayBuffers)
|
||||
{
|
||||
mServerSocket = aServerSocket;
|
||||
AddIPDLReference();
|
||||
gNeckoChild->SendPTCPServerSocketConstructor(this, aLocalPort, aBacklog, nsString(aBinaryType));
|
||||
return NS_OK;
|
||||
gNeckoChild->SendPTCPServerSocketConstructor(this, aLocalPort, aBacklog, aUseArrayBuffers);
|
||||
}
|
||||
|
||||
void
|
||||
@ -83,34 +76,16 @@ TCPServerSocketChild::~TCPServerSocketChild()
|
||||
bool
|
||||
TCPServerSocketChild::RecvCallbackAccept(PTCPSocketChild *psocket)
|
||||
{
|
||||
TCPSocketChild* socket = static_cast<TCPSocketChild*>(psocket);
|
||||
|
||||
nsresult rv = mServerSocket->CallListenerAccept(static_cast<nsITCPSocketChild*>(socket));
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("CallListenerAccept threw exception.");
|
||||
}
|
||||
nsRefPtr<TCPSocketChild> socket = static_cast<TCPSocketChild*>(psocket);
|
||||
nsresult rv = mServerSocket->AcceptChildSocket(socket);
|
||||
NS_ENSURE_SUCCESS(rv, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
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
|
||||
void
|
||||
TCPServerSocketChild::Close()
|
||||
{
|
||||
SendClose();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
|
@ -4,8 +4,10 @@
|
||||
* 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_TCPServerSocketChild_h
|
||||
#define mozilla_dom_TCPServerSocketChild_h
|
||||
|
||||
#include "mozilla/net/PTCPServerSocketChild.h"
|
||||
#include "nsITCPServerSocketChild.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
@ -17,7 +19,9 @@ class nsITCPServerSocketInternal;
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class TCPServerSocketChildBase : public nsITCPServerSocketChild {
|
||||
class TCPServerSocket;
|
||||
|
||||
class TCPServerSocketChildBase : public nsISupports {
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS(TCPServerSocketChildBase)
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
@ -29,7 +33,7 @@ protected:
|
||||
TCPServerSocketChildBase();
|
||||
virtual ~TCPServerSocketChildBase();
|
||||
|
||||
nsCOMPtr<nsITCPServerSocketInternal> mServerSocket;
|
||||
nsRefPtr<TCPServerSocket> mServerSocket;
|
||||
bool mIPCOpen;
|
||||
};
|
||||
|
||||
@ -37,18 +41,18 @@ class TCPServerSocketChild : public mozilla::net::PTCPServerSocketChild
|
||||
, public TCPServerSocketChildBase
|
||||
{
|
||||
public:
|
||||
NS_DECL_NSITCPSERVERSOCKETCHILD
|
||||
NS_IMETHOD_(MozExternalRefCountType) Release() override;
|
||||
|
||||
TCPServerSocketChild();
|
||||
TCPServerSocketChild(TCPServerSocket* aServerSocket, uint16_t aLocalPort,
|
||||
uint16_t aBacklog, bool aUseArrayBuffers);
|
||||
~TCPServerSocketChild();
|
||||
|
||||
void Close();
|
||||
|
||||
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 mozilla
|
||||
|
||||
#endif // mozilla_dom_TCPServerSocketChild_h
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user