Merge m-c to b2g-inbound. a=merge

This commit is contained in:
Ryan VanderMeulen 2015-06-12 14:59:58 -04:00
Родитель 73f701718b ee56f8bd59
Коммит 551f69887c
513 изменённых файлов: 6942 добавлений и 1388 удалений

Просмотреть файл

@ -42,7 +42,7 @@
<hbox id="clientBox">
<vbox id="leftBox" flex="1"/>
<vbox id="rightBox" flex="1">
#expand <label id="version">__MOZ_APP_VERSION__</label>
#expand <label id="version">__MOZ_APP_VERSION_ABOUT__</label>
<label id="distribution" class="text-blurb"/>
<label id="distributionId" class="text-blurb"/>

Просмотреть файл

@ -9,7 +9,7 @@ var FullScreen = {
_MESSAGES: [
"DOMFullscreen:Request",
"DOMFullscreen:NewOrigin",
"DOMFullscreen:Exited",
"DOMFullscreen:Exit",
],
init: function() {
@ -112,25 +112,27 @@ var FullScreen = {
this.cancelWarning();
break;
case "MozDOMFullscreen:Entered": {
// The original target is the element which requested the DOM
// The event target is the element which requested the DOM
// fullscreen. If we were entering DOM fullscreen for a remote
// browser, this element would be that browser element, which
// was the parameter of `remoteFrameFullscreenChanged` call.
// If the fullscreen request was initiated from an in-process
// browser, we need to get its corresponding browser element.
let originalTarget = event.originalTarget;
// browser, the target would be `gBrowser` and the original
// target would be the browser which was the parameter of
// `remoteFrameFullscreenChanged` call. If the fullscreen
// request was initiated from an in-process browser, we need
// to get its corresponding browser here.
let browser;
if (this._isBrowser(originalTarget)) {
browser = originalTarget;
if (event.target == gBrowser) {
browser = event.originalTarget;
} else {
let topWin = originalTarget.ownerDocument.defaultView.top;
let topWin = event.target.ownerDocument.defaultView.top;
browser = gBrowser.getBrowserForContentWindow(topWin);
if (!browser) {
document.mozCancelFullScreen();
break;
}
}
this.enterDomFullscreen(browser);
if (!this.enterDomFullscreen(browser)) {
break;
}
// If it is a remote browser, send a message to ask the content
// to enter fullscreen state. We don't need to do so if it is an
// in-process browser, since all related document should have
@ -157,13 +159,8 @@ var FullScreen = {
this.showWarning(aMessage.data.originNoSuffix);
break;
}
case "DOMFullscreen:Exited": {
// Like entering DOM fullscreen, we also need to exit fullscreen
// at the operating system level in the parent process here.
if (this._isRemoteBrowser(browser)) {
this._windowUtils.remoteFrameFullscreenReverted();
}
this.cleanupDomFullscreen();
case "DOMFullscreen:Exit": {
this._windowUtils.remoteFrameFullscreenReverted();
break;
}
}
@ -171,7 +168,7 @@ var FullScreen = {
enterDomFullscreen : function(aBrowser) {
if (!document.mozFullScreen)
return;
return false;
// If we've received a fullscreen notification, we have to ensure that the
// element that's requesting fullscreen belongs to the browser that's currently
@ -179,7 +176,7 @@ var FullScreen = {
// actually visible now.
if (gBrowser.selectedBrowser != aBrowser) {
document.mozCancelFullScreen();
return;
return false;
}
let focusManager = Services.focus;
@ -187,7 +184,7 @@ var FullScreen = {
// The top-level window has lost focus since the request to enter
// full-screen was made. Cancel full-screen.
document.mozCancelFullScreen();
return;
return false;
}
document.documentElement.setAttribute("inDOMFullscreen", true);
@ -209,6 +206,7 @@ var FullScreen = {
// the toolbar hide immediately.
this.hideNavToolbox(true);
this._fullScrToggler.hidden = true;
return true;
},
cleanup: function () {
@ -240,13 +238,6 @@ var FullScreen = {
.broadcastAsyncMessage("DOMFullscreen:CleanUp");
},
_isBrowser: function (aNode) {
if (aNode.tagName != "xul:browser") {
return false;
}
let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
return aNode.nodePrincipal == systemPrincipal;
},
_isRemoteBrowser: function (aBrowser) {
return gMultiProcessBrowser && aBrowser.getAttribute("remote") == "true";
},

Просмотреть файл

@ -522,38 +522,38 @@ let LoopUI;
* has been fetched.
*/
getFavicon: function(callback) {
let favicon = gBrowser.getIcon(gBrowser.selectedTab);
// If the tab image's url starts with http(s), fetch icon from favicon
// service via the moz-anno protocol.
if (/^https?:/.test(favicon)) {
let faviconURI = makeURI(favicon);
favicon = this.favIconService.getFaviconLinkForIcon(faviconURI).spec;
}
if (!favicon) {
callback(new Error("No favicon found"));
let pageURI = gBrowser.selectedTab.linkedBrowser.currentURI.spec;
// If the tab pages url starts with http(s), fetch icon.
if (!/^https?:/.test(pageURI)) {
callback();
return;
}
favicon = this.PlacesUtils.getImageURLForResolution(window, favicon);
// We XHR the favicon to get a File object, which we can pass to the FileReader
// object. The FileReader turns the File object into a data-uri.
let xhr = new XMLHttpRequest();
xhr.open("get", favicon, true);
xhr.responseType = "blob";
xhr.overrideMimeType("image/x-icon");
xhr.onload = () => {
if (xhr.status != 200) {
callback(new Error("Invalid status code received for favicon XHR: " + xhr.status));
return;
}
this.PlacesUtils.promiseFaviconLinkUrl(pageURI).then(uri => {
uri = this.PlacesUtils.getImageURLForResolution(window, uri.spec);
let reader = new FileReader();
reader.onload = () => callback(null, reader.result);
reader.onerror = callback;
reader.readAsDataURL(xhr.response);
};
xhr.onerror = callback;
xhr.send();
// We XHR the favicon to get a File object, which we can pass to the FileReader
// object. The FileReader turns the File object into a data-uri.
let xhr = new XMLHttpRequest();
xhr.open("get", uri, true);
xhr.responseType = "blob";
xhr.overrideMimeType("image/x-icon");
xhr.onload = () => {
if (xhr.status != 200) {
callback(new Error("Invalid status code received for favicon XHR: " + xhr.status));
return;
}
let reader = new FileReader();
reader.onload = reader.onload = () => callback(null, reader.result);
reader.onerror = callback;
reader.readAsDataURL(xhr.response);
};
xhr.onerror = callback;
xhr.send();
}).catch(err => {
callback(err || new Error("No favicon found"));
});
}
};
})();
@ -563,5 +563,3 @@ XPCOMUtils.defineLazyModuleGetter(LoopUI, "LoopRooms", "resource:///modules/loop
XPCOMUtils.defineLazyModuleGetter(LoopUI, "MozLoopService", "resource:///modules/loop/MozLoopService.jsm");
XPCOMUtils.defineLazyModuleGetter(LoopUI, "PanelFrame", "resource:///modules/PanelFrame.jsm");
XPCOMUtils.defineLazyModuleGetter(LoopUI, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(LoopUI, "favIconService",
"@mozilla.org/browser/favicon-service;1", "nsIFaviconService");

Просмотреть файл

@ -34,8 +34,8 @@
* along any dimension beyond the point at which an overflow event would
* occur". But none of -moz-{fit,max,min}-content do what we want here. So..
*/
min-width: 320px;
min-height: 280px;
min-width: 260px;
min-height: 315px;
}
#main-window[customize-entered] {
@ -907,8 +907,32 @@ chatbox {
width: 260px; /* CHAT_WIDTH_OPEN in socialchat.xml */
}
chatbox[large="true"] {
width: 300px;
chatbox[customSize] {
width: 300px; /* CHAT_WIDTH_OPEN_ALT in socialchat.xml */
}
#chat-window[customSize] {
min-width: 300px;
}
chatbox[customSize="loopChatEnabled"] {
/* 325px as defined per UX */
height: 325px;
}
#chat-window[customSize="loopChatEnabled"] {
/* 325px + 30px top bar height. */
min-height: calc(325px + 30px);
}
chatbox[customSize="loopChatMessageAppended"] {
/* 445px as defined per UX */
height: 445px;
}
#chat-window[customSize="loopChatMessageAppended"] {
/* 445px + 30px top bar height. */
min-height: calc(445px + 30px);
}
chatbox[minimized="true"] {
@ -922,6 +946,15 @@ chatbar {
max-height: 0;
}
.chatbar-innerbox {
margin: -285px 0 0;
}
chatbar[customSize] > .chatbar-innerbox {
/* 425px to make room for the maximum custom-size chatbox; currently 'loopChatMessageAppended'. */
margin-top: -425px;
}
/* Apply crisp rendering for favicons at exactly 2dppx resolution */
@media (resolution: 2dppx) {
#social-sidebar-favico,

Просмотреть файл

@ -180,6 +180,7 @@ Sanitizer.prototype = {
clear: function ()
{
TelemetryStopwatch.start("FX_SANITIZE_COOKIES");
TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2");
var cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
.getService(Ci.nsICookieManager);
@ -199,12 +200,15 @@ Sanitizer.prototype = {
cookieMgr.removeAll();
}
TelemetryStopwatch.finish("FX_SANITIZE_COOKIES_2");
// Clear deviceIds. Done asynchronously (returns before complete).
let mediaMgr = Components.classes["@mozilla.org/mediaManagerService;1"]
.getService(Ci.nsIMediaManagerService);
mediaMgr.sanitizeDeviceIds(this.range && this.range[0]);
// Clear plugin data.
TelemetryStopwatch.start("FX_SANITIZE_PLUGINS");
const phInterface = Ci.nsIPluginHost;
const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
@ -234,6 +238,7 @@ Sanitizer.prototype = {
}
}
TelemetryStopwatch.finish("FX_SANITIZE_PLUGINS");
TelemetryStopwatch.finish("FX_SANITIZE_COOKIES");
},

Просмотреть файл

@ -160,8 +160,7 @@
<parameter name="aTarget"/>
<body><![CDATA[
aTarget.setAttribute("label", this.contentDocument.title);
if (this.getAttribute("dark") == "true")
aTarget.setAttribute("dark", "true");
aTarget.src = this.src;
aTarget.content.setAttribute("origin", this.content.getAttribute("origin"));
aTarget.content.popupnotificationanchor.className = this.content.popupnotificationanchor.className;
@ -169,6 +168,16 @@
]]></body>
</method>
<method name="setDecorationAttributes">
<parameter name="aTarget"/>
<body><![CDATA[
for (let attr of ["dark", "customSize"]) {
if (this.hasAttribute(attr))
aTarget.setAttribute(attr, this.getAttribute(attr));
}
]]></body>
</method>
<method name="onTitlebarClick">
<parameter name="aEvent"/>
<body><![CDATA[
@ -211,6 +220,8 @@
let chatbar = win.document.getElementById("pinnedchats");
let origin = this.content.getAttribute("origin");
let cb = chatbar.openChat(origin, title, "about:blank");
this.setDecorationAttributes(cb);
cb.promiseChatLoaded.then(
() => {
this.swapDocShells(cb);
@ -225,6 +236,12 @@
chatbar.chatboxForURL.delete("about:blank");
chatbar.chatboxForURL.set(this.src, Cu.getWeakReference(cb));
let attachEvent = new cb.contentWindow.CustomEvent("socialFrameAttached", {
bubbles: true,
cancelable: true,
});
cb.contentDocument.dispatchEvent(attachEvent);
deferred.resolve(cb);
}
);
@ -457,8 +474,10 @@
// These are from the CSS for the chatbox and must be kept in sync.
// We can't use calcTotalWidthOf due to the transitions...
const CHAT_WIDTH_OPEN = 260;
const CHAT_WIDTH_OPEN_ALT = 300;
const CHAT_WIDTH_MINIMIZED = 160;
let openWidth = aChatbox.hasAttribute("large") ? 300 : CHAT_WIDTH_OPEN;
let openWidth = aChatbox.hasAttribute("customSize") ?
CHAT_WIDTH_OPEN_ALT : CHAT_WIDTH_OPEN;
return aChatbox.minimized ? CHAT_WIDTH_MINIMIZED : openWidth;
]]></body>
@ -684,18 +703,27 @@
if (event.target != otherWin.document)
return;
if (aChatbox.hasAttribute("customSize")) {
otherWin.document.getElementById("chat-window").
setAttribute("customSize", aChatbox.getAttribute("customSize"));
}
let document = aChatbox.contentDocument;
let detachEvent = new aChatbox.contentWindow.CustomEvent("socialFrameDetached", {
bubbles: true,
cancelable: true,
});
aChatbox.contentDocument.dispatchEvent(detachEvent);
otherWin.removeEventListener("load", _chatLoad, true);
let otherChatbox = otherWin.document.getElementById("chatter");
aChatbox.setDecorationAttributes(otherChatbox);
aChatbox.swapDocShells(otherChatbox);
aChatbox.close();
chatbar.chatboxForURL.set(aChatbox.src, Cu.getWeakReference(otherChatbox));
// All processing is done, now we can fire the event.
document.dispatchEvent(detachEvent);
deferred.resolve(otherChatbox);
}, true);
return deferred.promise;

Просмотреть файл

@ -594,7 +594,7 @@ let DOMFullscreenHandler = {
addMessageListener("DOMFullscreen:CleanUp", this);
addEventListener("MozDOMFullscreen:Request", this);
addEventListener("MozDOMFullscreen:NewOrigin", this);
addEventListener("MozDOMFullscreen:Exited", this);
addEventListener("MozDOMFullscreen:Exit", this);
},
get _windowUtils() {
@ -610,7 +610,7 @@ let DOMFullscreenHandler = {
// If we don't actually have any pending fullscreen request
// to handle, neither we have been in fullscreen, tell the
// parent to just exit.
sendAsyncMessage("DOMFullscreen:Exited");
sendAsyncMessage("DOMFullscreen:Exit");
}
break;
}
@ -643,8 +643,8 @@ let DOMFullscreenHandler = {
});
break;
}
case "MozDOMFullscreen:Exited": {
sendAsyncMessage("DOMFullscreen:Exited");
case "MozDOMFullscreen:Exit": {
sendAsyncMessage("DOMFullscreen:Exit");
break;
}
}

Просмотреть файл

@ -25,6 +25,7 @@ BROWSER_CHROME_MANIFESTS += [
]
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
DEFINES['MOZ_APP_VERSION_ABOUT'] = CONFIG['MOZ_APP_VERSION_ABOUT']
DEFINES['APP_LICENSE_BLOCK'] = '%s/content/overrides/app-license.html' % SRCDIR

Просмотреть файл

@ -269,10 +269,6 @@ html[dir="rtl"] .new-room-view > .context > .context-content > .context-preview
float: left;
}
.new-room-view > .context > .context-content > .context-preview[src=""] {
display: none;
}
.new-room-view > .context > .context-content > .context-description {
flex: 0 1 auto;
display: block;

Просмотреть файл

@ -29,7 +29,7 @@ loop.conversationViews = (function(mozL10n) {
if (!contact.email || contact.email.length === 0) {
return { value: "" };
}
return contact.email.find(e => e.pref) || contact.email[0];
return contact.email.find(function find(e) { return e.pref; }) || contact.email[0];
}
function _getContactDisplayName(contact) {

Просмотреть файл

@ -29,7 +29,7 @@ loop.conversationViews = (function(mozL10n) {
if (!contact.email || contact.email.length === 0) {
return { value: "" };
}
return contact.email.find(e => e.pref) || contact.email[0];
return contact.email.find(function find(e) { return e.pref; }) || contact.email[0];
}
function _getContactDisplayName(contact) {

Просмотреть файл

@ -500,9 +500,8 @@ loop.panel = (function(_, mozL10n) {
return (
React.createElement("div", {className: "room-entry-context-item"},
React.createElement("a", {href: roomUrl.location, onClick: this.handleClick},
React.createElement("img", {title: roomUrl.description,
src: roomUrl.thumbnail})
React.createElement("a", {href: roomUrl.location, title: roomUrl.description, onClick: this.handleClick},
React.createElement("img", {src: roomUrl.thumbnail || "loop/shared/img/icons-16x16.svg#globe"})
)
)
);
@ -766,6 +765,7 @@ loop.panel = (function(_, mozL10n) {
hide: !hostname ||
!this.props.mozLoop.getLoopPref("contextInConversations.enabled")
});
var thumbnail = this.state.previewImage || "loop/shared/img/icons-16x16.svg#globe";
return (
React.createElement("div", {className: "new-room-view"},
@ -773,7 +773,7 @@ loop.panel = (function(_, mozL10n) {
React.createElement(Checkbox, {label: mozL10n.get("context_inroom_label"),
onChange: this.onCheckboxChange}),
React.createElement("div", {className: "context-content"},
React.createElement("img", {className: "context-preview", src: this.state.previewImage}),
React.createElement("img", {className: "context-preview", src: thumbnail}),
React.createElement("span", {className: "context-description"},
this.state.description,
React.createElement("span", {className: "context-url"}, hostname)

Просмотреть файл

@ -500,9 +500,8 @@ loop.panel = (function(_, mozL10n) {
return (
<div className="room-entry-context-item">
<a href={roomUrl.location} onClick={this.handleClick}>
<img title={roomUrl.description}
src={roomUrl.thumbnail} />
<a href={roomUrl.location} title={roomUrl.description} onClick={this.handleClick}>
<img src={roomUrl.thumbnail || "loop/shared/img/icons-16x16.svg#globe"} />
</a>
</div>
);
@ -766,6 +765,7 @@ loop.panel = (function(_, mozL10n) {
hide: !hostname ||
!this.props.mozLoop.getLoopPref("contextInConversations.enabled")
});
var thumbnail = this.state.previewImage || "loop/shared/img/icons-16x16.svg#globe";
return (
<div className="new-room-view">
@ -773,7 +773,7 @@ loop.panel = (function(_, mozL10n) {
<Checkbox label={mozL10n.get("context_inroom_label")}
onChange={this.onCheckboxChange} />
<div className="context-content">
<img className="context-preview" src={this.state.previewImage}/>
<img className="context-preview" src={thumbnail} />
<span className="context-description">
{this.state.description}
<span className="context-url">{hostname}</span>

Просмотреть файл

@ -480,7 +480,7 @@ loop.roomViews = (function(mozL10n) {
}
var url = this._getURL();
var thumbnail = url && url.thumbnail || "";
var thumbnail = url && url.thumbnail || "loop/shared/img/icons-16x16.svg#globe";
var urlDescription = url && url.description || "";
var location = url && url.location || "";
var locationData = null;
@ -718,7 +718,6 @@ loop.roomViews = (function(mozL10n) {
return (
React.createElement("div", {className: "room-conversation-wrapper"},
React.createElement(sharedViews.TextChatView, {dispatcher: this.props.dispatcher}),
React.createElement(DesktopRoomInvitationView, {
dispatcher: this.props.dispatcher,
error: this.state.error,
@ -761,7 +760,8 @@ loop.roomViews = (function(mozL10n) {
savingContext: this.state.savingContext,
mozLoop: this.props.mozLoop,
roomData: roomData,
show: !shouldRenderInvitationOverlay && shouldRenderContextView})
show: !shouldRenderInvitationOverlay && shouldRenderContextView}),
React.createElement(sharedViews.TextChatView, {dispatcher: this.props.dispatcher})
)
);
}

Просмотреть файл

@ -480,7 +480,7 @@ loop.roomViews = (function(mozL10n) {
}
var url = this._getURL();
var thumbnail = url && url.thumbnail || "";
var thumbnail = url && url.thumbnail || "loop/shared/img/icons-16x16.svg#globe";
var urlDescription = url && url.description || "";
var location = url && url.location || "";
var locationData = null;
@ -553,7 +553,7 @@ loop.roomViews = (function(mozL10n) {
<div className="room-context-label">{mozL10n.get("context_inroom_label")}</div>
<div className="room-context-content"
onClick={this.handleContextClick}>
<img className="room-context-thumbnail" src={thumbnail}/>
<img className="room-context-thumbnail" src={thumbnail} />
<div className="room-context-description"
title={urlDescription}>
{this._truncate(urlDescription)}
@ -718,7 +718,6 @@ loop.roomViews = (function(mozL10n) {
return (
<div className="room-conversation-wrapper">
<sharedViews.TextChatView dispatcher={this.props.dispatcher} />
<DesktopRoomInvitationView
dispatcher={this.props.dispatcher}
error={this.state.error}
@ -762,6 +761,7 @@ loop.roomViews = (function(mozL10n) {
mozLoop={this.props.mozLoop}
roomData={roomData}
show={!shouldRenderInvitationOverlay && shouldRenderContextView} />
<sharedViews.TextChatView dispatcher={this.props.dispatcher} />
</div>
);
}

Просмотреть файл

@ -651,27 +651,6 @@ html, .fx-embedded, #main,
height: 100%;
}
/**
* The .fx-embbeded .text-chat-* styles are very temporarily whilst we work on
* text chat (bug 1108892 and dependencies).
*/
.fx-embedded .text-chat-view {
height: 60px;
color: white;
background-color: black;
}
.fx-embedded .text-chat-entries {
/* XXX Should use flex, this is just for the initial implementation. */
height: calc(100% - 2em);
width: 100%;
}
.fx-embedded .text-chat-box {
width: 100%;
margin: auto;
}
/* We use 641px rather than 640, as min-width and max-width are inclusive */
@media screen and (min-width:641px) {
.standalone .conversation-toolbar {
@ -681,10 +660,6 @@ html, .fx-embedded, #main,
right: 0;
}
.fx-embedded .local-stream {
position: fixed;
}
.standalone .local-stream,
.standalone .remote-inset-stream {
position: absolute;
@ -697,7 +672,7 @@ html, .fx-embedded, #main,
}
/* Nested video elements */
.conversation .media.nested {
.standalone .conversation .media.nested {
position: relative;
height: 100%;
}
@ -744,7 +719,7 @@ html, .fx-embedded, #main,
}
/* Nested video elements */
.conversation .media.nested {
.standalone .conversation .media.nested {
display: flex;
flex-direction: column;
align-items: center;
@ -969,10 +944,6 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
flex: 0 1 auto;
}
.room-context-thumbnail[src=""] {
display: none;
}
.room-context > .error-display-area.error {
display: block;
background-color: rgba(215,67,69,.8);
@ -1211,9 +1182,8 @@ html[dir="rtl"] .room-context-btn-edit {
.standalone-context-url > img {
margin: 1em auto;
max-width: 50%;
/* allows 20% for the description wrapper plus the margins */
max-height: calc(80% - 2em);
width: 16px;
height: 16px;
}
.standalone-context-url-description-wrapper {
@ -1244,18 +1214,64 @@ html[dir="rtl"] .room-context-btn-edit {
height: auto;
}
/* Text chat in rooms styles */
.fx-embedded .room-conversation-wrapper {
display: flex;
flex-flow: column nowrap;
}
.fx-embedded .video-layout-wrapper {
flex: 1 1 auto;
}
.text-chat-view {
background: #fff;
}
.fx-embedded .text-chat-view {
flex: 1 0 auto;
display: flex;
flex-flow: column nowrap;
}
.fx-embedded .text-chat-entries {
flex: 1 1 auto;
max-height: 120px;
min-height: 60px;
padding: .7em .5em 0;
}
.fx-embedded .text-chat-box {
flex: 0 0 auto;
max-height: 40px;
min-height: 40px;
width: 100%;
}
.text-chat-entries {
margin: auto;
overflow: scroll;
border: 1px solid red;
}
.text-chat-entry {
text-align: left;
text-align: end;
margin-bottom: 1.5em;
}
.text-chat-entry > span {
border-width: 1px;
border-style: solid;
border-color: #0095dd;
border-radius: 10000px;
padding: .5em 1em;
}
.text-chat-entry.received {
text-align: right;
text-align: start;
}
.text-chat-entry.received > span {
border-color: #d8d8d8;
}
.text-chat-box {
@ -1264,6 +1280,14 @@ html[dir="rtl"] .room-context-btn-edit {
.text-chat-box > form > input {
width: 100%;
height: 40px;
padding: 0 .5em .5em;
font-size: 1.1em;
}
.fx-embedded .text-chat-box > form > input {
border: 0;
border-top: 1px solid #999;
}
@media screen and (max-width:640px) {

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

До

Ширина:  |  Высота:  |  Размер: 13 KiB

После

Ширина:  |  Высота:  |  Размер: 73 KiB

Просмотреть файл

@ -255,6 +255,10 @@ loop.shared.mixins = (function() {
*/
var DocumentVisibilityMixin = {
_onDocumentVisibilityChanged: function(event) {
if (!this.isMounted()) {
return;
}
var hidden = event.target.hidden;
if (hidden && typeof this.onDocumentHidden === "function") {
this.onDocumentHidden();
@ -267,6 +271,9 @@ loop.shared.mixins = (function() {
componentDidMount: function() {
rootObject.document.addEventListener(
"visibilitychange", this._onDocumentVisibilityChanged);
// Assume that the consumer components is only mounted when the document
// has become visible.
this._onDocumentVisibilityChanged({ target: rootObject.document });
},
componentWillUnmount: function() {

Просмотреть файл

@ -68,6 +68,27 @@ loop.store.TextChatStore = (function() {
*/
dataChannelsAvailable: function() {
this.setStoreState({ textChatEnabled: true });
window.dispatchEvent(new CustomEvent("LoopChatEnabled"));
},
/**
* Appends a message to the store, which may be of type 'sent' or 'received'.
*
* @param {String} type
* @param {sharedActions.ReceivedTextChatMessage|sharedActions.SendTextChatMessage} actionData
*/
_appendTextChatMessage: function(type, actionData) {
// We create a new list to avoid updating the store's state directly,
// which confuses the views.
var message = {
type: type,
contentType: actionData.contentType,
message: actionData.message
};
var newList = this._storeState.messageList.concat(message);
this.setStoreState({ messageList: newList });
window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
},
/**
@ -81,14 +102,8 @@ loop.store.TextChatStore = (function() {
if (actionData.contentType != CHAT_CONTENT_TYPES.TEXT) {
return;
}
// We create a new list to avoid updating the store's state directly,
// which confuses the views.
var newList = this._storeState.messageList.concat({
type: CHAT_MESSAGE_TYPES.RECEIVED,
contentType: actionData.contentType,
message: actionData.message
});
this.setStoreState({ messageList: newList });
this._appendTextChatMessage(CHAT_MESSAGE_TYPES.RECEIVED, actionData);
},
/**
@ -97,15 +112,8 @@ loop.store.TextChatStore = (function() {
* @param {sharedActions.SendTextChatMessage} actionData
*/
sendTextChatMessage: function(actionData) {
// We create a new list to avoid updating the store's state directly,
// which confuses the views.
var newList = this._storeState.messageList.concat({
type: CHAT_MESSAGE_TYPES.SENT,
contentType: actionData.contentType,
message: actionData.message
});
this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SENT, actionData);
this._sdkDriver.sendTextChatMessage(actionData);
this.setStoreState({ messageList: newList });
}
});

Просмотреть файл

@ -29,7 +29,7 @@ loop.shared.views.TextChatView = (function(mozl10n) {
return (
React.createElement("div", {className: classes},
this.props.message
React.createElement("span", null, this.props.message)
)
);
}
@ -49,6 +49,9 @@ loop.shared.views.TextChatView = (function(mozl10n) {
componentWillUpdate: function() {
var node = this.getDOMNode();
if (!node) {
return;
}
// Scroll only if we're right at the bottom of the display.
this.shouldScroll = node.scrollHeight === node.scrollTop + node.clientHeight;
},
@ -64,17 +67,23 @@ loop.shared.views.TextChatView = (function(mozl10n) {
},
render: function() {
if (!this.props.messageList.length) {
return null;
}
return (
React.createElement("div", {className: "text-chat-entries"},
this.props.messageList.map(function(entry, i) {
return (
React.createElement(TextChatEntry, {key: i,
message: entry.message,
type: entry.type})
);
}, this)
React.createElement("div", {className: "text-chat-scroller"},
this.props.messageList.map(function(entry, i) {
return (
React.createElement(TextChatEntry, {key: i,
message: entry.message,
type: entry.type})
);
}, this)
)
)
);
}
@ -134,12 +143,15 @@ loop.shared.views.TextChatView = (function(mozl10n) {
return null;
}
var messageList = this.state.messageList;
return (
React.createElement("div", {className: "text-chat-view"},
React.createElement(TextChatEntriesView, {messageList: this.state.messageList}),
React.createElement(TextChatEntriesView, {messageList: messageList}),
React.createElement("div", {className: "text-chat-box"},
React.createElement("form", {onSubmit: this.handleFormSubmit},
React.createElement("input", {type: "text",
placeholder: messageList.length ? "" : mozl10n.get("chat_textbox_placeholder"),
onKeyDown: this.handleKeyDown,
valueLink: this.linkState("messageDetail")})
)

Просмотреть файл

@ -29,7 +29,7 @@ loop.shared.views.TextChatView = (function(mozl10n) {
return (
<div className={classes}>
{this.props.message}
<span>{this.props.message}</span>
</div>
);
}
@ -49,6 +49,9 @@ loop.shared.views.TextChatView = (function(mozl10n) {
componentWillUpdate: function() {
var node = this.getDOMNode();
if (!node) {
return;
}
// Scroll only if we're right at the bottom of the display.
this.shouldScroll = node.scrollHeight === node.scrollTop + node.clientHeight;
},
@ -64,17 +67,23 @@ loop.shared.views.TextChatView = (function(mozl10n) {
},
render: function() {
if (!this.props.messageList.length) {
return null;
}
return (
<div className="text-chat-entries">
{
this.props.messageList.map(function(entry, i) {
return (
<TextChatEntry key={i}
message={entry.message}
type={entry.type} />
);
}, this)
}
<div className="text-chat-scroller">
{
this.props.messageList.map(function(entry, i) {
return (
<TextChatEntry key={i}
message={entry.message}
type={entry.type} />
);
}, this)
}
</div>
</div>
);
}
@ -134,12 +143,15 @@ loop.shared.views.TextChatView = (function(mozl10n) {
return null;
}
var messageList = this.state.messageList;
return (
<div className="text-chat-view">
<TextChatEntriesView messageList={this.state.messageList} />
<TextChatEntriesView messageList={messageList} />
<div className="text-chat-box">
<form onSubmit={this.handleFormSubmit}>
<input type="text"
placeholder={messageList.length ? "" : mozl10n.get("chat_textbox_placeholder")}
onKeyDown={this.handleKeyDown}
valueLink={this.linkState("messageDetail")} />
</form>

Просмотреть файл

@ -853,27 +853,51 @@ let MozLoopServiceInternal = {
return;
}
chatbox.setAttribute("dark", true);
chatbox.setAttribute("large", true);
chatbox.addEventListener("DOMContentLoaded", function loaded(event) {
if (event.target != chatbox.contentDocument) {
return;
}
chatbox.removeEventListener("DOMContentLoaded", loaded, true);
let chatbar = chatbox.parentNode;
let window = chatbox.contentWindow;
function socialFrameChanged(eventName) {
UITour.availableTargetsCache.clear();
UITour.notify(eventName);
if (eventName == "Loop:ChatWindowDetached" || eventName == "Loop:ChatWindowAttached") {
// After detach, re-attach of the chatbox, refresh its reference so
// we can keep using it here.
let ref = chatbar.chatboxForURL.get(chatbox.src);
chatbox = ref && ref.get() || chatbox;
}
}
window.addEventListener("socialFrameHide", socialFrameChanged.bind(null, "Loop:ChatWindowHidden"));
window.addEventListener("socialFrameShow", socialFrameChanged.bind(null, "Loop:ChatWindowShown"));
window.addEventListener("socialFrameDetached", socialFrameChanged.bind(null, "Loop:ChatWindowDetached"));
window.addEventListener("socialFrameAttached", socialFrameChanged.bind(null, "Loop:ChatWindowAttached"));
window.addEventListener("unload", socialFrameChanged.bind(null, "Loop:ChatWindowClosed"));
const kSizeMap = {
LoopChatEnabled: "loopChatEnabled",
LoopChatMessageAppended: "loopChatMessageAppended"
};
function onChatEvent(event) {
// When the chat box or messages are shown, resize the panel or window
// to be slightly higher to accomodate them.
let customSize = kSizeMap[event.type];
if (customSize) {
chatbox.setAttribute("customSize", customSize);
chatbox.parentNode.setAttribute("customSize", customSize);
}
}
window.addEventListener("LoopChatEnabled", onChatEvent);
window.addEventListener("LoopChatMessageAppended", onChatEvent);
injectLoopAPI(window);
let ourID = window.QueryInterface(Ci.nsIInterfaceRequestor)
@ -918,8 +942,16 @@ let MozLoopServiceInternal = {
}.bind(this), true);
};
if (!Chat.open(null, origin, "", url, undefined, undefined, callback)) {
let chatbox = Chat.open(null, origin, "", url, undefined, undefined, callback);
if (!chatbox) {
return null;
// It's common for unit tests to overload Chat.open.
} else if (chatbox.setAttribute) {
// Set properties that influence visual appeara nce of the chatbox right
// away to circumvent glitches.
chatbox.setAttribute("dark", true);
chatbox.setAttribute("customSize", "loopDefault");
chatbox.parentNode.setAttribute("customSize", "loopDefault");
}
return windowId;
},

Просмотреть файл

@ -262,7 +262,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
return (
React.createElement("div", {className: classes},
React.createElement("img", {src: this.props.roomContextUrl.thumbnail}),
React.createElement("img", {src: this.props.roomContextUrl.thumbnail || "shared/img/icons-16x16.svg#globe"}),
React.createElement("div", {className: "standalone-context-url-description-wrapper"},
this.props.roomContextUrl.description,
React.createElement("br", null), React.createElement("a", {href: locationInfo.location,

Просмотреть файл

@ -262,7 +262,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
return (
<div className={classes}>
<img src={this.props.roomContextUrl.thumbnail} />
<img src={this.props.roomContextUrl.thumbnail || "shared/img/icons-16x16.svg#globe"} />
<div className="standalone-context-url-description-wrapper">
{this.props.roomContextUrl.description}
<br /><a href={locationInfo.location}

Просмотреть файл

@ -137,3 +137,7 @@ status_in_conversation=In conversation
status_conversation_ended=Conversation ended
status_error=Something went wrong
support_link=Get Help
# Text chat strings
chat_textbox_placeholder=Type here…

Просмотреть файл

@ -850,6 +850,24 @@ describe("loop.panel", function() {
expect(contextContent).to.not.equal(null);
});
it("should show a default favicon when none is available", function() {
fakeMozLoop.getSelectedTabMetadata = function (callback) {
callback({
url: "https://www.example.com",
description: "fake description",
previews: [""]
});
};
var view = createTestComponent();
// Simulate being visible
view.onDocumentVisible();
var previewImage = view.getDOMNode().querySelector(".context-preview");
expect(previewImage.src).to.match(/loop\/shared\/img\/icons-16x16.svg#globe$/);
});
it("should not show context information when a URL is unavailable", function() {
fakeMozLoop.getSelectedTabMetadata = function (callback) {
callback({

Просмотреть файл

@ -305,6 +305,19 @@ describe("loop.roomViews", function () {
expect(view.getDOMNode().querySelector(".room-context-url").textContent)
.eql("hostname");
});
it("should show a default favicon when none is available", function() {
fakeContextURL.thumbnail = null;
view = mountTestComponent({
showContext: true,
roomData: {
roomContextUrls: [fakeContextURL]
}
});
expect(view.getDOMNode().querySelector(".room-context-thumbnail").src)
.to.match(/loop\/shared\/img\/icons-16x16.svg#globe$/);
});
});
});

Просмотреть файл

@ -28,10 +28,22 @@ add_task(function* test_mozLoop_getSelectedTabMetadata() {
metadata = yield promiseGetMetadata();
Assert.strictEqual(metadata.url, null, "URL should be empty for about:home");
Assert.ok(metadata.favicon.startsWith("data:image/x-icon;base64,"),
"Favicon should be set for about:home");
Assert.strictEqual(metadata.favicon, null, "Favicon should be empty for about:home");
Assert.ok(metadata.title, "Title should be set for about:home");
Assert.deepEqual(metadata.previews, [], "No previews available for about:home");
gBrowser.removeTab(tab);
});
add_task(function* test_mozLoop_getSelectedTabMetadata_defaultIcon() {
let tab = gBrowser.selectedTab = gBrowser.addTab();
yield promiseTabLoadEvent(tab, "http://example.com/");
let metadata = yield promiseGetMetadata();
Assert.strictEqual(metadata.url, "http://example.com/", "URL should match");
Assert.strictEqual(metadata.favicon, null, "Favicon should be empty");
Assert.ok(metadata.title, "Title should be set");
Assert.deepEqual(metadata.previews, [], "No previews available");
gBrowser.removeTab(tab);
});

Просмотреть файл

@ -177,7 +177,8 @@ describe("loop.shared.mixins", function() {
comp = TestUtils.renderIntoDocument(React.createElement(TestComp));
sinon.assert.calledOnce(onDocumentVisibleStub);
// Twice, because it's also called when the component was mounted.
sinon.assert.calledTwice(onDocumentVisibleStub);
});
it("should call onDocumentVisible when document visibility changes to hidden",

Просмотреть файл

@ -25,6 +25,11 @@ describe("loop.store.TextChatStore", function () {
store = new loop.store.TextChatStore(dispatcher, {
sdkDriver: fakeSdkDriver
});
sandbox.stub(window, "dispatchEvent");
sandbox.stub(window, "CustomEvent", function(name) {
this.name = name;
});
});
afterEach(function() {
@ -37,6 +42,14 @@ describe("loop.store.TextChatStore", function () {
expect(store.getStoreState("textChatEnabled")).eql(true);
});
it("should dispatch a LoopChatEnabled event", function() {
store.dataChannelsAvailable();
sinon.assert.calledOnce(window.dispatchEvent);
sinon.assert.calledWithExactly(window.dispatchEvent,
new CustomEvent("LoopChatEnabled"));
});
});
describe("#receivedTextChatMessage", function() {
@ -63,6 +76,17 @@ describe("loop.store.TextChatStore", function () {
expect(store.getStoreState("messageList").length).eql(0);
});
it("should dispatch a LoopChatMessageAppended event", function() {
store.receivedTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!"
});
sinon.assert.calledOnce(window.dispatchEvent);
sinon.assert.calledWithExactly(window.dispatchEvent,
new CustomEvent("LoopChatMessageAppended"));
});
});
describe("#sendTextChatMessage", function() {
@ -92,5 +116,16 @@ describe("loop.store.TextChatStore", function () {
message: messageData.message
}]);
});
it("should dipatch a LoopChatMessageAppended event", function() {
store.sendTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!"
});
sinon.assert.calledOnce(window.dispatchEvent);
sinon.assert.calledWithExactly(window.dispatchEvent,
new CustomEvent("LoopChatMessageAppended"));
});
});
});

Просмотреть файл

@ -80,5 +80,32 @@ describe("loop.shared.views.TextChatView", function () {
message: "Hello!"
}));
});
it("should not render message entries when none are sent/ received yet", function() {
view = mountTestComponent();
expect(view.getDOMNode().querySelector(".text-chat-entries")).to.eql(null);
});
it("should render message entries when message were sent/ received", function() {
view = mountTestComponent();
store.receivedTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!"
});
store.sendTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Is it me you're looking for?"
});
var node = view.getDOMNode();
expect(node.querySelector(".text-chat-entries")).to.not.eql(null);
var entries = node.querySelectorAll(".text-chat-entry");
expect(entries.length).to.eql(2);
expect(entries[0].classList.contains("received")).to.eql(true);
expect(entries[1].classList.contains("received")).to.not.eql(true);
});
});
});

Просмотреть файл

@ -149,6 +149,20 @@ describe("loop.standaloneRoomViews", function() {
linkInfo: "Shared URL"
}));
});
it("should display the default favicon when no thumbnail is available", function() {
var view = mountTestComponent({
roomName: "Mike's room",
roomContextUrls: [{
description: "Mark's super page",
location: "http://invalid.com",
thumbnail: ""
}]
});
expect(view.getDOMNode().querySelector(".standalone-context-url > img").src)
.to.match(/shared\/img\/icons-16x16.svg#globe$/);
});
});
describe("StandaloneRoomHeader", function() {

Просмотреть файл

@ -148,10 +148,6 @@ body {
.svg-icon {
display: inline-block;
width: 16px;
height: 16px;
margin-left: .5rem;
background-repeat: no-repeat;
background-size: 16px 16px;
background-position: center;
border: 0;
}

Просмотреть файл

@ -300,13 +300,12 @@
var SVGIcon = React.createClass({displayName: "SVGIcon",
render: function() {
var sizeUnit = this.props.size.split("x")[0] + "px";
var sizeUnit = this.props.size.split("x");
return (
React.createElement("span", {className: "svg-icon", style: {
"backgroundImage": "url(../content/shared/img/icons-" + this.props.size +
".svg#" + this.props.shapeId + ")",
"backgroundSize": sizeUnit + " " + sizeUnit
}})
React.createElement("img", {className: "svg-icon",
src: "../content/shared/img/icons-" + this.props.size + ".svg#" + this.props.shapeId,
width: sizeUnit[0],
height: sizeUnit[1]})
);
}
});
@ -328,7 +327,7 @@
],
"16x16": ["add", "add-hover", "add-active", "audio", "audio-hover", "audio-active",
"block", "block-red", "block-hover", "block-active", "contacts", "contacts-hover",
"contacts-active", "copy", "checkmark", "delete", "google", "google-hover",
"contacts-active", "copy", "checkmark", "delete", "globe", "google", "google-hover",
"google-active", "history", "history-hover", "history-active", "leave",
"precall", "precall-hover", "precall-active", "screen-white", "screenmute-white",
"settings", "settings-hover", "settings-active", "share-darkgrey", "tag",

Просмотреть файл

@ -300,13 +300,12 @@
var SVGIcon = React.createClass({
render: function() {
var sizeUnit = this.props.size.split("x")[0] + "px";
var sizeUnit = this.props.size.split("x");
return (
<span className="svg-icon" style={{
"backgroundImage": "url(../content/shared/img/icons-" + this.props.size +
".svg#" + this.props.shapeId + ")",
"backgroundSize": sizeUnit + " " + sizeUnit
}} />
<img className="svg-icon"
src={"../content/shared/img/icons-" + this.props.size + ".svg#" + this.props.shapeId}
width={sizeUnit[0]}
height={sizeUnit[1]} />
);
}
});
@ -328,7 +327,7 @@
],
"16x16": ["add", "add-hover", "add-active", "audio", "audio-hover", "audio-active",
"block", "block-red", "block-hover", "block-active", "contacts", "contacts-hover",
"contacts-active", "copy", "checkmark", "delete", "google", "google-hover",
"contacts-active", "copy", "checkmark", "delete", "globe", "google", "google-hover",
"google-active", "history", "history-hover", "history-active", "leave",
"precall", "precall-hover", "precall-active", "screen-white", "screenmute-white",
"settings", "settings-hover", "settings-active", "share-darkgrey", "tag",

Просмотреть файл

@ -1 +1,3 @@
41.0a1
# Version to display in the about box:
41.0a1

Просмотреть файл

@ -38,6 +38,7 @@ MOZ_SERVICES_METRICS=1
MOZ_SERVICES_SYNC=1
MOZ_SERVICES_CLOUDSYNC=1
MOZ_APP_VERSION=$FIREFOX_VERSION
MOZ_APP_VERSION_ABOUT=$FIREFOX_VERSION_ABOUT
MOZ_EXTENSIONS_DEFAULT=" gio"
# MOZ_APP_DISPLAYNAME will be set by branding/configure.sh
# MOZ_BRANDING_DIRECTORY is the default branding directory used when none is

Просмотреть файл

@ -193,6 +193,10 @@ let NetMonitorView = {
* @return string (e.g, "network-inspector-view" or "network-statistics-view")
*/
get currentFrontendMode() {
// The getter may be called from a timeout after the panel is destroyed.
if (!this._body.selectedPanel) {
return null;
}
return this._body.selectedPanel.id;
},

Просмотреть файл

@ -21,6 +21,7 @@ support-files =
html_post-raw-test-page.html
html_post-raw-with-headers-test-page.html
html_simple-test-page.html
html_send-beacon.html
html_sorting-test-page.html
html_statistics-test-page.html
html_status-codes-test-page.html
@ -99,6 +100,8 @@ skip-if = e10s # Bug 1091612
[browser_net_security-tab-deselect.js]
[browser_net_security-tab-visibility.js]
[browser_net_security-warnings.js]
[browser_net_send-beacon.js]
[browser_net_send-beacon-other-tab.js]
[browser_net_simple-init.js]
[browser_net_simple-request-data.js]
[browser_net_simple-request-details.js]

Просмотреть файл

@ -0,0 +1,31 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if beacons from other tabs are properly ignored.
*/
let test = Task.async(function*() {
let [, debuggee, monitor] = yield initNetMonitor(SIMPLE_URL);
let { RequestsMenu } = monitor.panelWin.NetMonitorView;
RequestsMenu.lazyUpdate = false;
let tab = yield addTab(SEND_BEACON_URL);
let beaconDebuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
info("Beacon tab added successfully.");
is(RequestsMenu.itemCount, 0, "The requests menu should be empty.");
beaconDebuggee.performRequest();
debuggee.location.reload();
yield waitForNetworkEvents(monitor, 1);
is(RequestsMenu.itemCount, 1, "Only the reload should be recorded.");
let request = RequestsMenu.getItemAtIndex(0);
is(request.attachment.method, "GET", "The method is correct.");
is(request.attachment.status, "200", "The status is correct.");
yield teardown(monitor);
removeTab(tab);
finish();
});

Просмотреть файл

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if beacons are handled correctly.
*/
let test = Task.async(function*() {
let [, debuggee, monitor] = yield initNetMonitor(SEND_BEACON_URL);
let { RequestsMenu } = monitor.panelWin.NetMonitorView;
RequestsMenu.lazyUpdate = false;
is(RequestsMenu.itemCount, 0, "The requests menu should be empty.");
debuggee.performRequest();
yield waitForNetworkEvents(monitor, 1);
is(RequestsMenu.itemCount, 1, "The beacon should be recorded.");
let request = RequestsMenu.getItemAtIndex(0);
is(request.attachment.method, "POST", "The method is correct.");
ok(request.attachment.url.endsWith("beacon_request"), "The URL is correct.");
is(request.attachment.status, "404", "The status is correct.");
yield teardown(monitor);
finish();
});

Просмотреть файл

@ -40,6 +40,7 @@ const SINGLE_GET_URL = EXAMPLE_URL + "html_single-get-page.html";
const STATISTICS_URL = EXAMPLE_URL + "html_statistics-test-page.html";
const CURL_URL = EXAMPLE_URL + "html_copy-as-curl.html";
const CURL_UTILS_URL = EXAMPLE_URL + "html_curl-utils.html";
const SEND_BEACON_URL = EXAMPLE_URL + "html_send-beacon.html";
const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";

Просмотреть файл

@ -0,0 +1,23 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>
<body>
<p>Send beacon test</p>
<script type="text/javascript">
function performRequest() {
navigator.sendBeacon("beacon_request");
}
</script>
</body>
</html>

Просмотреть файл

@ -116,6 +116,7 @@ support-files =
[browser_perf_recordings-io-02.js]
[browser_perf_recordings-io-03.js]
[browser_perf_recordings-io-04.js]
[browser_perf_recordings-io-05.js]
[browser_perf-range-changed-render.js]
[browser_perf-recording-selected-01.js]
[browser_perf-recording-selected-02.js]

Просмотреть файл

@ -29,7 +29,6 @@ let test = Task.async(function*() {
yield DetailsView.selectView("memory-calltree");
yield DetailsView.selectView("memory-flamegraph");
// Verify original recording.
let originalData = PerformanceController.getCurrentRecording().getAllData();
@ -81,25 +80,6 @@ let test = Task.async(function*() {
is(importedData.configuration.withMemory, originalData.configuration.withMemory,
"The imported data is identical to the original data (9).");
yield teardown(panel);
// Test that when importing and no graphs rendered yet,
// we do not get a getMappedSelection error
// bug 1160828
var { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
var { EVENTS, PerformanceController, DetailsView, DetailsSubview, OverviewView, WaterfallView } = panel.panelWin;
yield PerformanceController.clearRecordings();
rerendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
yield PerformanceController.importRecording("", file);
yield imported;
ok(true, "The recording data appears to have been successfully imported.");
yield rerendered;
ok(true, "The imported data was re-rendered.");
yield teardown(panel);
finish();
});

Просмотреть файл

@ -0,0 +1,41 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that when importing and no graphs rendered yet, we do not get a
* `getMappedSelection` error.
*/
let test = Task.async(function*() {
var { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
var { EVENTS, PerformanceController, WaterfallView } = panel.panelWin;
yield startRecording(panel);
yield stopRecording(panel);
// Save recording.
let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
let exported = once(PerformanceController, EVENTS.RECORDING_EXPORTED);
yield PerformanceController.exportRecording("", PerformanceController.getCurrentRecording(), file);
yield exported;
ok(true, "The recording data appears to have been successfully saved.");
// Clear and re-import.
yield PerformanceController.clearRecordings();
let rendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
let imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
yield PerformanceController.importRecording("", file);
yield imported;
yield rendered;
ok(true, "No error was thrown.");
yield teardown(panel);
finish();
});

Просмотреть файл

@ -65,10 +65,8 @@ function* spawnTest() {
is(afterResizeBarsCount, beforeResizeBarsCount,
"The same subset of the total markers remained visible.");
// Temporarily disable the following assertion; intermittent failures.
// Bug 1169352.
// is(Array.indexOf($$(".waterfall-tree-item"), $(".waterfall-tree-item:focus")), 2,
// "The correct item is still focused in the tree.");
is(Array.indexOf($$(".waterfall-tree-item"), $(".waterfall-tree-item:focus")), 2,
"The correct item is still focused in the tree.");
ok(!$("#waterfall-details").hidden,
"The waterfall sidebar is still visible.");

Просмотреть файл

@ -11,6 +11,7 @@ skip-if = buildapp == 'mulet'
[browser_projecteditor_confirm_unsaved.js]
[browser_projecteditor_contextmenu_01.js]
[browser_projecteditor_contextmenu_02.js]
skip-if = true # Bug 1173950
[browser_projecteditor_delete_file.js]
skip-if = e10s # Frequent failures in e10s - Bug 1020027
[browser_projecteditor_rename_file.js]
@ -23,6 +24,7 @@ skip-if = buildapp == 'mulet'
[browser_projecteditor_init.js]
[browser_projecteditor_menubar_01.js]
[browser_projecteditor_menubar_02.js]
skip-if = true # Bug 1173950
[browser_projecteditor_new_file.js]
[browser_projecteditor_stores.js]
[browser_projecteditor_tree_selection_01.js]

Просмотреть файл

@ -6,7 +6,7 @@ include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
CONFIG_DIR = instgen
SFX_MODULE = $(topsrcdir)/other-licenses/7zstub/firefox/7zSD.sfx
APP_VERSION := $(shell cat $(srcdir)/../../config/version.txt)
APP_VERSION := $(FIREFOX_VERSION)
DEFINES += -DAPP_VERSION=$(APP_VERSION)
INSTALLER_FILES = \

Просмотреть файл

@ -63,10 +63,10 @@ share_email_subject5={{clientShortname2}} — Join the conversation
share_email_subject_context={{clientShortname2}} conversation: {{title}}
## LOCALIZATION NOTE (share_email_body4): In this item, don't translate the
## part between {{..}} and leave the \n\n part alone
share_email_body5=Hello!\n\nJoin me for a video conversation on {{clientShortname2}}.\n\nIt's the easiest way to connect by video with anyone anywhere. With {{clientSuperShortname}}, you don't have to download or install anything. Just click or paste this link into your {{brandShortname}}, Opera, or Chrome browser to join the conversation:\n\n{{callUrl}}\n\nIf you'd like to learn more about {{clientSuperShortname}} and how you can start your own free video conversations, visit {{learnMoreUrl}}\n\nTalk to you soon!
share_email_body5=Hello!\n\nJoin me for a video conversation on {{clientShortname2}}.\n\nIt's the easiest way to connect by video with anyone anywhere. With {{clientSuperShortname}}, you don't have to download or install anything. Just click or paste this link into your {{brandShortname}}, Opera or Chrome browser to join the conversation:\n\n{{callUrl}}\n\nIf you'd like to learn more about {{clientSuperShortname}} and how you can start your own free video conversations, visit {{learnMoreUrl}}\n\nTalk to you soon!
## LOCALIZATION NOTE (share_email_body_context): In this item, don't translate
## the part between {{..}} and leave the \n\n part alone.
share_email_body_context=Hello!\n\nJoin me for a video conversation on {{clientShortname2}} about:\n{{title}}.\n\nIt's the easiest way to connect by video with anyone anywhere. With {{clientSuperShortname}}, you don't have to download or install anything. Just click or paste this link into your {{brandShortname}}, Opera, or Chrome browser to join the conversation:\n\n{{callUrl}}\n\nIf you'd like to learn more about {{clientSuperShortname}} and how you can start your own free video conversations, visit {{learnMoreUrl}}\n\nTalk to you soon!
share_email_body_context=Hello!\n\nJoin me for a video conversation on {{clientShortname2}} about:\n{{title}}.\n\nIt's the easiest way to connect by video with anyone anywhere. With {{clientSuperShortname}}, you don't have to download or install anything. Just click or paste this link into your {{brandShortname}}, Opera or Chrome browser to join the conversation:\n\n{{callUrl}}\n\nIf you'd like to learn more about {{clientSuperShortname}} and how you can start your own free video conversations, visit {{learnMoreUrl}}\n\nTalk to you soon!
## LOCALIZATION NOTE (share_tweeet): In this item, don't translate the part
## between {{..}}. Please keep the text below 117 characters to make sure it fits
## in a tweet.
@ -354,3 +354,7 @@ context_show_tooltip=Show Context
context_save_label2=Save
context_link_modified=This link was modified.
context_learn_more_link_label=Learn more.
# Text chat strings
chat_textbox_placeholder=Type here…

Просмотреть файл

@ -195,7 +195,6 @@ chatbox[dark=true] > .chat-titlebar > hbox > .chat-title {
.chatbar-innerbox {
background: transparent;
margin: -285px 0 0;
overflow: hidden;
}

Просмотреть файл

@ -1075,7 +1075,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
list-style-image: url(chrome://browser/skin/undoCloseTab.png);
}
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
#alltabs_undoCloseTab {
list-style-image: url(chrome://browser/skin/undoCloseTab@2x.png);
}
@ -1508,16 +1508,19 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
padding: 0 3px;
}
@media (-moz-os-version: windows-xp),
@media not all and (-moz-os-version: windows-vista),
not all and (-moz-windows-default-theme) {
richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-box > .ac-action-icon {
-moz-image-region: rect(11px, 16px, 22px, 0);
}
@media not all and (-moz-os-version: windows-win7),
not all and (-moz-windows-default-theme) {
richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-box > .ac-action-icon {
-moz-image-region: rect(11px, 16px, 22px, 0);
}
.ac-comment[selected="true"],
.ac-url-text[selected="true"],
.ac-action-text[selected="true"] {
color: inherit !important;
.ac-comment[selected="true"],
.ac-url-text[selected="true"],
.ac-action-text[selected="true"] {
color: inherit !important;
}
}
}
@ -1892,7 +1895,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
%include ../shared/tabs.inc.css
@media (min-resolution: 1.25dppx) {
@media (min-resolution: 1.1dppx) {
/* image preloading hack from shared/tabs.inc.css */
#tabbrowser-tabs::before {
background-image:
@ -1934,8 +1937,17 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
}
}
#TabsToolbar[brighttext] .tab-close-button:not(:hover):not([visuallyselected="true"]) {
-moz-image-region: rect(0, 64px, 16px, 48px) !important;
/* Invert the unhovered close tab icons on bright-text tabs */
@media not all and (min-resolution: 1.1dppx) {
#TabsToolbar[brighttext] .tab-close-button:not(:hover):not([visuallyselected="true"]) {
-moz-image-region: rect(0, 64px, 16px, 48px) !important;
}
}
@media (min-resolution: 1.1dppx) {
#TabsToolbar[brighttext] .tab-close-button:not(:hover):not([visuallyselected="true"]) {
-moz-image-region: rect(0, 128px, 32px, 96px) !important;
}
}
/* tabbrowser-tab focus ring */

Просмотреть файл

@ -1947,12 +1947,17 @@ MOZILLA_UAVERSION=`$PYTHON $srcdir/python/mozbuild/mozbuild/milestone.py --topsr
MOZILLA_SYMBOLVERSION=`$PYTHON $srcdir/python/mozbuild/mozbuild/milestone.py --topsrcdir $srcdir --symbolversion`
dnl Get version of various core apps from the version files.
FIREFOX_VERSION=`cat $_topsrcdir/browser/config/version.txt`
FIREFOX_VERSION=`cat $_topsrcdir/browser/config/version.txt|head -1`
FIREFOX_VERSION_ABOUT=`cat $_topsrcdir/browser/config/version.txt|tail -1`
if test -z "$FIREFOX_VERSION"; then
AC_MSG_ERROR([FIREFOX_VERSION is unexpectedly blank.])
fi
if test -z "$FIREFOX_VERSION_ABOUT"; then
AC_MSG_ERROR([FIREFOX_VERSION_ABOUT is unexpectedly blank.])
fi
AC_DEFINE_UNQUOTED(MOZILLA_VERSION,"$MOZILLA_VERSION")
AC_DEFINE_UNQUOTED(MOZILLA_VERSION_U,$MOZILLA_VERSION)
AC_DEFINE_UNQUOTED(MOZILLA_UAVERSION,"$MOZILLA_UAVERSION")
@ -8692,6 +8697,7 @@ AC_DEFINE_UNQUOTED(MOZ_APP_UA_NAME, "$MOZ_APP_UA_NAME")
AC_SUBST(MOZ_APP_UA_NAME)
AC_DEFINE_UNQUOTED(MOZ_APP_UA_VERSION, "$MOZ_APP_VERSION")
AC_SUBST(MOZ_APP_VERSION)
AC_SUBST(MOZ_APP_VERSION_ABOUT)
AC_SUBST(MOZ_APP_MAXVERSION)
AC_DEFINE_UNQUOTED(FIREFOX_VERSION,$FIREFOX_VERSION)
AC_SUBST(FIREFOX_VERSION)

Просмотреть файл

@ -11209,6 +11209,27 @@ nsDocument::RestorePreviousFullScreenState()
return;
}
// Check whether we are restoring to non-fullscreen state.
bool exitingFullscreen = true;
for (nsIDocument* doc = this; doc; doc = doc->GetParentDocument()) {
if (static_cast<nsDocument*>(doc)->mFullScreenStack.Length() > 1) {
exitingFullscreen = false;
break;
}
}
if (exitingFullscreen) {
// If we are fully exiting fullscreen, don't touch anything here,
// just wait for the window to get out from fullscreen first.
if (XRE_GetProcessType() == GeckoProcessType_Content) {
(new AsyncEventDispatcher(
this, NS_LITERAL_STRING("MozDOMFullscreen:Exit"),
/* Bubbles */ true, /* ChromeOnly */ true))->PostDOMEvent();
} else {
SetWindowFullScreen(this, false);
}
return;
}
// If fullscreen mode is updated the pointer should be unlocked
UnlockPointer();
@ -11256,17 +11277,9 @@ nsDocument::RestorePreviousFullScreenState()
}
}
if (doc == nullptr) {
// We moved all documents in this doctree out of fullscreen mode,
// move the top-level window out of fullscreen mode.
NS_ASSERTION(!nsContentUtils::GetRootDocument(this)->IsFullScreenDoc(),
"Should have cleared all docs' stacks");
nsRefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
this, NS_LITERAL_STRING("MozDOMFullscreen:Exited"), true, true);
asyncDispatcher->PostDOMEvent();
FullscreenRoots::Remove(this);
SetWindowFullScreen(this, false);
}
MOZ_ASSERT(doc, "If we were going to exit from fullscreen on all documents "
"in this doctree, we should've asked the window to exit first "
"instead of reaching here.");
}
bool

Просмотреть файл

@ -2382,6 +2382,17 @@ nsGlobalWindow::SetNewDocument(nsIDocument* aDocument,
jsapi.Init();
JSContext *cx = jsapi.cx();
// Check if we're anywhere near the stack limit before we reach the
// transplanting code, since it has no good way to handle errors. This uses
// the untrusted script limit, which is not strictly necessary since no
// actual script should run.
bool overrecursed = false;
JS_CHECK_RECURSION_CONSERVATIVE_DONT_REPORT(cx, overrecursed = true);
if (overrecursed) {
NS_WARNING("Overrecursion in SetNewDocument");
return NS_ERROR_FAILURE;
}
if (!mDoc) {
// First document load.
@ -2438,12 +2449,6 @@ nsGlobalWindow::SetNewDocument(nsIDocument* aDocument,
bool thisChrome = IsChromeWindow();
// Check if we're anywhere near the stack limit before we reach the
// transplanting code, since it has no good way to handle errors. This uses
// the untrusted script limit, which is not strictly necessary since no
// actual script should run.
JS_CHECK_RECURSION_CONSERVATIVE(cx, return NS_ERROR_FAILURE);
nsCOMPtr<WindowStateHolder> wsh = do_QueryInterface(aState);
NS_ASSERTION(!aState || wsh, "What kind of weird state are you giving me here?");
@ -6070,6 +6075,21 @@ nsGlobalWindow::SetFullScreen(bool aFullScreen)
return SetFullScreenInternal(aFullScreen, true);
}
void
FinishDOMFullscreenChange(nsIDocument* aDoc, bool aInDOMFullscreen)
{
if (aInDOMFullscreen) {
// Ask the document to handle any pending DOM fullscreen change.
nsIDocument::HandlePendingFullscreenRequests(aDoc);
} else {
// Force exit from DOM full-screen mode. This is so that if we're in
// DOM full-screen mode and the user exits full-screen mode with
// the browser full-screen mode toggle keyboard-shortcut, we'll detect
// that and leave DOM API full-screen mode too.
nsIDocument::ExitFullscreen(aDoc, /* async */ false);
}
}
nsresult
nsGlobalWindow::SetFullScreenInternal(bool aFullScreen, bool aFullscreenMode,
gfx::VRHMDInfo* aHMD)
@ -6111,10 +6131,11 @@ nsGlobalWindow::SetFullScreenInternal(bool aFullScreen, bool aFullscreenMode,
if (aFullscreenMode) {
mFullscreenMode = aFullScreen;
} else {
// If we are exiting from DOM fullscreen while we
// initially make the window fullscreen because of
// fullscreen mode, don't restore the window.
// If we are exiting from DOM fullscreen while we initially make
// the window fullscreen because of fullscreen mode, don't restore
// the window. But we still need to exit the DOM fullscreen state.
if (!aFullScreen && mFullscreenMode) {
FinishDOMFullscreenChange(mDoc, false);
return NS_OK;
}
}
@ -6179,26 +6200,16 @@ nsGlobalWindow::FinishFullscreenChange(bool aIsFullscreen)
return;
}
// Ask the document to handle any pending DOM fullscreen change. Note
// we must make the state changes before dispatching the "fullscreen"
// event below, so that the chrome can distinguish between browser
// fullscreen mode and DOM fullscreen.
if (mFullScreen) {
nsIDocument::HandlePendingFullscreenRequests(mDoc);
}
// Note that we must call this to toggle the DOM fullscreen state
// of the document before dispatching the "fullscreen" event, so
// that the chrome can distinguish between browser fullscreen mode
// and DOM fullscreen.
FinishDOMFullscreenChange(mDoc, mFullScreen);
// dispatch a "fullscreen" DOM event so that XUL apps can
// respond visually if we are kicked into full screen mode
DispatchCustomEvent(NS_LITERAL_STRING("fullscreen"));
if (!mFullScreen) {
// Force exit from DOM full-screen mode. This is so that if we're in
// DOM full-screen mode and the user exits full-screen mode with
// the browser full-screen mode toggle keyboard-shortcut, we'll detect
// that and leave DOM API full-screen mode too.
nsIDocument::ExitFullscreen(mDoc, /* async */ false);
}
if (!mWakeLock && mFullScreen) {
nsRefPtr<power::PowerManagerService> pmService =
power::PowerManagerService::GetInstance();

Просмотреть файл

@ -154,8 +154,8 @@ BrowserElementChild.prototype = {
/* useCapture = */ true,
/* wantsUntrusted = */ false);
addEventListener("MozDOMFullscreen:Exited",
this._mozExitedDomFullscreen.bind(this),
addEventListener("MozDOMFullscreen:Exit",
this._mozExitDomFullscreen.bind(this),
/* useCapture = */ true,
/* wantsUntrusted = */ false);
@ -450,7 +450,7 @@ BrowserElementChild.prototype = {
// If we don't actually have any pending fullscreen request
// to handle, neither we have been in fullscreen, tell the
// parent to just exit.
sendAsyncMsg("exited-dom-fullscreen");
sendAsyncMsg("exit-dom-fullscreen");
}
},
@ -997,8 +997,8 @@ BrowserElementChild.prototype = {
});
},
_mozExitedDomFullscreen: function(e) {
sendAsyncMsg("exited-dom-fullscreen");
_mozExitDomFullscreen: function(e) {
sendAsyncMsg("exit-dom-fullscreen");
},
_getContentDimensions: function() {

Просмотреть файл

@ -199,7 +199,7 @@ BrowserElementParent.prototype = {
"got-can-go-forward": this._gotDOMRequestResult,
"requested-dom-fullscreen": this._requestedDOMFullscreen,
"fullscreen-origin-change": this._fullscreenOriginChange,
"exited-dom-fullscreen": this._exitedDomFullscreen,
"exit-dom-fullscreen": this._exitDomFullscreen,
"got-visible": this._gotDOMRequestResult,
"visibilitychange": this._childVisibilityChange,
"got-set-input-method-active": this._gotDOMRequestResult,
@ -1005,7 +1005,7 @@ BrowserElementParent.prototype = {
this._frameElement, "fullscreen-origin-change", data.json.originNoSuffix);
},
_exitedDomFullscreen: function(data) {
_exitDomFullscreen: function(data) {
this._windowUtils.remoteFrameFullscreenReverted();
},

Просмотреть файл

@ -183,7 +183,6 @@ function enter_from_arg_test_1(event) {
ok(document.mozFullScreen, "Should have entered full-screen after calling with bogus (ignored) argument (fourth time)");
addFullscreenChangeContinuation("exit", exit_to_arg_test_2);
document.mozCancelFullScreen();
ok(!document.mozFullScreen, "Should have left full-screen mode.");
}
function exit_to_arg_test_2(event) {
@ -204,7 +203,6 @@ function enter_from_arg_test_2(event) {
ok(document.mozFullScreen, "Should have entered full-screen after calling with vrDisplay null argument (fifth time)");
addFullscreenChangeContinuation("exit", exit4);
document.mozCancelFullScreen();
ok(!document.mozFullScreen, "Should have left full-screen mode.");
}
function exit4(event) {
@ -228,7 +226,6 @@ function enter5(event) {
ok(document.mozFullScreen, "Moved to full-screen after mouse click");
addFullscreenChangeContinuation("exit", exit5);
document.mozCancelFullScreen();
ok(!document.mozFullScreen, "Should have left full-screen mode.");
}
function exit5(event) {

Просмотреть файл

@ -576,11 +576,13 @@ public:
const MutableFileData& aData,
JS::MutableHandle<JSObject*> aResult)
{
MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aDatabase);
MOZ_ASSERT(aFile.mFileInfo);
if (!IndexedDatabaseManager::IsMainProcess() || !NS_IsMainThread()) {
return false;
}
nsRefPtr<IDBMutableFile> mutableFile =
IDBMutableFile::Create(aDatabase,
aData.name,

Просмотреть файл

@ -362,13 +362,20 @@ IDBRequest::SetResultCallback(ResultCallback* aCallback)
JS::Rooted<JS::Value> result(cx);
nsresult rv = aCallback->GetResult(cx, &result);
if (NS_WARN_IF(NS_FAILED(rv))) {
SetError(rv);
mResultVal.setUndefined();
} else {
mError = nullptr;
mResultVal = result;
// This can only fail if the structured clone contains a mutable file
// and the child is not in the main thread and main process.
// In that case CreateAndWrapMutableFile() returns false which shows up
// as NS_ERROR_DOM_DATA_CLONE_ERR here.
MOZ_ASSERT(rv == NS_ERROR_DOM_DATA_CLONE_ERR);
// We are not setting a result or an error object here since we want to
// throw an exception when the 'result' property is being touched.
return;
}
mError = nullptr;
mResultVal = result;
mHaveResultOrErrorCode = true;
}

Просмотреть файл

@ -1,8 +0,0 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
onmessage = function(event) {
throw("Shouldn't be called!");
}

Просмотреть файл

@ -3,7 +3,6 @@ support-files =
bfcache_iframe1.html
bfcache_iframe2.html
blob_worker_crash_iframe.html
dummy_worker.js
error_events_abort_transactions_iframe.html
event_propagation_iframe.html
exceptions_in_events_iframe.html

Просмотреть файл

@ -14,22 +14,55 @@
{
const name = window.location.pathname;
var testBuffer = getRandomBuffer(100000);
let testBuffer = getRandomBuffer(100000);
let request = indexedDB.open(name, 1);
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = errorHandler;
let event = yield undefined;
is(event.type, "upgradeneeded", "Got correct event type");
let db = event.target.result;
db.onerror = errorHandler;
db.createObjectStore("Foo", { });
request.onupgradeneeded = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield undefined;
is(event.type, "success", "Got correct event type");
request = db.createMutableFile("test.txt");
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield undefined;
let mutableFile = event.target.result;
function dummyWorkerScript() {
onmessage = function(event) {
throw("Shouldn't be called!");
}
}
let url =
URL.createObjectURL(new Blob(["(", dummyWorkerScript.toSource(), ")()"]));
let worker = new Worker(url);
try {
worker.postMessage(mutableFile);
ok(false, "Should have thrown!");
}
catch (e) {
ok(e instanceof DOMException, "Got exception.");
is(e.name, "DataCloneError", "Good error.");
is(e.code, DOMException.DATA_CLONE_ERR, "Good error code.")
}
worker.terminate();
mutableFile.onerror = errorHandler;
let fileHandle = mutableFile.open("readwrite");
@ -44,7 +77,8 @@
let file = event.target.result;
var worker = new Worker("dummy_worker.js");
worker = new Worker(url);
URL.revokeObjectURL(url);
try {
worker.postMessage(file);
ok(false, "Should have thrown!");
@ -56,6 +90,51 @@
}
worker.terminate();
let objectStore =
db.transaction("Foo", "readwrite").objectStore("Foo");
request = objectStore.add(mutableFile, 42);
request.onsuccess = grabEventAndContinueHandler;
event = yield undefined;
function workerScript() {
onmessage = function(event) {
var name = event.data;
var request = indexedDB.open(name, 1);
request.onsuccess = function(event) {
var db = event.target.result;
let objectStore = db.transaction("Foo").objectStore("Foo");
request = objectStore.get(42);
request.onsuccess = function(event) {
try {
let result = request.result;
postMessage("error");
}
catch (e) {
postMessage("success");
}
}
request.onerror = function(event) {
postMessage("error");
}
}
request.onerror = function(event) {
postMessage("error");
}
}
}
url = URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
worker = new Worker(url);
URL.revokeObjectURL(url);
worker.postMessage(name);
worker.onmessage = grabEventAndContinueHandler;
event = yield undefined;
is(event.data, "success", "Good response.");
worker.terminate();
finishTest();
yield undefined;
}

Двоичный файл не отображается.

Просмотреть файл

@ -2634,14 +2634,17 @@ ContentChild::RecvStopProfiler()
}
bool
ContentChild::RecvGetProfile(nsCString* aProfile)
ContentChild::RecvGatherProfile()
{
nsCString profileCString;
UniquePtr<char[]> profile = profiler_get_profile();
if (profile) {
*aProfile = nsCString(profile.get(), strlen(profile.get()));
profileCString = nsCString(profile.get(), strlen(profile.get()));
} else {
*aProfile = EmptyCString();
profileCString = EmptyCString();
}
unused << SendProfile(profileCString);
return true;
}

Просмотреть файл

@ -398,7 +398,7 @@ public:
nsTArray<nsCString>&& aFeatures,
nsTArray<nsCString>&& aThreadNameFilters) override;
virtual bool RecvStopProfiler() override;
virtual bool RecvGetProfile(nsCString* aProfile) override;
virtual bool RecvGatherProfile() override;
virtual bool RecvDomainSetChanged(const uint32_t& aSetType, const uint32_t& aChangeType,
const OptionalURIParams& aDomain) override;
virtual bool RecvShutdown() override;

Просмотреть файл

@ -79,6 +79,7 @@
#include "mozilla/Preferences.h"
#include "mozilla/ProcessHangMonitor.h"
#include "mozilla/ProcessHangMonitorIPC.h"
#include "mozilla/ProfileGatherer.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/Telemetry.h"
@ -238,6 +239,7 @@ static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
using base::ChildPrivileges;
using base::KillProcess;
using mozilla::ProfileGatherer;
#ifdef MOZ_CRASHREPORTER
using namespace CrashReporter;
@ -661,6 +663,7 @@ static const char* sObserverTopics[] = {
#ifdef MOZ_ENABLE_PROFILER_SPS
"profiler-started",
"profiler-stopped",
"profiler-subprocess-gather",
"profiler-subprocess",
#endif
};
@ -3132,13 +3135,17 @@ ContentParent::Observe(nsISupports* aSubject,
else if (!strcmp(aTopic, "profiler-stopped")) {
unused << SendStopProfiler();
}
else if (!strcmp(aTopic, "profiler-subprocess-gather")) {
mGatherer = static_cast<ProfileGatherer*>(aSubject);
mGatherer->WillGatherOOPProfile();
unused << SendGatherProfile();
}
else if (!strcmp(aTopic, "profiler-subprocess")) {
nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject);
if (pse) {
nsCString result;
unused << SendGetProfile(&result);
if (!result.IsEmpty()) {
pse->AddSubProfile(result.get());
if (!mProfile.IsEmpty()) {
pse->AddSubProfile(mProfile.get());
mProfile.Truncate();
}
}
}
@ -4343,7 +4350,12 @@ ContentParent::RecvPrivateDocShellsExist(const bool& aExist)
sPrivateContent->AppendElement(this);
} else {
sPrivateContent->RemoveElement(this);
if (!sPrivateContent->Length()) {
// Only fire the notification if we have private and non-private
// windows: if privatebrowsing.autostart is true, all windows are
// private.
if (!sPrivateContent->Length() &&
!Preferences::GetBool("browser.privatebrowsing.autostart")) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->NotifyObservers(nullptr, "last-pb-context-exited", nullptr);
delete sPrivateContent;
@ -5091,6 +5103,18 @@ ContentParent::RecvGamepadListenerRemoved()
return true;
}
bool
ContentParent::RecvProfile(const nsCString& aProfile)
{
if (NS_WARN_IF(!mGatherer)) {
return true;
}
mProfile = aProfile;
mGatherer->GatheredOOPProfile();
mGatherer = nullptr;
return true;
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -37,6 +37,7 @@ class nsIWidget;
namespace mozilla {
class PRemoteSpellcheckEngineParent;
class ProfileGatherer;
namespace ipc {
class OptionalURIParams;
@ -856,6 +857,7 @@ private:
virtual bool RecvGamepadListenerAdded() override;
virtual bool RecvGamepadListenerRemoved() override;
virtual bool RecvProfile(const nsCString& aProfile) override;
// If you add strong pointers to cycle collected objects here, be sure to
// release these objects in ShutDownProcess. See the comment there for more
@ -931,6 +933,8 @@ private:
#endif
PProcessHangMonitorParent* mHangMonitorActor;
nsRefPtr<mozilla::ProfileGatherer> mGatherer;
nsCString mProfile;
};
} // namespace dom

Просмотреть файл

@ -624,8 +624,8 @@ child:
async StartProfiler(uint32_t aEntries, double aInterval, nsCString[] aFeatures,
nsCString[] aThreadNameFilters);
async StopProfiler();
prio(high) sync GetProfile()
returns (nsCString aProfile);
async GatherProfile();
InvokeDragSession(IPCDataTransfer[] transfers, uint32_t action);
@ -758,7 +758,7 @@ parent:
PSpeechSynthesis();
PStorage();
prio(urgent) async PStorage();
PTelephony();
@ -1054,6 +1054,8 @@ parent:
*/
GamepadListenerRemoved();
async Profile(nsCString aProfile);
both:
AsyncMessage(nsString aMessage, ClonedMessageData aData,
CpowEntry[] aCpows, Principal aPrincipal);

Просмотреть файл

@ -129,6 +129,11 @@ public:
return mStart <= aX && aX < mEnd;
}
bool ContainsWithStrictEnd(const T& aX) const
{
return mStart - mFuzz <= aX && aX < mEnd;
}
bool Contains(const SelfType& aOther) const
{
return (mStart - mFuzz <= aOther.mStart + aOther.mFuzz) &&
@ -140,6 +145,12 @@ public:
return mStart <= aOther.mStart && aOther.mEnd <= mEnd;
}
bool ContainsWithStrictEnd(const SelfType& aOther) const
{
return (mStart - mFuzz <= aOther.mStart + aOther.mFuzz) &&
aOther.mEnd <= mEnd;
}
bool Intersects(const SelfType& aOther) const
{
return (mStart - mFuzz < aOther.mEnd + aOther.mFuzz) &&
@ -560,6 +571,15 @@ public:
return false;
}
bool ContainsWithStrictEnd(const T& aX) const {
for (const auto& interval : mIntervals) {
if (interval.ContainsWithStrictEnd(aX)) {
return true;
}
}
return false;
}
// Shift all values by aOffset.
SelfType& Shift(const T& aOffset)
{

Просмотреть файл

@ -50,6 +50,14 @@ public:
// again to retry once more data has been received.
virtual nsRefPtr<InitPromise> Init() = 0;
// MediaFormatReader ensures that calls to the MediaDataDemuxer are thread-safe.
// This is done by having multiple demuxers, created with Clone(), one per
// running thread.
// However, should the MediaDataDemuxer object guaranteed to be thread-safe
// such cloning is unecessary and only one demuxer will be used across
// all threads.
virtual bool IsThreadSafe() { return false; }
// Clone the demuxer and return a new initialized demuxer.
// This can only be called once Init() has succeeded.
// The new demuxer can be immediately use to retrieve the track demuxers.
@ -91,6 +99,11 @@ public:
// This will be called should the demuxer be used with MediaSource.
virtual void NotifyDataRemoved() { }
// Indicate to MediaFormatReader if it should compute the start time
// of the demuxed data. If true (default) the first sample returned will be
// used as reference time base.
virtual bool ShouldComputeStartTime() const { return true; }
protected:
virtual ~MediaDataDemuxer()
{
@ -143,6 +156,15 @@ public:
// sample will contains multiple audio frames.
virtual nsRefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) = 0;
// Returns true if a call to GetSamples() may block while waiting on the
// underlying resource to return the data.
// This is used by the MediaFormatReader to determine if buffering heuristics
// should be used.
virtual bool GetSamplesMayBlock() const
{
return true;
}
// Cancel all pending actions (Seek, GetSamples) and reset current state
// All pending promises are to be rejected with CANCEL.
// The next call to GetSamples would return the first sample available in the

Просмотреть файл

@ -338,7 +338,9 @@ MediaDecoderReader::RequestAudioData()
void
MediaDecoderReader::BreakCycles()
{
mTaskQueue = nullptr;
// Nothing left to do here these days. We keep this method around so that, if
// we need it, we don't have to make all of the subclass implementations call
// the superclass method again.
}
nsRefPtr<ShutdownPromise>

Просмотреть файл

@ -286,8 +286,6 @@ MediaDecoderStateMachine::~MediaDecoderStateMachine()
{
MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
MOZ_COUNT_DTOR(MediaDecoderStateMachine);
NS_ASSERTION(!mPendingWakeDecoder.get(),
"WakeDecoder should have been revoked already");
mReader = nullptr;
@ -458,8 +456,6 @@ void MediaDecoderStateMachine::SendStreamData()
AudioSegment* audio = new AudioSegment();
mediaStream->AddAudioTrack(audioTrackId, mInfo.mAudio.mRate, 0, audio,
SourceMediaStream::ADDTRACK_QUEUED);
stream->mStream->DispatchWhenNotEnoughBuffered(audioTrackId,
TaskQueue(), GetWakeDecoderRunnable());
stream->mNextAudioTime = mStreamStartTime;
}
if (mInfo.HasVideo()) {
@ -467,9 +463,6 @@ void MediaDecoderStateMachine::SendStreamData()
VideoSegment* video = new VideoSegment();
mediaStream->AddTrack(videoTrackId, 0, video,
SourceMediaStream::ADDTRACK_QUEUED);
stream->mStream->DispatchWhenNotEnoughBuffered(videoTrackId,
TaskQueue(), GetWakeDecoderRunnable());
stream->mNextVideoTime = mStreamStartTime;
}
mediaStream->FinishAddTracks();
@ -607,18 +600,6 @@ void MediaDecoderStateMachine::SendStreamData()
}
}
MediaDecoderStateMachine::WakeDecoderRunnable*
MediaDecoderStateMachine::GetWakeDecoderRunnable()
{
MOZ_ASSERT(OnTaskQueue());
AssertCurrentThreadInMonitor();
if (!mPendingWakeDecoder.get()) {
mPendingWakeDecoder = new WakeDecoderRunnable(this);
}
return mPendingWakeDecoder.get();
}
bool MediaDecoderStateMachine::HaveEnoughDecodedAudio(int64_t aAmpleAudioUSecs)
{
MOZ_ASSERT(OnTaskQueue());
@ -640,8 +621,6 @@ bool MediaDecoderStateMachine::HaveEnoughDecodedAudio(int64_t aAmpleAudioUSecs)
if (!stream->mStream->HaveEnoughBuffered(audioTrackId)) {
return false;
}
stream->mStream->DispatchWhenNotEnoughBuffered(audioTrackId,
TaskQueue(), GetWakeDecoderRunnable());
}
return true;
@ -664,8 +643,6 @@ bool MediaDecoderStateMachine::HaveEnoughDecodedVideo()
if (!stream->mStream->HaveEnoughBuffered(videoTrackId)) {
return false;
}
stream->mStream->DispatchWhenNotEnoughBuffered(videoTrackId,
TaskQueue(), GetWakeDecoderRunnable());
}
return true;
@ -1556,8 +1533,6 @@ void MediaDecoderStateMachine::SetDormant(bool aDormant)
// it here as well.
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseMediaResources);
DecodeTaskQueue()->Dispatch(r.forget());
// There's now no possibility of mPendingWakeDecoder being needed again. Revoke it.
mPendingWakeDecoder = nullptr;
mDecoder->GetReentrantMonitor().NotifyAll();
} else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) {
mDecodingFrozenAtStateDecoding = true;
@ -2520,10 +2495,6 @@ MediaDecoderStateMachine::FinishShutdown()
AudioQueue().ClearListeners();
VideoQueue().ClearListeners();
// Now that those threads are stopped, there's no possibility of
// mPendingWakeDecoder being needed again. Revoke it.
mPendingWakeDecoder = nullptr;
// Disconnect canonicals and mirrors before shutting down our task queue.
mEstimatedDuration.DisconnectIfConnected();
mExplicitDuration.DisconnectIfConnected();
@ -3201,7 +3172,10 @@ void MediaDecoderStateMachine::UpdateNextFrameStatus()
MediaDecoderOwner::NextFrameStatus status;
const char* statusString;
if (IsBuffering()) {
if (mState <= DECODER_STATE_DECODING_FIRSTFRAME) {
status = MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
statusString = "NEXT_FRAME_UNAVAILABLE";
} else if (IsBuffering()) {
status = MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING;
statusString = "NEXT_FRAME_UNAVAILABLE_BUFFERING";
} else if (IsSeeking()) {

Просмотреть файл

@ -427,41 +427,6 @@ protected:
void LogicalPlaybackRateChanged();
void PreservesPitchChanged();
class WakeDecoderRunnable : public nsRunnable {
public:
explicit WakeDecoderRunnable(MediaDecoderStateMachine* aSM)
: mMutex("WakeDecoderRunnable"), mStateMachine(aSM) {}
NS_IMETHOD Run() override
{
nsRefPtr<MediaDecoderStateMachine> stateMachine;
{
// Don't let Run() (called by media stream graph thread) race with
// Revoke() (called by decoder state machine thread)
MutexAutoLock lock(mMutex);
if (!mStateMachine)
return NS_OK;
stateMachine = mStateMachine;
}
stateMachine->ScheduleStateMachineWithLockAndWakeDecoder();
return NS_OK;
}
void Revoke()
{
MutexAutoLock lock(mMutex);
mStateMachine = nullptr;
}
Mutex mMutex;
// Protected by mMutex.
// We don't use an owning pointer here, because keeping mStateMachine alive
// would mean in some cases we'd have to destroy mStateMachine from this
// object, which would be problematic since MediaDecoderStateMachine can
// only be destroyed on the main thread whereas this object can be destroyed
// on the media stream graph thread.
MediaDecoderStateMachine* mStateMachine;
};
WakeDecoderRunnable* GetWakeDecoderRunnable();
MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }
MediaQueue<VideoData>& VideoQueue() { return mVideoQueue; }
@ -976,13 +941,6 @@ protected:
// This is created in the state machine's constructor.
nsRefPtr<MediaDecoderReader> mReader;
// Accessed only on the state machine thread.
// Not an nsRevocableEventPtr since we must Revoke() it well before
// this object is destroyed, anyway.
// Protected by decoder monitor except during the SHUTDOWN state after the
// decoder thread has been stopped.
nsRevocableEventPtr<WakeDecoderRunnable> mPendingWakeDecoder;
// The time of the current frame in microseconds, corresponding to the "current
// playback position" in HTML5. This is referenced from 0, which is the initial
// playback position.

Просмотреть файл

@ -72,6 +72,7 @@ MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
, mInitDone(false)
, mSeekable(false)
, mIsEncrypted(false)
, mTrackDemuxersMayBlock(false)
, mCachedTimeRangesStale(true)
#if defined(READER_DORMANT_HEURISTIC)
, mDormantEnabled(Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false))
@ -304,6 +305,7 @@ MediaFormatReader::OnDemuxerInitDone(nsresult)
mInfo.mVideo = *mVideo.mTrackDemuxer->GetInfo()->GetAsVideoInfo();
mVideo.mCallback = new DecoderCallback(this, TrackInfo::kVideoTrack);
mVideo.mTimeRanges = mVideo.mTrackDemuxer->GetBuffered();
mTrackDemuxersMayBlock |= mVideo.mTrackDemuxer->GetSamplesMayBlock();
}
bool audioActive = !!mDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
@ -313,6 +315,7 @@ MediaFormatReader::OnDemuxerInitDone(nsresult)
mInfo.mAudio = *mAudio.mTrackDemuxer->GetInfo()->GetAsAudioInfo();
mAudio.mCallback = new DecoderCallback(this, TrackInfo::kAudioTrack);
mAudio.mTimeRanges = mAudio.mTrackDemuxer->GetBuffered();
mTrackDemuxersMayBlock |= mAudio.mTrackDemuxer->GetSamplesMayBlock();
}
UniquePtr<EncryptionInfo> crypto = mDemuxer->GetCrypto();
@ -341,7 +344,11 @@ MediaFormatReader::OnDemuxerInitDone(nsresult)
mSeekable = mDemuxer->IsSeekable();
// Create demuxer object for main thread.
mMainThreadDemuxer = mDemuxer->Clone();
if (mDemuxer->IsThreadSafe()) {
mMainThreadDemuxer = mDemuxer;
} else {
mMainThreadDemuxer = mDemuxer->Clone();
}
if (!mMainThreadDemuxer) {
mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
NS_WARNING("Unable to clone current MediaDataDemuxer");
@ -846,6 +853,9 @@ MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack,
aA.mParsed += decoder.mQueuedSamples.Length();
}
decoder.mQueuedSamples.Clear();
// We have serviced the decoder's request for more data.
decoder.mInputExhausted = false;
}
void
@ -924,7 +934,6 @@ MediaFormatReader::Update(TrackType aTrack)
}
needInput = true;
decoder.mInputExhausted = false;
LOGV("Update(%s) ni=%d no=%d ie=%d, in:%d out:%d qs=%d",
TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted,
@ -1137,7 +1146,12 @@ MediaFormatReader::OnVideoSkipFailed(MediaTrackDemuxer::SkipFailureHolder aFailu
mDecoder->NotifyDecodedFrames(aFailure.mSkipped, 0, aFailure.mSkipped);
MOZ_ASSERT(mVideo.HasPromise());
if (aFailure.mFailure == DemuxerFailureReason::END_OF_STREAM) {
mVideo.mDemuxEOS = true;
ScheduleUpdate(TrackType::kVideoTrack);
mVideo.RejectPromise(END_OF_STREAM, __func__);
} else if (aFailure.mFailure == DemuxerFailureReason::WAITING_FOR_DATA) {
NotifyWaitingForData(TrackType::kVideoTrack);
mVideo.RejectPromise(WAITING_FOR_DATA, __func__);
} else {
mVideo.RejectPromise(DECODE_ERROR, __func__);
}
@ -1460,4 +1474,13 @@ MediaFormatReader::NotifyDataRemoved()
TaskQueue()->Dispatch(task.forget());
}
int64_t
MediaFormatReader::ComputeStartTime(const VideoData* aVideo, const AudioData* aAudio)
{
if (mDemuxer->ShouldComputeStartTime()) {
return MediaDecoderReader::ComputeStartTime(aVideo, aAudio);
}
return 0;
}
} // namespace mozilla

Просмотреть файл

@ -35,15 +35,15 @@ public:
virtual ~MediaFormatReader();
virtual nsresult Init(MediaDecoderReader* aCloneDonor) override;
nsresult Init(MediaDecoderReader* aCloneDonor) override;
virtual size_t SizeOfVideoQueueInFrames() override;
virtual size_t SizeOfAudioQueueInFrames() override;
size_t SizeOfVideoQueueInFrames() override;
size_t SizeOfAudioQueueInFrames() override;
virtual nsRefPtr<VideoDataPromise>
nsRefPtr<VideoDataPromise>
RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) override;
virtual nsRefPtr<AudioDataPromise> RequestAudioData() override;
nsRefPtr<AudioDataPromise> RequestAudioData() override;
bool HasVideo() override
{
@ -55,47 +55,54 @@ public:
return mInfo.HasAudio();
}
virtual nsRefPtr<MetadataPromise> AsyncReadMetadata() override;
nsRefPtr<MetadataPromise> AsyncReadMetadata() override;
virtual void ReadUpdatedMetadata(MediaInfo* aInfo) override;
void ReadUpdatedMetadata(MediaInfo* aInfo) override;
virtual nsRefPtr<SeekPromise>
nsRefPtr<SeekPromise>
Seek(int64_t aTime, int64_t aUnused) override;
virtual bool IsMediaSeekable() override
bool IsMediaSeekable() override
{
return mSeekable;
}
virtual int64_t GetEvictionOffset(double aTime) override;
virtual void NotifyDataArrived(const char* aBuffer,
int64_t GetEvictionOffset(double aTime) override;
void NotifyDataArrived(const char* aBuffer,
uint32_t aLength,
int64_t aOffset) override;
virtual void NotifyDataRemoved() override;
void NotifyDataRemoved() override;
virtual media::TimeIntervals GetBuffered() override;
media::TimeIntervals GetBuffered() override;
// For Media Resource Management
virtual void SetIdle() override;
virtual bool IsDormantNeeded() override;
virtual void ReleaseMediaResources() override;
virtual void SetSharedDecoderManager(SharedDecoderManager* aManager)
void SetIdle() override;
bool IsDormantNeeded() override;
void ReleaseMediaResources() override;
void SetSharedDecoderManager(SharedDecoderManager* aManager)
override;
virtual nsresult ResetDecode() override;
nsresult ResetDecode() override;
virtual nsRefPtr<ShutdownPromise> Shutdown() override;
nsRefPtr<ShutdownPromise> Shutdown() override;
virtual bool IsAsync() const override { return true; }
bool IsAsync() const override { return true; }
virtual bool VideoIsHardwareAccelerated() const override;
bool VideoIsHardwareAccelerated() const override;
virtual void DisableHardwareAcceleration() override;
void DisableHardwareAcceleration() override;
virtual bool IsWaitForDataSupported() override { return true; }
virtual nsRefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) override;
bool IsWaitForDataSupported() override { return true; }
nsRefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) override;
virtual bool IsWaitingOnCDMResource() override;
bool IsWaitingOnCDMResource() override;
int64_t ComputeStartTime(const VideoData* aVideo, const AudioData* aAudio) override;
bool UseBufferingHeuristics() override
{
return mTrackDemuxersMayBlock;
}
private:
bool InitDemuxer();
@ -154,22 +161,22 @@ private:
, mType(aType)
{
}
virtual void Output(MediaData* aSample) override {
void Output(MediaData* aSample) override {
mReader->Output(mType, aSample);
}
virtual void InputExhausted() override {
void InputExhausted() override {
mReader->InputExhausted(mType);
}
virtual void Error() override {
void Error() override {
mReader->Error(mType);
}
virtual void DrainComplete() override {
void DrainComplete() override {
mReader->DrainComplete(mType);
}
virtual void ReleaseMediaResources() override {
void ReleaseMediaResources() override {
mReader->ReleaseMediaResources();
}
virtual bool OnReaderTaskQueue() override {
bool OnReaderTaskQueue() override {
return mReader->OnTaskQueue();
}
@ -364,6 +371,9 @@ private:
// become incorrect.
bool mIsEncrypted;
// Set to true if any of our track buffers may be blocking.
bool mTrackDemuxersMayBlock;
// Seeking objects.
bool IsSeeking() const { return mPendingSeekTime.isSome(); }
void AttemptSeek();

Просмотреть файл

@ -167,9 +167,7 @@ MediaTaskQueue::BeginShutdown()
MonitorAutoLock mon(mQueueMonitor);
mIsShutdown = true;
nsRefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
if (!mIsRunning) {
mShutdownPromise.Resolve(true, __func__);
}
MaybeResolveShutdown();
mon.NotifyAll();
return p;
}
@ -238,7 +236,7 @@ MediaTaskQueue::Runner::Run()
MOZ_ASSERT(mQueue->mIsRunning);
if (mQueue->mTasks.size() == 0) {
mQueue->mIsRunning = false;
mQueue->mShutdownPromise.ResolveIfExists(true, __func__);
mQueue->MaybeResolveShutdown();
mon.NotifyAll();
return NS_OK;
}
@ -269,7 +267,7 @@ MediaTaskQueue::Runner::Run()
if (mQueue->mTasks.size() == 0) {
// No more events to run. Exit the task runner.
mQueue->mIsRunning = false;
mQueue->mShutdownPromise.ResolveIfExists(true, __func__);
mQueue->MaybeResolveShutdown();
mon.NotifyAll();
return NS_OK;
}
@ -286,6 +284,7 @@ MediaTaskQueue::Runner::Run()
MonitorAutoLock mon(mQueue->mQueueMonitor);
mQueue->mIsRunning = false;
mQueue->mIsShutdown = true;
mQueue->MaybeResolveShutdown();
mon.NotifyAll();
}

Просмотреть файл

@ -94,6 +94,15 @@ protected:
DispatchFailureHandling aFailureHandling,
DispatchReason aReason = NormalDispatch);
void MaybeResolveShutdown()
{
mQueueMonitor.AssertCurrentThreadOwns();
if (mIsShutdown && !mIsRunning) {
mShutdownPromise.ResolveIfExists(true, __func__);
mPool = nullptr;
}
}
RefPtr<SharedThreadPool> mPool;
// Monitor that protects the queue and mIsRunning;

Просмотреть файл

@ -98,13 +98,15 @@ GMPContentChild::DeallocPGMPVideoDecoderChild(PGMPVideoDecoderChild* aActor)
PGMPVideoEncoderChild*
GMPContentChild::AllocPGMPVideoEncoderChild()
{
return new GMPVideoEncoderChild(this);
GMPVideoEncoderChild* actor = new GMPVideoEncoderChild(this);
actor->AddRef();
return actor;
}
bool
GMPContentChild::DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor)
{
delete aActor;
static_cast<GMPVideoEncoderChild*>(aActor)->Release();
return true;
}

Просмотреть файл

@ -808,19 +808,21 @@ GMPParent::ReadGMPMetaData()
bool
GMPParent::CanBeSharedCrossNodeIds() const
{
return mNodeId.IsEmpty() &&
// XXX bug 1159300 hack -- maybe remove after openh264 1.4
// We don't want to use CDM decoders for non-encrypted playback
// just yet; especially not for WebRTC. Don't allow CDMs to be used
// without a node ID.
!mCanDecrypt;
return !mAsyncShutdownInProgress &&
mNodeId.IsEmpty() &&
// XXX bug 1159300 hack -- maybe remove after openh264 1.4
// We don't want to use CDM decoders for non-encrypted playback
// just yet; especially not for WebRTC. Don't allow CDMs to be used
// without a node ID.
!mCanDecrypt;
}
bool
GMPParent::CanBeUsedFrom(const nsACString& aNodeId) const
{
return (mNodeId.IsEmpty() && State() == GMPStateNotLoaded) ||
mNodeId == aNodeId;
return !mAsyncShutdownInProgress &&
((mNodeId.IsEmpty() && State() == GMPStateNotLoaded) ||
mNodeId == aNodeId);
}
void

Просмотреть файл

@ -9,21 +9,25 @@
#include "mozilla/unused.h"
#include "GMPVideoEncodedFrameImpl.h"
#include "GMPVideoi420FrameImpl.h"
#include "runnable_utils.h"
namespace mozilla {
namespace gmp {
GMPVideoEncoderChild::GMPVideoEncoderChild(GMPContentChild* aPlugin)
: GMPSharedMemManager(aPlugin),
mPlugin(aPlugin),
mVideoEncoder(nullptr),
mVideoHost(this)
: GMPSharedMemManager(aPlugin)
, mPlugin(aPlugin)
, mVideoEncoder(nullptr)
, mVideoHost(this)
, mNeedShmemIntrCount(0)
, mPendingEncodeComplete(false)
{
MOZ_ASSERT(mPlugin);
}
GMPVideoEncoderChild::~GMPVideoEncoderChild()
{
MOZ_ASSERT(!mNeedShmemIntrCount);
}
void
@ -162,6 +166,17 @@ GMPVideoEncoderChild::RecvSetPeriodicKeyFrames(const bool& aEnable)
bool
GMPVideoEncoderChild::RecvEncodingComplete()
{
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
if (mNeedShmemIntrCount) {
// There's a GMP blocked in Alloc() waiting for the CallNeedShem() to
// return a frame they can use. Don't call the GMP's EncodingComplete()
// now and don't delete the GMPVideoEncoderChild, defer processing the
// EncodingComplete() until once the Alloc() finishes.
mPendingEncodeComplete = true;
return true;
}
if (!mVideoEncoder) {
unused << Send__delete__(this);
return false;
@ -179,5 +194,41 @@ GMPVideoEncoderChild::RecvEncodingComplete()
return true;
}
bool
GMPVideoEncoderChild::Alloc(size_t aSize,
Shmem::SharedMemory::SharedMemoryType aType,
Shmem* aMem)
{
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
bool rv;
#ifndef SHMEM_ALLOC_IN_CHILD
++mNeedShmemIntrCount;
rv = CallNeedShmem(aSize, aMem);
--mNeedShmemIntrCount;
if (mPendingEncodeComplete) {
auto t = NewRunnableMethod(this, &GMPVideoEncoderChild::RecvEncodingComplete);
mPlugin->GMPMessageLoop()->PostTask(FROM_HERE, t);
}
#else
#ifdef GMP_SAFE_SHMEM
rv = AllocShmem(aSize, aType, aMem);
#else
rv = AllocUnsafeShmem(aSize, aType, aMem);
#endif
#endif
return rv;
}
void
GMPVideoEncoderChild::Dealloc(Shmem& aMem)
{
#ifndef SHMEM_ALLOC_IN_CHILD
SendParentShmemForPool(aMem);
#else
DeallocShmem(aMem);
#endif
}
} // namespace gmp
} // namespace mozilla

Просмотреть файл

@ -22,8 +22,9 @@ class GMPVideoEncoderChild : public PGMPVideoEncoderChild,
public GMPSharedMemManager
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPVideoEncoderChild);
explicit GMPVideoEncoderChild(GMPContentChild* aPlugin);
virtual ~GMPVideoEncoderChild();
void Init(GMPVideoEncoder* aEncoder);
GMPVideoHostImpl& Host();
@ -35,28 +36,13 @@ public:
virtual void Error(GMPErr aError) override;
// GMPSharedMemManager
virtual bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override
{
#ifndef SHMEM_ALLOC_IN_CHILD
return CallNeedShmem(aSize, aMem);
#else
#ifdef GMP_SAFE_SHMEM
return AllocShmem(aSize, aType, aMem);
#else
return AllocUnsafeShmem(aSize, aType, aMem);
#endif
#endif
}
virtual void Dealloc(Shmem& aMem) override
{
#ifndef SHMEM_ALLOC_IN_CHILD
SendParentShmemForPool(aMem);
#else
DeallocShmem(aMem);
#endif
}
virtual bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType,
Shmem* aMem) override;
virtual void Dealloc(Shmem& aMem) override;
private:
virtual ~GMPVideoEncoderChild();
// PGMPVideoEncoderChild
virtual bool RecvInitEncode(const GMPVideoCodec& aCodecSettings,
InfallibleTArray<uint8_t>&& aCodecSpecific,
@ -76,6 +62,11 @@ private:
GMPContentChild* mPlugin;
GMPVideoEncoder* mVideoEncoder;
GMPVideoHostImpl mVideoHost;
// Non-zero when a GMP is blocked spinning the IPC message loop while
// waiting on an NeedShmem to complete.
int mNeedShmemIntrCount;
bool mPendingEncodeComplete;
};
} // namespace gmp

Просмотреть файл

@ -413,8 +413,6 @@ MediaSource::Detach()
MOZ_ASSERT(mActiveSourceBuffers->IsEmpty() && mSourceBuffers->IsEmpty());
return;
}
mDecoder->DetachMediaSource();
mDecoder = nullptr;
mMediaElement = nullptr;
mFirstSourceBufferInitialized = false;
SetReadyState(MediaSourceReadyState::Closed);
@ -424,6 +422,8 @@ MediaSource::Detach()
if (mSourceBuffers) {
mSourceBuffers->Clear();
}
mDecoder->DetachMediaSource();
mDecoder = nullptr;
}
MediaSource::MediaSource(nsPIDOMWindow* aWindow)

Просмотреть файл

@ -14,6 +14,8 @@
#include "MediaSourceUtils.h"
#include "SourceBufferDecoder.h"
#include "VideoUtils.h"
#include "MediaFormatReader.h"
#include "MediaSourceDemuxer.h"
extern PRLogModuleInfo* GetMediaSourceLog();
@ -28,6 +30,8 @@ class SourceBufferDecoder;
MediaSourceDecoder::MediaSourceDecoder(dom::HTMLMediaElement* aElement)
: mMediaSource(nullptr)
, mIsUsingFormatReader(Preferences::GetBool("media.mediasource.format-reader", false))
, mEnded(false)
{
SetExplicitDuration(UnspecifiedNaN<double>());
Init(aElement);
@ -43,7 +47,12 @@ MediaSourceDecoder::Clone()
MediaDecoderStateMachine*
MediaSourceDecoder::CreateStateMachine()
{
mReader = new MediaSourceReader(this);
if (mIsUsingFormatReader) {
mDemuxer = new MediaSourceDemuxer();
mReader = new MediaFormatReader(this, mDemuxer);
} else {
mReader = new MediaSourceReader(this);
}
return new MediaDecoderStateMachine(this, mReader);
}
@ -130,29 +139,29 @@ MediaSourceDecoder::DetachMediaSource()
already_AddRefed<SourceBufferDecoder>
MediaSourceDecoder::CreateSubDecoder(const nsACString& aType, int64_t aTimestampOffset)
{
MOZ_ASSERT(mReader);
return mReader->CreateSubDecoder(aType, aTimestampOffset);
MOZ_ASSERT(mReader && !mIsUsingFormatReader);
return GetReader()->CreateSubDecoder(aType, aTimestampOffset);
}
void
MediaSourceDecoder::AddTrackBuffer(TrackBuffer* aTrackBuffer)
{
MOZ_ASSERT(mReader);
mReader->AddTrackBuffer(aTrackBuffer);
MOZ_ASSERT(mReader && !mIsUsingFormatReader);
GetReader()->AddTrackBuffer(aTrackBuffer);
}
void
MediaSourceDecoder::RemoveTrackBuffer(TrackBuffer* aTrackBuffer)
{
MOZ_ASSERT(mReader);
mReader->RemoveTrackBuffer(aTrackBuffer);
MOZ_ASSERT(mReader && !mIsUsingFormatReader);
GetReader()->RemoveTrackBuffer(aTrackBuffer);
}
void
MediaSourceDecoder::OnTrackBufferConfigured(TrackBuffer* aTrackBuffer, const MediaInfo& aInfo)
{
MOZ_ASSERT(mReader);
mReader->OnTrackBufferConfigured(aTrackBuffer, aInfo);
MOZ_ASSERT(mReader && !mIsUsingFormatReader);
GetReader()->OnTrackBufferConfigured(aTrackBuffer, aInfo);
}
void
@ -160,15 +169,17 @@ MediaSourceDecoder::Ended(bool aEnded)
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
static_cast<MediaSourceResource*>(GetResource())->SetEnded(aEnded);
mReader->Ended(aEnded);
if (!mIsUsingFormatReader) {
GetReader()->Ended(aEnded);
}
mEnded = true;
mon.NotifyAll();
}
bool
MediaSourceDecoder::IsExpectingMoreData()
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
return !mReader->IsEnded();
return !mEnded;
}
void
@ -206,8 +217,8 @@ MediaSourceDecoder::SetMediaSourceDuration(double aDuration, MSRangeRemovalActio
} else {
SetExplicitDuration(PositiveInfinity<double>());
}
if (mReader) {
mReader->SetMediaSourceDuration(ExplicitDuration());
if (!mIsUsingFormatReader && GetReader()) {
GetReader()->SetMediaSourceDuration(ExplicitDuration());
}
MediaDecoder::DurationChanged(TimeUnit::FromSeconds(ExplicitDuration()));
@ -226,21 +237,27 @@ MediaSourceDecoder::GetMediaSourceDuration()
void
MediaSourceDecoder::NotifyTimeRangesChanged()
{
MOZ_ASSERT(mReader);
mReader->NotifyTimeRangesChanged();
MOZ_ASSERT(mReader && !mIsUsingFormatReader);
GetReader()->NotifyTimeRangesChanged();
}
void
MediaSourceDecoder::PrepareReaderInitialization()
{
if (mIsUsingFormatReader) {
return;
}
MOZ_ASSERT(mReader);
mReader->PrepareInitialization();
GetReader()->PrepareInitialization();
}
void
MediaSourceDecoder::GetMozDebugReaderData(nsAString& aString)
{
mReader->GetMozDebugReaderData(aString);
if (mIsUsingFormatReader) {
return;
}
GetReader()->GetMozDebugReaderData(aString);
}
#ifdef MOZ_EME
@ -249,9 +266,10 @@ MediaSourceDecoder::SetCDMProxy(CDMProxy* aProxy)
{
nsresult rv = MediaDecoder::SetCDMProxy(aProxy);
NS_ENSURE_SUCCESS(rv, rv);
rv = mReader->SetCDMProxy(aProxy);
NS_ENSURE_SUCCESS(rv, rv);
if (!mIsUsingFormatReader) {
rv = GetReader()->SetCDMProxy(aProxy);
NS_ENSURE_SUCCESS(rv, rv);
}
if (aProxy) {
// The sub readers can't decrypt EME content until they have a CDMProxy,
// and the CDMProxy knows the capabilities of the CDM. The MediaSourceReader
@ -271,7 +289,7 @@ MediaSourceDecoder::SetCDMProxy(CDMProxy* aProxy)
bool
MediaSourceDecoder::IsActiveReader(MediaDecoderReader* aReader)
{
return mReader->IsActiveReader(aReader);
return !mIsUsingFormatReader && GetReader()->IsActiveReader(aReader);
}
double
@ -286,6 +304,7 @@ MediaSourceDecoder::SelectDecoder(int64_t aTarget,
int64_t aTolerance,
const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders)
{
MOZ_ASSERT(!mIsUsingFormatReader);
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
media::TimeUnit target{media::TimeUnit::FromMicroseconds(aTarget)};

Просмотреть файл

@ -7,6 +7,7 @@
#ifndef MOZILLA_MEDIASOURCEDECODER_H_
#define MOZILLA_MEDIASOURCEDECODER_H_
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "nsCOMPtr.h"
#include "nsError.h"
@ -22,6 +23,7 @@ class MediaDecoderStateMachine;
class SourceBufferDecoder;
class TrackBuffer;
enum MSRangeRemovalAction : uint8_t;
class MediaSourceDemuxer;
namespace dom {
@ -75,7 +77,15 @@ public:
virtual nsresult SetCDMProxy(CDMProxy* aProxy) override;
#endif
MediaSourceReader* GetReader() { return mReader; }
MediaSourceReader* GetReader()
{
MOZ_ASSERT(!mIsUsingFormatReader);
return static_cast<MediaSourceReader*>(mReader.get());
}
MediaSourceDemuxer* GetDemuxer()
{
return mDemuxer;
}
// Returns true if aReader is a currently active audio or video
// reader in this decoders MediaSourceReader.
@ -98,7 +108,11 @@ private:
// calls Attach/DetachMediaSource on this decoder to set and clear
// mMediaSource.
dom::MediaSource* mMediaSource;
nsRefPtr<MediaSourceReader> mReader;
nsRefPtr<MediaDecoderReader> mReader;
bool mIsUsingFormatReader;
nsRefPtr<MediaSourceDemuxer> mDemuxer;
Atomic<bool> mEnded;
};
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,459 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 <algorithm>
#include <limits>
#include <stdint.h>
#include "MediaSourceDemuxer.h"
#include "SourceBufferList.h"
namespace mozilla {
typedef TrackInfo::TrackType TrackType;
using media::TimeUnit;
using media::TimeIntervals;
MediaSourceDemuxer::MediaSourceDemuxer()
: mTaskQueue(new MediaTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
/* aSupportsTailDispatch = */ true))
, mMonitor("MediaSourceDemuxer")
{
MOZ_ASSERT(NS_IsMainThread());
}
nsRefPtr<MediaSourceDemuxer::InitPromise>
MediaSourceDemuxer::Init()
{
return ProxyMediaCall(GetTaskQueue(), this, __func__,
&MediaSourceDemuxer::AttemptInit);
}
nsRefPtr<MediaSourceDemuxer::InitPromise>
MediaSourceDemuxer::AttemptInit()
{
MOZ_ASSERT(OnTaskQueue());
if (ScanSourceBuffersForContent()) {
return InitPromise::CreateAndResolve(NS_OK, __func__);
}
return InitPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA,
__func__);
}
bool
MediaSourceDemuxer::ScanSourceBuffersForContent()
{
MOZ_ASSERT(OnTaskQueue());
if (mSourceBuffers.IsEmpty()) {
return false;
}
MonitorAutoLock mon(mMonitor);
bool haveEmptySourceBuffer = false;
for (const auto& sourceBuffer : mSourceBuffers) {
MediaInfo info = sourceBuffer->GetMetadata();
if (!info.HasAudio() && !info.HasVideo()) {
haveEmptySourceBuffer = true;
}
if (info.HasAudio() && !mAudioTrack) {
mInfo.mAudio = info.mAudio;
mAudioTrack = sourceBuffer;
}
if (info.HasVideo() && !mVideoTrack) {
mInfo.mVideo = info.mVideo;
mVideoTrack = sourceBuffer;
}
if (info.IsEncrypted() && !mInfo.IsEncrypted()) {
mInfo.mCrypto = info.mCrypto;
}
}
if (mInfo.HasAudio() && mInfo.HasVideo()) {
// We have both audio and video. We can ignore non-ready source buffer.
return true;
}
return !haveEmptySourceBuffer;
}
bool
MediaSourceDemuxer::HasTrackType(TrackType aType) const
{
MonitorAutoLock mon(mMonitor);
switch (aType) {
case TrackType::kAudioTrack:
return mInfo.HasAudio();
case TrackType::kVideoTrack:
return mInfo.HasVideo();
default:
return false;
}
}
uint32_t
MediaSourceDemuxer::GetNumberTracks(TrackType aType) const
{
return HasTrackType(aType) ? 1u : 0;
}
already_AddRefed<MediaTrackDemuxer>
MediaSourceDemuxer::GetTrackDemuxer(TrackType aType, uint32_t aTrackNumber)
{
nsRefPtr<TrackBuffersManager> manager = GetManager(aType);
if (!manager) {
MOZ_CRASH("TODO: sourcebuffer was deleted from under us");
return nullptr;
}
nsRefPtr<MediaSourceTrackDemuxer> e =
new MediaSourceTrackDemuxer(this, aType, manager);
mDemuxers.AppendElement(e);
return e.forget();
}
bool
MediaSourceDemuxer::IsSeekable() const
{
return true;
}
UniquePtr<EncryptionInfo>
MediaSourceDemuxer::GetCrypto()
{
MonitorAutoLock mon(mMonitor);
auto crypto = MakeUnique<EncryptionInfo>();
*crypto = mInfo.mCrypto;
return crypto;
}
void
MediaSourceDemuxer::NotifyTimeRangesChanged()
{
MOZ_ASSERT(OnTaskQueue());
for (uint32_t i = 0; i < mDemuxers.Length(); i++) {
mDemuxers[i]->NotifyTimeRangesChanged();
}
}
void
MediaSourceDemuxer::AttachSourceBuffer(TrackBuffersManager* aSourceBuffer)
{
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArg<TrackBuffersManager*>(
this, &MediaSourceDemuxer::DoAttachSourceBuffer,
aSourceBuffer);
GetTaskQueue()->Dispatch(task.forget());
}
void
MediaSourceDemuxer::DoAttachSourceBuffer(mozilla::TrackBuffersManager* aSourceBuffer)
{
MOZ_ASSERT(OnTaskQueue());
mSourceBuffers.AppendElement(aSourceBuffer);
ScanSourceBuffersForContent();
}
void
MediaSourceDemuxer::DetachSourceBuffer(TrackBuffersManager* aSourceBuffer)
{
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArg<TrackBuffersManager*>(
this, &MediaSourceDemuxer::DoDetachSourceBuffer,
aSourceBuffer);
GetTaskQueue()->Dispatch(task.forget());
}
void
MediaSourceDemuxer::DoDetachSourceBuffer(TrackBuffersManager* aSourceBuffer)
{
MOZ_ASSERT(OnTaskQueue());
for (uint32_t i = 0; i < mSourceBuffers.Length(); i++) {
if (mSourceBuffers[i].get() == aSourceBuffer) {
mSourceBuffers.RemoveElementAt(i);
}
}
if (aSourceBuffer == mAudioTrack) {
mAudioTrack = nullptr;
}
if (aSourceBuffer == mVideoTrack) {
mVideoTrack = nullptr;
}
ScanSourceBuffersForContent();
}
TrackInfo*
MediaSourceDemuxer::GetTrackInfo(TrackType aTrack)
{
MonitorAutoLock mon(mMonitor);
switch (aTrack) {
case TrackType::kAudioTrack:
return &mInfo.mAudio;
case TrackType::kVideoTrack:
return &mInfo.mVideo;
default:
return nullptr;
}
}
TrackBuffersManager*
MediaSourceDemuxer::GetManager(TrackType aTrack)
{
MonitorAutoLock mon(mMonitor);
switch (aTrack) {
case TrackType::kAudioTrack:
return mAudioTrack;
case TrackType::kVideoTrack:
return mVideoTrack;
default:
return nullptr;
}
}
MediaSourceDemuxer::~MediaSourceDemuxer()
{
mTaskQueue->BeginShutdown();
mTaskQueue = nullptr;
}
MediaSourceTrackDemuxer::MediaSourceTrackDemuxer(MediaSourceDemuxer* aParent,
TrackInfo::TrackType aType,
TrackBuffersManager* aManager)
: mParent(aParent)
, mManager(aManager)
, mType(aType)
, mNextSampleIndex(0)
, mMonitor("MediaSourceTrackDemuxer")
{
}
UniquePtr<TrackInfo>
MediaSourceTrackDemuxer::GetInfo() const
{
return mParent->GetTrackInfo(mType)->Clone();
}
nsRefPtr<MediaSourceTrackDemuxer::SeekPromise>
MediaSourceTrackDemuxer::Seek(media::TimeUnit aTime)
{
MOZ_ASSERT(mParent, "Called after BreackCycle()");
return ProxyMediaCall(mParent->GetTaskQueue(), this, __func__,
&MediaSourceTrackDemuxer::DoSeek, aTime);
}
nsRefPtr<MediaSourceTrackDemuxer::SamplesPromise>
MediaSourceTrackDemuxer::GetSamples(int32_t aNumSamples)
{
MOZ_ASSERT(mParent, "Called after BreackCycle()");
return ProxyMediaCall(mParent->GetTaskQueue(), this, __func__,
&MediaSourceTrackDemuxer::DoGetSamples, aNumSamples);
}
void
MediaSourceTrackDemuxer::Reset()
{
MOZ_ASSERT(mParent, "Called after BreackCycle()");
nsRefPtr<MediaSourceTrackDemuxer> self = this;
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction([self] () {
self->mNextSampleTime = TimeUnit();
self->mNextSampleIndex = 0;
{
MonitorAutoLock mon(self->mMonitor);
self->mNextRandomAccessPoint = self->GetNextRandomAccessPoint();
}
});
mParent->GetTaskQueue()->Dispatch(task.forget());
}
nsresult
MediaSourceTrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
{
MonitorAutoLock mon(mMonitor);
*aTime = mNextRandomAccessPoint;
return NS_OK;
}
nsRefPtr<MediaSourceTrackDemuxer::SkipAccessPointPromise>
MediaSourceTrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold)
{
return ProxyMediaCall(mParent->GetTaskQueue(), this, __func__,
&MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint,
aTimeThreshold);
}
int64_t
MediaSourceTrackDemuxer::GetEvictionOffset(media::TimeUnit aTime)
{
// Unused.
return 0;
}
media::TimeIntervals
MediaSourceTrackDemuxer::GetBuffered()
{
return mManager->Buffered();
}
void
MediaSourceTrackDemuxer::BreakCycles()
{
nsRefPtr<MediaSourceTrackDemuxer> self = this;
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction([self]() { self->mParent = nullptr; } );
mParent->GetTaskQueue()->Dispatch(task.forget());
}
nsRefPtr<MediaSourceTrackDemuxer::SeekPromise>
MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime)
{
if (aTime.ToMicroseconds() && !mManager->Buffered(mType).Contains(aTime)) {
// We don't have the data to seek to.
return SeekPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA,
__func__);
}
const TrackBuffersManager::TrackBuffer& track =
mManager->GetTrackBuffer(mType);
TimeUnit lastKeyFrameTime;
uint32_t lastKeyFrameIndex = 0;
for (uint32_t i = 0; i < track.Length(); i++) {
const nsRefPtr<MediaRawData>& sample = track[i];
if (sample->mKeyframe) {
lastKeyFrameTime = TimeUnit::FromMicroseconds(sample->mTime);
lastKeyFrameIndex = i;
}
if (sample->mTime >= aTime.ToMicroseconds()) {
break;
}
}
mNextSampleIndex = lastKeyFrameIndex;
mNextSampleTime = lastKeyFrameTime;
{
MonitorAutoLock mon(mMonitor);
mNextRandomAccessPoint = GetNextRandomAccessPoint();
}
return SeekPromise::CreateAndResolve(mNextSampleTime, __func__);
}
nsRefPtr<MediaSourceTrackDemuxer::SamplesPromise>
MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples)
{
DemuxerFailureReason failure;
nsRefPtr<MediaRawData> sample = GetSample(failure);
if (!sample) {
return SamplesPromise::CreateAndReject(failure, __func__);
}
nsRefPtr<SamplesHolder> samples = new SamplesHolder;
samples->mSamples.AppendElement(sample);
if (mNextRandomAccessPoint.ToMicroseconds() <= sample->mTime) {
MonitorAutoLock mon(mMonitor);
mNextRandomAccessPoint = GetNextRandomAccessPoint();
}
return SamplesPromise::CreateAndResolve(samples, __func__);
}
nsRefPtr<MediaSourceTrackDemuxer::SkipAccessPointPromise>
MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint(media::TimeUnit aTimeThreadshold)
{
bool found = false;
int32_t parsed = 0;
const TrackBuffersManager::TrackBuffer& track =
mManager->GetTrackBuffer(mType);
for (uint32_t i = mNextSampleIndex; i < track.Length(); i++) {
const nsRefPtr<MediaRawData>& sample = track[i];
if (sample->mKeyframe &&
sample->mTime >= aTimeThreadshold.ToMicroseconds()) {
mNextSampleTime = TimeUnit::FromMicroseconds(sample->GetEndTime());
found = true;
break;
}
parsed++;
}
if (found) {
return SkipAccessPointPromise::CreateAndResolve(parsed, __func__);
}
SkipFailureHolder holder(
mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM :
DemuxerFailureReason::WAITING_FOR_DATA, parsed);
return SkipAccessPointPromise::CreateAndReject(holder, __func__);
}
already_AddRefed<MediaRawData>
MediaSourceTrackDemuxer::GetSample(DemuxerFailureReason& aFailure)
{
const TrackBuffersManager::TrackBuffer& track =
mManager->GetTrackBuffer(mType);
const TimeIntervals& ranges = mManager->Buffered(mType);
if (mNextSampleTime >= ranges.GetEnd()) {
if (mManager->IsEnded()) {
aFailure = DemuxerFailureReason::END_OF_STREAM;
} else {
aFailure = DemuxerFailureReason::WAITING_FOR_DATA;
}
return nullptr;
}
if (mNextSampleTime == TimeUnit()) {
// First demux, get first sample time.
mNextSampleTime = ranges.GetStart();
}
if (!ranges.ContainsWithStrictEnd(mNextSampleTime)) {
aFailure = DemuxerFailureReason::WAITING_FOR_DATA;
return nullptr;
}
if (mNextSampleIndex) {
const nsRefPtr<MediaRawData>& sample = track[mNextSampleIndex];
nsRefPtr<MediaRawData> p = sample->Clone();
if (!p) {
aFailure = DemuxerFailureReason::DEMUXER_ERROR;
return nullptr;
}
mNextSampleTime = TimeUnit::FromMicroseconds(sample->GetEndTime());
mNextSampleIndex++;
return p.forget();
}
for (uint32_t i = 0; i < track.Length(); i++) {
const nsRefPtr<MediaRawData>& sample = track[i];
if (sample->mTime >= mNextSampleTime.ToMicroseconds()) {
nsRefPtr<MediaRawData> p = sample->Clone();
mNextSampleTime = TimeUnit::FromMicroseconds(sample->GetEndTime());
mNextSampleIndex = i+1;
if (!p) {
// OOM
break;
}
return p.forget();
}
}
aFailure = DemuxerFailureReason::DEMUXER_ERROR;
return nullptr;
}
TimeUnit
MediaSourceTrackDemuxer::GetNextRandomAccessPoint()
{
const TrackBuffersManager::TrackBuffer& track = mManager->GetTrackBuffer(mType);
for (uint32_t i = mNextSampleIndex; i < track.Length(); i++) {
const nsRefPtr<MediaRawData>& sample = track[i];
if (sample->mKeyframe) {
return TimeUnit::FromMicroseconds(sample->mTime);
}
}
return media::TimeUnit::FromInfinity();
}
void
MediaSourceTrackDemuxer::NotifyTimeRangesChanged()
{
if (!mParent) {
return;
}
MOZ_ASSERT(mParent->OnTaskQueue());
mNextSampleIndex = 0;
}
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,139 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(MediaSourceDemuxer_h_)
#define MediaSourceDemuxer_h_
#include "mozilla/Maybe.h"
#include "MediaDataDemuxer.h"
#include "MediaDecoderReader.h"
#include "MediaResource.h"
#include "MediaSource.h"
#include "MediaTaskQueue.h"
#include "TrackBuffersManager.h"
#include "mozilla/Atomics.h"
#include "mozilla/Monitor.h"
namespace mozilla {
class MediaSourceTrackDemuxer;
class MediaSourceDemuxer : public MediaDataDemuxer
{
public:
explicit MediaSourceDemuxer();
nsRefPtr<InitPromise> Init() override;
bool IsThreadSafe() override { return true; }
already_AddRefed<MediaDataDemuxer> Clone() const override
{
MOZ_CRASH("Shouldn't be called");
return nullptr;
}
bool HasTrackType(TrackInfo::TrackType aType) const override;
uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override;
already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(TrackInfo::TrackType aType,
uint32_t aTrackNumber) override;
bool IsSeekable() const override;
UniquePtr<EncryptionInfo> GetCrypto() override;
bool ShouldComputeStartTime() const override { return false; }
/* interface for TrackBuffersManager */
void AttachSourceBuffer(TrackBuffersManager* aSourceBuffer);
void DetachSourceBuffer(TrackBuffersManager* aSourceBuffer);
MediaTaskQueue* GetTaskQueue() { return mTaskQueue; }
void NotifyTimeRangesChanged();
private:
~MediaSourceDemuxer();
friend class MediaSourceTrackDemuxer;
// Scan source buffers and update information.
bool ScanSourceBuffersForContent();
nsRefPtr<InitPromise> AttemptInit();
TrackBuffersManager* GetManager(TrackInfo::TrackType aType);
TrackInfo* GetTrackInfo(TrackInfo::TrackType);
void DoAttachSourceBuffer(TrackBuffersManager* aSourceBuffer);
void DoDetachSourceBuffer(TrackBuffersManager* aSourceBuffer);
bool OnTaskQueue()
{
return !GetTaskQueue() || GetTaskQueue()->IsCurrentThreadIn();
}
RefPtr<MediaTaskQueue> mTaskQueue;
nsTArray<nsRefPtr<MediaSourceTrackDemuxer>> mDemuxers;
nsTArray<nsRefPtr<TrackBuffersManager>> mSourceBuffers;
// Monitor to protect members below across multiple threads.
mutable Monitor mMonitor;
nsRefPtr<TrackBuffersManager> mAudioTrack;
nsRefPtr<TrackBuffersManager> mVideoTrack;
MediaInfo mInfo;
};
class MediaSourceTrackDemuxer : public MediaTrackDemuxer
{
public:
MediaSourceTrackDemuxer(MediaSourceDemuxer* aParent,
TrackInfo::TrackType aType,
TrackBuffersManager* aManager);
UniquePtr<TrackInfo> GetInfo() const override;
nsRefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
nsRefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
void Reset() override;
nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override;
nsRefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) override;
media::TimeIntervals GetBuffered() override;
int64_t GetEvictionOffset(media::TimeUnit aTime) override;
void BreakCycles() override;
bool GetSamplesMayBlock() const override
{
return false;
}
// Called by TrackBuffersManager to indicate that new frames were added or
// removed.
void NotifyTimeRangesChanged();
private:
nsRefPtr<SeekPromise> DoSeek(media::TimeUnit aTime);
nsRefPtr<SamplesPromise> DoGetSamples(int32_t aNumSamples);
nsRefPtr<SkipAccessPointPromise> DoSkipToNextRandomAccessPoint(TimeUnit aTimeThreadshold);
already_AddRefed<MediaRawData> GetSample(DemuxerFailureReason& aFailure);
// Return the timestamp of the next keyframe after mLastSampleIndex.
TimeUnit GetNextRandomAccessPoint();
nsRefPtr<MediaSourceDemuxer> mParent;
nsRefPtr<TrackBuffersManager> mManager;
TrackInfo::TrackType mType;
uint32_t mNextSampleIndex;
media::TimeUnit mNextSampleTime;
// Monitor protecting members below accessed from multiple threads.
Monitor mMonitor;
media::TimeUnit mNextRandomAccessPoint;
};
} // namespace mozilla
#endif

Просмотреть файл

@ -272,9 +272,17 @@ SourceBuffer::Detach()
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("Detach");
if (!mMediaSource) {
MSE_DEBUG("Already detached");
return;
}
AbortBufferAppend();
if (mContentManager) {
mContentManager->Detach();
if (mIsUsingFormatReader) {
mMediaSource->GetDecoder()->GetDemuxer()->DetachSourceBuffer(
static_cast<mozilla::TrackBuffersManager*>(mContentManager.get()));
}
}
mContentManager = nullptr;
mMediaSource = nullptr;
@ -287,6 +295,9 @@ SourceBuffer::Ended()
MOZ_ASSERT(IsAttached());
MSE_DEBUG("Ended");
mContentManager->Ended();
// We want the MediaSourceReader to refresh its buffered range as it may
// have been modified (end lined up).
mMediaSource->GetDecoder()->NotifyDataArrived(nullptr, 1, mReportedOffset++);
}
SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
@ -299,6 +310,7 @@ SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
, mUpdating(false)
, mActive(false)
, mUpdateID(0)
, mReportedOffset(0)
, mType(aType)
{
MOZ_ASSERT(NS_IsMainThread());
@ -325,6 +337,10 @@ SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
} else {
SetMode(SourceBufferAppendMode::Segments, dummy);
}
if (mIsUsingFormatReader) {
mMediaSource->GetDecoder()->GetDemuxer()->AttachSourceBuffer(
static_cast<mozilla::TrackBuffersManager*>(mContentManager.get()));
}
}
SourceBuffer::~SourceBuffer()
@ -467,7 +483,14 @@ SourceBuffer::AppendDataCompletedWithSuccess(bool aHasActiveTracks)
mActive = true;
mMediaSource->SourceBufferIsActive(this);
mMediaSource->QueueInitializationEvent();
if (mIsUsingFormatReader) {
mMediaSource->GetDecoder()->NotifyWaitingForResourcesStatusChanged();
}
}
// Tell our parent decoder that we have received new data.
// The information provided do not matter much so long as it is monotonically
// increasing.
mMediaSource->GetDecoder()->NotifyDataArrived(nullptr, 1, mReportedOffset++);
}
CheckEndTime();

Просмотреть файл

@ -192,6 +192,7 @@ private:
// This allows for a queued AppendData task to identify if it was earlier
// aborted and another AppendData queued.
uint32_t mUpdateID;
int64_t mReportedOffset;
MediaPromiseRequestHolder<SourceBufferContentManager::AppendPromise> mPendingAppend;
const nsCString mType;

Просмотреть файл

@ -7,6 +7,7 @@
#include "TrackBuffersManager.h"
#include "SourceBufferResource.h"
#include "SourceBuffer.h"
#include "MediaSourceDemuxer.h"
#ifdef MOZ_FMP4
#include "MP4Demuxer.h"
@ -46,10 +47,10 @@ TrackBuffersManager::TrackBuffersManager(dom::SourceBuffer* aParent, MediaSource
, mParser(ContainerParser::CreateForMIMEType(aType))
, mProcessedInput(0)
, mAppendRunning(false)
, mTaskQueue(new MediaTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
/* aSupportsTailDispatch = */ true))
, mTaskQueue(aParentDecoder->GetDemuxer()->GetTaskQueue())
, mParent(new nsMainThreadPtrHolder<dom::SourceBuffer>(aParent, false /* strict */))
, mParentDecoder(new nsMainThreadPtrHolder<MediaSourceDecoder>(aParentDecoder, false /* strict */))
, mMediaSourceDemuxer(mParentDecoder->GetDemuxer())
, mMediaSourceDuration(mTaskQueue, Maybe<double>(), "TrackBuffersManager::mMediaSourceDuration (Mirror)")
, mAbort(false)
, mMonitor("TrackBuffersManager")
@ -520,6 +521,9 @@ TrackBuffersManager::CodedFrameRemoval(TimeInterval aInterval)
// Update our reported total size.
mSizeSourceBuffer = mVideoTracks.mSizeBuffer + mAudioTracks.mSizeBuffer;
// Tell our demuxer that data was removed.
mMediaSourceDemuxer->NotifyTimeRangesChanged();
return dataRemoved;
}
@ -1093,6 +1097,9 @@ TrackBuffersManager::CompleteCodedFrameProcessing()
// 7. Set append state to WAITING_FOR_SEGMENT.
SetAppendState(AppendState::WAITING_FOR_SEGMENT);
// Tell our demuxer that data was added.
mMediaSourceDemuxer->NotifyTimeRangesChanged();
// 8. Jump to the loop top step above.
ResolveProcessing(false, __func__);
}
@ -1451,8 +1458,6 @@ TrackBuffersManager::RestartGroupStartTimestamp()
TrackBuffersManager::~TrackBuffersManager()
{
mTaskQueue->BeginShutdown();
mTaskQueue = nullptr;
}
MediaInfo

Просмотреть файл

@ -22,6 +22,7 @@ namespace mozilla {
class ContainerParser;
class MediaLargeByteBuffer;
class MediaRawData;
class MediaSourceDemuxer;
class SourceBuffer;
class SourceBufferResource;
@ -263,6 +264,7 @@ private:
// Strong references to external objects.
nsMainThreadPtrHandle<dom::SourceBuffer> mParent;
nsMainThreadPtrHandle<MediaSourceDecoder> mParentDecoder;
nsRefPtr<MediaSourceDemuxer> mMediaSourceDemuxer;
// MediaSource duration mirrored from MediaDecoder on the main thread..
Mirror<Maybe<double>> mMediaSourceDuration;

Просмотреть файл

@ -8,6 +8,7 @@ MOCHITEST_MANIFESTS += ['test/mochitest.ini']
EXPORTS += [
'AsyncEventRunner.h',
'MediaSourceDecoder.h',
'MediaSourceDemuxer.h',
'MediaSourceReader.h',
'SourceBufferContentManager.h',
]
@ -22,6 +23,7 @@ UNIFIED_SOURCES += [
'ContainerParser.cpp',
'MediaSource.cpp',
'MediaSourceDecoder.cpp',
'MediaSourceDemuxer.cpp',
'MediaSourceReader.cpp',
'MediaSourceUtils.cpp',
'ResourceQueue.cpp',

Просмотреть файл

@ -396,9 +396,6 @@ MediaCodecReader::DecodeAudioDataSync()
} else if (status == -EAGAIN) {
if (TimeStamp::Now() > timeout) {
// Don't let this loop run for too long. Try it again later.
if (CheckAudioResources()) {
DispatchAudioTask();
}
return;
}
continue; // Try it again now.
@ -441,11 +438,22 @@ MediaCodecReader::DecodeAudioDataSync()
void
MediaCodecReader::DecodeAudioDataTask()
{
if (AudioQueue().GetSize() == 0 && !AudioQueue().IsFinished()) {
DecodeAudioDataSync();
}
MOZ_ASSERT(mAudioTrack.mTaskQueue->IsCurrentThreadIn());
MonitorAutoLock al(mAudioTrack.mTrackMonitor);
if (mAudioTrack.mAudioPromise.IsEmpty()) {
// Clear the data in queue because the promise might be canceled by
// ResetDecode().
AudioQueue().Reset();
return;
}
if (AudioQueue().GetSize() == 0 && !AudioQueue().IsFinished()) {
MonitorAutoUnlock ul(mAudioTrack.mTrackMonitor);
DecodeAudioDataSync();
}
// Since we unlock the monitor above, we should check the promise again
// because the promise might be canceled by ResetDecode().
if (mAudioTrack.mAudioPromise.IsEmpty()) {
AudioQueue().Reset();
return;
}
if (AudioQueue().GetSize() > 0) {
@ -467,9 +475,22 @@ MediaCodecReader::DecodeAudioDataTask()
void
MediaCodecReader::DecodeVideoFrameTask(int64_t aTimeThreshold)
{
DecodeVideoFrameSync(aTimeThreshold);
MOZ_ASSERT(mVideoTrack.mTaskQueue->IsCurrentThreadIn());
MonitorAutoLock al(mVideoTrack.mTrackMonitor);
if (mVideoTrack.mVideoPromise.IsEmpty()) {
// Clear the data in queue because the promise might be canceled by
// ResetDecode().
VideoQueue().Reset();
return;
}
{
MonitorAutoUnlock ul(mVideoTrack.mTrackMonitor);
DecodeVideoFrameSync(aTimeThreshold);
}
// Since we unlock the monitor above, we should check the promise again
// because the promise might be canceled by ResetDecode().
if (mVideoTrack.mVideoPromise.IsEmpty()) {
VideoQueue().Reset();
return;
}
if (VideoQueue().GetSize() > 0) {
@ -740,7 +761,6 @@ nsresult
MediaCodecReader::ResetDecode()
{
if (CheckAudioResources()) {
mAudioTrack.mTaskQueue->Flush();
MonitorAutoLock al(mAudioTrack.mTrackMonitor);
if (!mAudioTrack.mAudioPromise.IsEmpty()) {
mAudioTrack.mAudioPromise.Reject(CANCELED, __func__);
@ -749,7 +769,6 @@ MediaCodecReader::ResetDecode()
mAudioTrack.mDiscontinuity = true;
}
if (CheckVideoResources()) {
mVideoTrack.mTaskQueue->Flush();
MonitorAutoLock al(mVideoTrack.mTrackMonitor);
if (!mVideoTrack.mVideoPromise.IsEmpty()) {
mVideoTrack.mVideoPromise.Reject(CANCELED, __func__);
@ -893,9 +912,6 @@ MediaCodecReader::DecodeVideoFrameSync(int64_t aTimeThreshold)
} else if (status == -EAGAIN) {
if (TimeStamp::Now() > timeout) {
// Don't let this loop run for too long. Try it again later.
if (CheckVideoResources()) {
DispatchVideoTask(aTimeThreshold);
}
return;
}
continue; // Try it again now.
@ -1027,16 +1043,15 @@ MediaCodecReader::Seek(int64_t aTime, int64_t aEndTime)
{
MOZ_ASSERT(OnTaskQueue());
mVideoTrack.mSeekTimeUs = aTime;
mAudioTrack.mSeekTimeUs = aTime;
mVideoTrack.mInputEndOfStream = false;
mVideoTrack.mOutputEndOfStream = false;
mAudioTrack.mInputEndOfStream = false;
mAudioTrack.mOutputEndOfStream = false;
mAudioTrack.mFlushed = false;
mVideoTrack.mFlushed = false;
int64_t timestamp = sInvalidTimestampUs;
if (CheckVideoResources()) {
MonitorAutoLock al(mVideoTrack.mTrackMonitor);
mVideoTrack.mSeekTimeUs = aTime;
mVideoTrack.mInputEndOfStream = false;
mVideoTrack.mOutputEndOfStream = false;
mVideoTrack.mFlushed = false;
VideoFrameContainer* videoframe = mDecoder->GetVideoFrameContainer();
if (videoframe) {
layers::ImageContainer* image = videoframe->GetImageContainer();
@ -1047,7 +1062,6 @@ MediaCodecReader::Seek(int64_t aTime, int64_t aEndTime)
MediaBuffer* source_buffer = nullptr;
MediaSource::ReadOptions options;
int64_t timestamp = sInvalidTimestampUs;
options.setSeekTo(aTime, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC);
if (mVideoTrack.mSource->read(&source_buffer, &options) != OK ||
source_buffer == nullptr) {
@ -1058,12 +1072,25 @@ MediaCodecReader::Seek(int64_t aTime, int64_t aEndTime)
if (format->findInt64(kKeyTime, &timestamp) &&
IsValidTimestampUs(timestamp)) {
mVideoTrack.mSeekTimeUs = timestamp;
mAudioTrack.mSeekTimeUs = timestamp;
}
format = nullptr;
}
source_buffer->release();
}
{
MonitorAutoLock al(mAudioTrack.mTrackMonitor);
mAudioTrack.mInputEndOfStream = false;
mAudioTrack.mOutputEndOfStream = false;
mAudioTrack.mFlushed = false;
if (IsValidTimestampUs(timestamp)) {
mAudioTrack.mSeekTimeUs = timestamp;
} else {
mAudioTrack.mSeekTimeUs = aTime;
}
}
return SeekPromise::CreateAndResolve(aTime, __func__);
}
@ -1284,11 +1311,11 @@ bool
MediaCodecReader::CreateTaskQueues()
{
if (mAudioTrack.mSource != nullptr && !mAudioTrack.mTaskQueue) {
mAudioTrack.mTaskQueue = CreateFlushableMediaDecodeTaskQueue();
mAudioTrack.mTaskQueue = CreateMediaDecodeTaskQueue();
NS_ENSURE_TRUE(mAudioTrack.mTaskQueue, false);
}
if (mVideoTrack.mSource != nullptr && !mVideoTrack.mTaskQueue) {
mVideoTrack.mTaskQueue = CreateFlushableMediaDecodeTaskQueue();
mVideoTrack.mTaskQueue = CreateMediaDecodeTaskQueue();
NS_ENSURE_TRUE(mVideoTrack.mTaskQueue, false);
mVideoTrack.mReleaseBufferTaskQueue = CreateMediaDecodeTaskQueue();
NS_ENSURE_TRUE(mVideoTrack.mReleaseBufferTaskQueue, false);

Просмотреть файл

@ -148,15 +148,13 @@ protected:
// playback parameters
CheckedUint32 mInputIndex;
// mDiscontinuity, mFlushed, mInputEndOfStream, mInputEndOfStream,
// mSeekTimeUs don't be protected by a lock because the
// mTaskQueue->Flush() will flush all tasks.
bool mInputEndOfStream;
bool mOutputEndOfStream;
int64_t mSeekTimeUs;
bool mFlushed; // meaningless when mSeekTimeUs is invalid.
bool mDiscontinuity;
nsRefPtr<FlushableMediaTaskQueue> mTaskQueue;
nsRefPtr<MediaTaskQueue> mTaskQueue;
Monitor mTrackMonitor;
private:

Просмотреть файл

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This XML file describes the encryption applied to |bipbop_<res>-cenc*|. To
generate the encrypted files, run bipbop-cenc.sh
-->
<GPACDRM type="CENC AES-CTR">
<DRMInfo type="pssh" version="1">
<!--
SystemID specified in
https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
-->
<BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" />
<!-- Number of KeyIDs = 1 -->
<BS bits="32" value="1" />
<!-- KeyID -->
<BS ID128="0x7e571d047e571d047e571d047e571d21" />
</DRMInfo>
<CrypTrack trackID="2" isEncrypted="1" IV_size="16" saiSavedBox="senc"
first_IV="0x00000000000000000000000000000000">
<key KID="0x7e571d047e571d047e571d047e571d21"
value="0x7e5744447e5744447e5744447e574421" />
</CrypTrack>
</GPACDRM>

Просмотреть файл

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This XML file describes the encryption applied to |bipbop_<res>-cenc*|. To
generate the encrypted files, run bipbop-cenc.sh
-->
<GPACDRM type="CENC AES-CTR">
<DRMInfo type="pssh" version="1">
<!--
SystemID specified in
https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
-->
<BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" />
<!-- Number of KeyIDs = 1 -->
<BS bits="32" value="1" />
<!-- KeyID -->
<BS ID128="0x7e571d047e571d047e571d047e571d22" />
</DRMInfo>
<CrypTrack trackID="2" isEncrypted="1" IV_size="16" saiSavedBox="senc"
first_IV="0x00000000000000000000000000000000">
<key KID="0x7e571d047e571d047e571d047e571d22"
value="0x7e5744447e5744447e5744447e574422" />
</CrypTrack>
</GPACDRM>

Просмотреть файл

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This XML file describes the encryption applied to |bipbop_<res>-cenc*|. To
generate the encrypted files, run bipbop-cenc.sh
-->
<GPACDRM type="CENC AES-CTR">
<DRMInfo type="pssh" version="1">
<!--
SystemID specified in
https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
-->
<BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" />
<!-- Number of KeyIDs = 1 -->
<BS bits="32" value="1" />
<!-- KeyID -->
<BS ID128="0x7e571d037e571d037e571d037e571d11" />
</DRMInfo>
<CrypTrack trackID="1" isEncrypted="1" IV_size="16" saiSavedBox="senc"
first_IV="0x00000000000000000000000000000000">
<key KID="0x7e571d037e571d037e571d037e571d11"
value="0x7e5733337e5733337e5733337e573311" />
</CrypTrack>
</GPACDRM>

Просмотреть файл

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This XML file describes the encryption applied to |bipbop_<res>-cenc*|. To
generate the encrypted files, run bipbop-cenc.sh
-->
<GPACDRM type="CENC AES-CTR">
<DRMInfo type="pssh" version="1">
<!--
SystemID specified in
https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
-->
<BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" />
<!-- Number of KeyIDs = 1 -->
<BS bits="32" value="1" />
<!-- KeyID -->
<BS ID128="0x7e571d037e571d037e571d037e571d12" />
</DRMInfo>
<CrypTrack trackID="1" isEncrypted="1" IV_size="16" saiSavedBox="senc"
first_IV="0x00000000000000000000000000000000">
<key KID="0x7e571d037e571d037e571d037e571d12"
value="0x7e5733337e5733337e5733337e573312" />
</CrypTrack>
</GPACDRM>

29
dom/media/test/bipbop-cenc.sh Executable file
Просмотреть файл

@ -0,0 +1,29 @@
mkdir work.tmp
for r in 225w_175kbps 300_215kbps 300wp_227kbps 360w_253kbps 480_624kbps 480wp_663kbps 480_959kbps 480wp_1001kbps
do
for k in 1 2
do
# Encrypt bipbop_<res>.mp4 with the keys specified in this file,
# and output to |bipbop_<res>-cenc-{video,audio}.mp4|
MP4Box -crypt bipbop-cenc-audio-key${k}.xml -rem 1 -out work.tmp/bipbop_${r}-cenc-audio-key${k}.mp4 bipbop_${r}.mp4
MP4Box -crypt bipbop-cenc-video-key${k}.xml -rem 2 -out work.tmp/bipbop_${r}-cenc-video-key${k}.mp4 bipbop_${r}.mp4
# Fragment |bipbop_<res>-cenc-*.mp4| into 500ms segments:
MP4Box -dash 500 -rap -segment-name work.tmp/bipbop_${r}-cenc-audio-key${k}- -subsegs-per-sidx 5 work.tmp/bipbop_${r}-cenc-audio-key${k}.mp4
MP4Box -dash 500 -rap -segment-name work.tmp/bipbop_${r}-cenc-video-key${k}- -subsegs-per-sidx 5 work.tmp/bipbop_${r}-cenc-video-key${k}.mp4
# The above command will generate a set of fragments |bipbop_<res>-cenc-{video,audio}-*.m4s
# and |bipbop_<res>-cenc-{video,audio}-init.mp4| containing just the init segment.
# Remove unneeded mpd files.
rm bipbop_${r}-cenc-{audio,video}-key${k}_dash.mpd
done
done
# Only keep the first 4 audio & 2 video segments:
cp work.tmp/*-init[.]mp4 ./
cp work.tmp/*audio*-[1234][.]m4s ./
cp work.tmp/*video*-[12][.]m4s ./
rm -Rf work.tmp

Двоичные данные
dom/media/test/bipbop-cenc1-audio3.m4s

Двоичный файл не отображается.

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше