зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to b2g-inbound. a=merge
This commit is contained in:
Коммит
551f69887c
|
@ -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 page’s 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;
|
||||
}
|
||||
|
|
Двоичные данные
dom/indexedDB/test/unit/defaultStorageUpgrade_profile.zip
Двоичные данные
dom/indexedDB/test/unit/defaultStorageUpgrade_profile.zip
Двоичный файл не отображается.
|
@ -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, ×tamp) &&
|
||||
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>
|
|
@ -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
Двоичные данные
dom/media/test/bipbop-cenc1-audio3.m4s
Двоичный файл не отображается.
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче