зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to fx-team
This commit is contained in:
Коммит
bf1832d88a
|
@ -2,35 +2,21 @@
|
|||
* 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 "AccCollector.h"
|
||||
#include "EmbeddedObjCollector.h"
|
||||
|
||||
#include "Accessible.h"
|
||||
|
||||
using namespace mozilla::a11y;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsAccCollector
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AccCollector::
|
||||
AccCollector(Accessible* aRoot, filters::FilterFuncPtr aFilterFunc) :
|
||||
mFilterFunc(aFilterFunc), mRoot(aRoot), mRootChildIdx(0)
|
||||
{
|
||||
}
|
||||
|
||||
AccCollector::~AccCollector()
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t
|
||||
AccCollector::Count()
|
||||
EmbeddedObjCollector::Count()
|
||||
{
|
||||
EnsureNGetIndex(nullptr);
|
||||
return mObjects.Length();
|
||||
}
|
||||
|
||||
Accessible*
|
||||
AccCollector::GetAccessibleAt(uint32_t aIndex)
|
||||
EmbeddedObjCollector::GetAccessibleAt(uint32_t aIndex)
|
||||
{
|
||||
Accessible* accessible = mObjects.SafeElementAt(aIndex, nullptr);
|
||||
if (accessible)
|
||||
|
@ -39,26 +25,13 @@ AccCollector::GetAccessibleAt(uint32_t aIndex)
|
|||
return EnsureNGetObject(aIndex);
|
||||
}
|
||||
|
||||
int32_t
|
||||
AccCollector::GetIndexAt(Accessible* aAccessible)
|
||||
{
|
||||
int32_t index = mObjects.IndexOf(aAccessible);
|
||||
if (index != -1)
|
||||
return index;
|
||||
|
||||
return EnsureNGetIndex(aAccessible);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsAccCollector protected
|
||||
|
||||
Accessible*
|
||||
AccCollector::EnsureNGetObject(uint32_t aIndex)
|
||||
EmbeddedObjCollector::EnsureNGetObject(uint32_t aIndex)
|
||||
{
|
||||
uint32_t childCount = mRoot->ChildCount();
|
||||
while (mRootChildIdx < childCount) {
|
||||
Accessible* child = mRoot->GetChildAt(mRootChildIdx++);
|
||||
if (!(mFilterFunc(child) & filters::eMatch))
|
||||
if (child->IsText())
|
||||
continue;
|
||||
|
||||
AppendObject(child);
|
||||
|
@ -70,12 +43,12 @@ AccCollector::EnsureNGetObject(uint32_t aIndex)
|
|||
}
|
||||
|
||||
int32_t
|
||||
AccCollector::EnsureNGetIndex(Accessible* aAccessible)
|
||||
EmbeddedObjCollector::EnsureNGetIndex(Accessible* aAccessible)
|
||||
{
|
||||
uint32_t childCount = mRoot->ChildCount();
|
||||
while (mRootChildIdx < childCount) {
|
||||
Accessible* child = mRoot->GetChildAt(mRootChildIdx++);
|
||||
if (!(mFilterFunc(child) & filters::eMatch))
|
||||
if (child->IsText())
|
||||
continue;
|
||||
|
||||
AppendObject(child);
|
||||
|
@ -86,16 +59,6 @@ AccCollector::EnsureNGetIndex(Accessible* aAccessible)
|
|||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
AccCollector::AppendObject(Accessible* aAccessible)
|
||||
{
|
||||
mObjects.AppendElement(aAccessible);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// EmbeddedObjCollector
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int32_t
|
||||
EmbeddedObjCollector::GetIndexAt(Accessible* aAccessible)
|
||||
{
|
||||
|
@ -106,8 +69,7 @@ EmbeddedObjCollector::GetIndexAt(Accessible* aAccessible)
|
|||
if (aAccessible->mInt.mIndexOfEmbeddedChild != -1)
|
||||
return aAccessible->mInt.mIndexOfEmbeddedChild;
|
||||
|
||||
return mFilterFunc(aAccessible) & filters::eMatch ?
|
||||
EnsureNGetIndex(aAccessible) : -1;
|
||||
return !aAccessible->IsText() ? EnsureNGetIndex(aAccessible) : -1;
|
||||
}
|
||||
|
||||
void
|
|
@ -2,10 +2,8 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_a11y_AccCollector_h__
|
||||
#define mozilla_a11y_AccCollector_h__
|
||||
|
||||
#include "Filters.h"
|
||||
#ifndef mozilla_a11y_EmbeddedObjCollector_h__
|
||||
#define mozilla_a11y_EmbeddedObjCollector_h__
|
||||
|
||||
#include "nsTArray.h"
|
||||
|
||||
|
@ -15,14 +13,18 @@ namespace a11y {
|
|||
class Accessible;
|
||||
|
||||
/**
|
||||
* Collect accessible children complying with filter function. Provides quick
|
||||
* access to accessible by index.
|
||||
* Collect embedded objects. Provide quick access to accessible by index and
|
||||
* vice versa.
|
||||
*/
|
||||
class AccCollector
|
||||
class EmbeddedObjCollector final
|
||||
{
|
||||
public:
|
||||
AccCollector(Accessible* aRoot, filters::FilterFuncPtr aFilterFunc);
|
||||
virtual ~AccCollector();
|
||||
~EmbeddedObjCollector() { }
|
||||
|
||||
/**
|
||||
* Return index of the given accessible within the collection.
|
||||
*/
|
||||
int32_t GetIndexAt(Accessible* aAccessible);
|
||||
|
||||
/**
|
||||
* Return accessible count within the collection.
|
||||
|
@ -34,11 +36,6 @@ public:
|
|||
*/
|
||||
Accessible* GetAccessibleAt(uint32_t aIndex);
|
||||
|
||||
/**
|
||||
* Return index of the given accessible within the collection.
|
||||
*/
|
||||
virtual int32_t GetIndexAt(Accessible* aAccessible);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Ensure accessible at the given index is stored and return it.
|
||||
|
@ -50,43 +47,20 @@ protected:
|
|||
*/
|
||||
int32_t EnsureNGetIndex(Accessible* aAccessible);
|
||||
|
||||
// Make sure it's used by Accessible class only.
|
||||
explicit EmbeddedObjCollector(Accessible* aRoot) :
|
||||
mRoot(aRoot), mRootChildIdx(0) {}
|
||||
|
||||
/**
|
||||
* Append the object to collection.
|
||||
*/
|
||||
virtual void AppendObject(Accessible* aAccessible);
|
||||
|
||||
filters::FilterFuncPtr mFilterFunc;
|
||||
Accessible* mRoot;
|
||||
uint32_t mRootChildIdx;
|
||||
|
||||
nsTArray<Accessible*> mObjects;
|
||||
|
||||
private:
|
||||
AccCollector();
|
||||
AccCollector(const AccCollector&);
|
||||
AccCollector& operator =(const AccCollector&);
|
||||
};
|
||||
|
||||
/**
|
||||
* Collect embedded objects. Provide quick access to accessible by index and
|
||||
* vice versa.
|
||||
*/
|
||||
class EmbeddedObjCollector final : public AccCollector
|
||||
{
|
||||
public:
|
||||
virtual ~EmbeddedObjCollector() { }
|
||||
|
||||
public:
|
||||
virtual int32_t GetIndexAt(Accessible* aAccessible) override;
|
||||
|
||||
protected:
|
||||
// Make sure it's used by Accessible class only.
|
||||
explicit EmbeddedObjCollector(Accessible* aRoot) :
|
||||
AccCollector(aRoot, filters::GetEmbeddedObject) { }
|
||||
|
||||
virtual void AppendObject(Accessible* aAccessible) override;
|
||||
void AppendObject(Accessible* aAccessible);
|
||||
|
||||
friend class Accessible;
|
||||
|
||||
Accessible* mRoot;
|
||||
uint32_t mRootChildIdx;
|
||||
nsTArray<Accessible*> mObjects;
|
||||
};
|
||||
|
||||
} // namespace a11y
|
|
@ -49,9 +49,3 @@ filters::GetCell(Accessible* aAccessible)
|
|||
{
|
||||
return aAccessible->IsTableCell() ? eMatch : eSkipSubtree;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
filters::GetEmbeddedObject(Accessible* aAccessible)
|
||||
{
|
||||
return aAccessible->IsText() ? eSkipSubtree : eMatch | eSkipSubtree;
|
||||
}
|
||||
|
|
|
@ -43,12 +43,6 @@ uint32_t GetRow(Accessible* aAccessible);
|
|||
* Matches cell accessibles in children.
|
||||
*/
|
||||
uint32_t GetCell(Accessible* aAccessible);
|
||||
|
||||
/**
|
||||
* Matches embedded objects in children.
|
||||
*/
|
||||
uint32_t GetEmbeddedObject(Accessible* aAccessible);
|
||||
|
||||
} // namespace filters
|
||||
} // namespace a11y
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -828,7 +828,7 @@ TextAttrsMgr::TextPosValue
|
|||
TextAttrsMgr::TextPosTextAttr::
|
||||
GetTextPosValue(nsIFrame* aFrame) const
|
||||
{
|
||||
const nsStyleCoord& styleCoord = aFrame->StyleTextReset()->mVerticalAlign;
|
||||
const nsStyleCoord& styleCoord = aFrame->StyleDisplay()->mVerticalAlign;
|
||||
switch (styleCoord.GetUnit()) {
|
||||
case eStyleUnit_Enumerated:
|
||||
switch (styleCoord.GetIntValue()) {
|
||||
|
|
|
@ -26,7 +26,6 @@ if CONFIG['MOZ_DEBUG']:
|
|||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'AccCollector.cpp',
|
||||
'AccEvent.cpp',
|
||||
'AccGroupInfo.cpp',
|
||||
'AccIterator.cpp',
|
||||
|
@ -34,6 +33,7 @@ UNIFIED_SOURCES += [
|
|||
'ARIAStateMap.cpp',
|
||||
'Asserts.cpp',
|
||||
'DocManager.cpp',
|
||||
'EmbeddedObjCollector.cpp',
|
||||
'EventQueue.cpp',
|
||||
'EventTree.cpp',
|
||||
'Filters.cpp',
|
||||
|
|
|
@ -901,7 +901,7 @@ RuleCache::ApplyFilter(Accessible* aAccessible, uint16_t* aResult)
|
|||
if ((nsIAccessibleTraversalRule::PREFILTER_TRANSPARENT & mPreFilter) &&
|
||||
!(state & states::OPAQUE1)) {
|
||||
nsIFrame* frame = aAccessible->GetFrame();
|
||||
if (frame->StyleDisplay()->mOpacity == 0.0f) {
|
||||
if (frame->StyleEffects()->mOpacity == 0.0f) {
|
||||
*aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#include "nsIXBLAccessible.h"
|
||||
|
||||
#include "AccCollector.h"
|
||||
#include "EmbeddedObjCollector.h"
|
||||
#include "AccGroupInfo.h"
|
||||
#include "AccIterator.h"
|
||||
#include "nsAccUtils.h"
|
||||
|
@ -1205,8 +1205,7 @@ Accessible::State()
|
|||
if (!frame)
|
||||
return state;
|
||||
|
||||
const nsStyleDisplay* display = frame->StyleDisplay();
|
||||
if (display && display->mOpacity == 1.0f &&
|
||||
if (frame->StyleEffects()->mOpacity == 1.0f &&
|
||||
!(state & states::INVISIBLE)) {
|
||||
state |= states::OPAQUE1;
|
||||
}
|
||||
|
|
|
@ -1059,6 +1059,9 @@ pref("layout.accessiblecaret.bar.enabled", true);
|
|||
pref("layout.accessiblecaret.use_long_tap_injector", false);
|
||||
#endif
|
||||
|
||||
// The active caret is disallow to be dragged across the other (inactive) caret.
|
||||
pref("layout.accessiblecaret.allow_dragging_across_other_caret", false);
|
||||
|
||||
// Enable sync and mozId with Firefox Accounts.
|
||||
pref("services.sync.fxaccounts.enabled", true);
|
||||
pref("identity.fxaccounts.enabled", true);
|
||||
|
|
|
@ -1549,11 +1549,18 @@ pref("media.gmp.decoder.h264", 2);
|
|||
// decode H.264.
|
||||
pref("media.gmp.trial-create.enabled", true);
|
||||
|
||||
#ifdef MOZ_ADOBE_EME
|
||||
#if defined(MOZ_ADOBE_EME) || defined(MOZ_WIDEVINE_EME)
|
||||
pref("browser.eme.ui.enabled", true);
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_ADOBE_EME
|
||||
pref("media.gmp-eme-adobe.enabled", true);
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDEVINE_EME
|
||||
pref("media.gmp-widevinecdm.enabled", true);
|
||||
#endif
|
||||
|
||||
// Play with different values of the decay time and get telemetry,
|
||||
// 0 means to randomize (and persist) the experiment value in users' profiles,
|
||||
// -1 means no experiment is run and we use the preferred value for frecency (6h)
|
||||
|
|
|
@ -2311,28 +2311,6 @@
|
|||
|
||||
var browser = this.getBrowserForTab(aTab);
|
||||
|
||||
var closeWindow = false;
|
||||
var newTab = false;
|
||||
if (this.tabs.length - this._removingTabs.length == 1) {
|
||||
closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
|
||||
!window.toolbar.visible ||
|
||||
Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
|
||||
|
||||
// Closing the tab and replacing it with a blank one is notably slower
|
||||
// than closing the window right away. If the caller opts in, take
|
||||
// the fast path.
|
||||
if (closeWindow &&
|
||||
aCloseWindowFastpath &&
|
||||
this._removingTabs.length == 0) {
|
||||
// This call actually closes the window, unless the user
|
||||
// cancels the operation. We are finished here in both cases.
|
||||
this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow);
|
||||
return null;
|
||||
}
|
||||
|
||||
newTab = true;
|
||||
}
|
||||
|
||||
if (!aTab._pendingPermitUnload && !aAdoptedByTab && !aSkipPermitUnload) {
|
||||
// We need to block while calling permitUnload() because it
|
||||
// processes the event queue and may lead to another removeTab()
|
||||
|
@ -2350,6 +2328,35 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
var closeWindow = false;
|
||||
var newTab = false;
|
||||
if (this.tabs.length - this._removingTabs.length == 1) {
|
||||
closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
|
||||
!window.toolbar.visible ||
|
||||
Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
|
||||
|
||||
if (closeWindow) {
|
||||
// We've already called beforeunload on all the relevant tabs if we get here,
|
||||
// so avoid calling it again:
|
||||
window.skipNextCanClose = true;
|
||||
}
|
||||
|
||||
// Closing the tab and replacing it with a blank one is notably slower
|
||||
// than closing the window right away. If the caller opts in, take
|
||||
// the fast path.
|
||||
if (closeWindow &&
|
||||
aCloseWindowFastpath &&
|
||||
this._removingTabs.length == 0) {
|
||||
// This call actually closes the window, unless the user
|
||||
// cancels the operation. We are finished here in both cases.
|
||||
this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow);
|
||||
return null;
|
||||
}
|
||||
|
||||
newTab = true;
|
||||
}
|
||||
|
||||
aTab.closing = true;
|
||||
this._removingTabs.push(aTab);
|
||||
this._visibleTabs = null; // invalidate cache
|
||||
|
|
|
@ -438,6 +438,7 @@ run-if = e10s
|
|||
[browser_star_hsts.js]
|
||||
[browser_subframe_favicons_not_used.js]
|
||||
[browser_syncui.js]
|
||||
[browser_tab_close_dependent_window.js]
|
||||
[browser_tabDrop.js]
|
||||
skip-if = buildapp == 'mulet'
|
||||
[browser_tabReorder.js]
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
"use strict";
|
||||
|
||||
add_task(function* closing_tab_with_dependents_should_close_window() {
|
||||
info("Opening window");
|
||||
let win = yield BrowserTestUtils.openNewBrowserWindow();
|
||||
|
||||
info("Opening tab with data URI");
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, `data:text/html,<html%20onclick="W=window.open()"><body%20onbeforeunload="W.close()">`);
|
||||
info("Closing original tab in this window.");
|
||||
yield BrowserTestUtils.removeTab(win.gBrowser.tabs[0]);
|
||||
info("Clicking into the window");
|
||||
let depTabOpened = BrowserTestUtils.waitForEvent(win.gBrowser.tabContainer, "TabOpen");
|
||||
yield BrowserTestUtils.synthesizeMouse("html", 0, 0, {}, tab.linkedBrowser);
|
||||
|
||||
let openedTab = (yield depTabOpened).target;
|
||||
info("Got opened tab");
|
||||
|
||||
let windowClosedPromise = BrowserTestUtils.windowClosed(win);
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
is(openedTab.linkedBrowser, null, "Opened tab should also have closed");
|
||||
info("If we timeout now, the window failed to close - that shouldn't happen!");
|
||||
yield windowClosedPromise;
|
||||
});
|
||||
|
|
@ -19,5 +19,10 @@ export MOZ_TELEMETRY_REPORTING=1
|
|||
# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
|
||||
ac_add_options --enable-warnings-as-errors
|
||||
|
||||
# Enable Widevine CDMs on MacOSX in Mozilla builds.
|
||||
# Enabled here on the assumption that downstream vendors will not be using
|
||||
# these build configs.
|
||||
ac_add_options --enable-eme=widevine
|
||||
|
||||
# Package js shell.
|
||||
export MOZ_PACKAGE_JSSHELL=1
|
||||
|
|
|
@ -32,10 +32,10 @@ export MOZ_TELEMETRY_REPORTING=1
|
|||
# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
|
||||
ac_add_options --enable-warnings-as-errors
|
||||
|
||||
# Enable Adobe Primetime CDM on 32-bit Windows in Mozilla builds.
|
||||
# Enable Adobe Primetime and Widevine CDMs on 32-bit Windows in Mozilla builds.
|
||||
# Enabled here on the assumption that downstream vendors will not be using
|
||||
# these build configs.
|
||||
ac_add_options --enable-eme=adobe
|
||||
ac_add_options --enable-eme=adobe,widevine
|
||||
|
||||
# Package js shell.
|
||||
export MOZ_PACKAGE_JSSHELL=1
|
||||
|
|
|
@ -30,10 +30,10 @@ ac_add_options --enable-warnings-as-errors
|
|||
|
||||
. $topsrcdir/build/win64/mozconfig.vs2015
|
||||
|
||||
# Enable Adobe Primetime CDM on 64-bit Windows in Mozilla builds.
|
||||
# Enable Adobe Primetime and Widevine CDMs on 64-bit Windows in Mozilla builds.
|
||||
# Enabled here on the assumption that downstream vendors will not be using
|
||||
# these build configs.
|
||||
ac_add_options --enable-eme=adobe
|
||||
ac_add_options --enable-eme=adobe,widevine
|
||||
|
||||
# Package js shell.
|
||||
export MOZ_PACKAGE_JSSHELL=1
|
||||
|
|
|
@ -510,6 +510,10 @@ a {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a.learn-more-link.webconsole-learn-more-link {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Open DOMNode in inspector button */
|
||||
.open-inspector {
|
||||
background: url("chrome://devtools/skin/images/vview-open-inspector.png") no-repeat 0 0;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
@ -1341,8 +1341,10 @@ Messages.Extended.prototype = Heritage.extend(Messages.Simple.prototype,
|
|||
* The evaluation response packet received from the server.
|
||||
* @param string [errorMessage]
|
||||
* Optional error message to display.
|
||||
* @param string [errorDocLink]
|
||||
* Optional error doc URL to link to.
|
||||
*/
|
||||
Messages.JavaScriptEvalOutput = function(evalResponse, errorMessage)
|
||||
Messages.JavaScriptEvalOutput = function(evalResponse, errorMessage, errorDocLink)
|
||||
{
|
||||
let severity = "log", msg, quoteStrings = true;
|
||||
|
||||
|
@ -1365,7 +1367,13 @@ Messages.JavaScriptEvalOutput = function(evalResponse, errorMessage)
|
|||
severity: severity,
|
||||
quoteStrings: quoteStrings,
|
||||
};
|
||||
Messages.Extended.call(this, [msg], options);
|
||||
|
||||
let messages = [msg];
|
||||
if (errorDocLink) {
|
||||
messages.push(errorDocLink);
|
||||
}
|
||||
|
||||
Messages.Extended.call(this, messages, options);
|
||||
};
|
||||
|
||||
Messages.JavaScriptEvalOutput.prototype = Messages.Extended.prototype;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
@ -308,6 +308,22 @@ JSTerm.prototype = {
|
|||
return;
|
||||
}
|
||||
let errorMessage = response.exceptionMessage;
|
||||
let errorDocURL = response.exceptionDocURL;
|
||||
|
||||
let errorDocLink;
|
||||
if (errorDocURL) {
|
||||
errorMessage += " ";
|
||||
errorDocLink = this.hud.document.createElementNS(XHTML_NS, "a");
|
||||
errorDocLink.className = "learn-more-link webconsole-learn-more-link";
|
||||
errorDocLink.textContent = "[" + l10n.getStr("webConsoleMoreInfoLabel") + "]";
|
||||
errorDocLink.title = errorDocURL;
|
||||
errorDocLink.href = "#";
|
||||
errorDocLink.draggable = false;
|
||||
errorDocLink.addEventListener("click", () => {
|
||||
this.hud.owner.openLink(errorDocURL);
|
||||
});
|
||||
}
|
||||
|
||||
// Wrap thrown strings in Error objects, so `throw "foo"` outputs
|
||||
// "Error: foo"
|
||||
if (typeof(response.exception) === "string") {
|
||||
|
@ -356,7 +372,7 @@ JSTerm.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
let msg = new Messages.JavaScriptEvalOutput(response, errorMessage);
|
||||
let msg = new Messages.JavaScriptEvalOutput(response, errorMessage, errorDocLink);
|
||||
this.hud.output.addMessage(msg);
|
||||
|
||||
if (callback) {
|
||||
|
|
|
@ -166,4 +166,26 @@ function* testJSTerm(hud) {
|
|||
return node.parentNode.getAttribute("severity") === "error" &&
|
||||
node.textContent === Object.prototype.toString();
|
||||
}, "thrown object generates error message");
|
||||
|
||||
// check that errors with entires in errordocs.js display links
|
||||
// alongside their messages.
|
||||
const ErrorDocs = require("devtools/server/actors/errordocs");
|
||||
|
||||
const ErrorDocStatements = {
|
||||
"JSMSG_BAD_RADIX": "(42).toString(0);",
|
||||
"JSMSG_BAD_ARRAY_LENGTH": "([]).length = -1",
|
||||
"JSMSG_NEGATIVE_REPETITION_COUNT": "'abc'.repeat(-1);",
|
||||
"JSMSG_BAD_FORMAL": "var f = Function('x y', 'return x + y;');",
|
||||
"JSMSG_PRECISION_RANGE": "77.1234.toExponential(-1);",
|
||||
};
|
||||
|
||||
for (let errorMessageName of Object.keys(ErrorDocStatements)) {
|
||||
let url = ErrorDocs.GetURL(errorMessageName);
|
||||
|
||||
jsterm.clearOutput();
|
||||
yield jsterm.execute(ErrorDocStatements[errorMessageName]);
|
||||
yield checkResult((node) => {
|
||||
return node.parentNode.getElementsByTagName("a")[0].title == url;
|
||||
}, `error links to ${url}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ Cu.import("resource://devtools/client/shared/browser-loader.js", BrowserLoaderMo
|
|||
const promise = require("promise");
|
||||
const Services = require("Services");
|
||||
const ErrorDocs = require("devtools/server/actors/errordocs");
|
||||
const Telemetry = require("devtools/client/shared/telemetry")
|
||||
|
||||
loader.lazyServiceGetter(this, "clipboardHelper",
|
||||
"@mozilla.org/widget/clipboardhelper;1",
|
||||
|
@ -242,6 +243,8 @@ function WebConsoleFrame(webConsoleOwner) {
|
|||
this.ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
this.FrameView = this.React.createFactory(require("devtools/client/shared/components/frame"));
|
||||
|
||||
this._telemetry = new Telemetry();
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
exports.WebConsoleFrame = WebConsoleFrame;
|
||||
|
@ -1494,6 +1497,11 @@ WebConsoleFrame.prototype = {
|
|||
// Add the more info link node to messages that belong to certain categories
|
||||
this.addMoreInfoLink(msgBody, scriptError);
|
||||
|
||||
// Collect telemetry data regarding JavaScript errors
|
||||
this._telemetry.logKeyed("DEVTOOLS_JAVASCRIPT_ERROR_DISPLAYED",
|
||||
scriptError.errorMessageName,
|
||||
true);
|
||||
|
||||
if (objectActors.size > 0) {
|
||||
node._objectActors = objectActors;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ const { EnvironmentActor } = require("devtools/server/actors/environment");
|
|||
const { ThreadActor } = require("devtools/server/actors/script");
|
||||
const { ObjectActor, LongStringActor, createValueGrip, stringIsLong } = require("devtools/server/actors/object");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const ErrorDocs = require("devtools/server/actors/errordocs");
|
||||
|
||||
loader.lazyRequireGetter(this, "NetworkMonitor", "devtools/shared/webconsole/network-monitor", true);
|
||||
loader.lazyRequireGetter(this, "NetworkMonitorChild", "devtools/shared/webconsole/network-monitor", true);
|
||||
|
@ -873,7 +874,7 @@ WebConsoleActor.prototype =
|
|||
let evalResult = evalInfo.result;
|
||||
let helperResult = evalInfo.helperResult;
|
||||
|
||||
let result, errorMessage, errorGrip = null;
|
||||
let result, errorDocURL, errorMessage, errorGrip = null;
|
||||
if (evalResult) {
|
||||
if ("return" in evalResult) {
|
||||
result = evalResult.return;
|
||||
|
@ -889,6 +890,12 @@ WebConsoleActor.prototype =
|
|||
errorMessage = unsafeDereference && unsafeDereference.toString
|
||||
? unsafeDereference.toString()
|
||||
: String(error);
|
||||
|
||||
// It is possible that we won't have permission to unwrap an
|
||||
// object and retrieve its errorMessageName.
|
||||
try {
|
||||
errorDocURL = ErrorDocs.GetURL(error && error.errorMessageName);
|
||||
} catch (ex) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -910,6 +917,7 @@ WebConsoleActor.prototype =
|
|||
timestamp: timestamp,
|
||||
exception: errorGrip,
|
||||
exceptionMessage: this._createStringGrip(errorMessage),
|
||||
exceptionDocURL: errorDocURL,
|
||||
helperResult: helperResult,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -407,14 +407,23 @@ private:
|
|||
MOZ_ASSERT(aRunnable);
|
||||
}
|
||||
|
||||
// If something goes wrong, we still need to release the ConsoleCallData
|
||||
// object. For this reason we have a custom Cancel method.
|
||||
NS_IMETHOD
|
||||
Cancel() override
|
||||
{
|
||||
mRunnable->ReleaseData();
|
||||
mRunnable->mConsole = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
WorkerRun(JSContext* aCx, workers::WorkerPrivate* aWorkerPrivate) override
|
||||
{
|
||||
MOZ_ASSERT(aWorkerPrivate);
|
||||
aWorkerPrivate->AssertIsOnWorkerThread();
|
||||
|
||||
mRunnable->ReleaseData();
|
||||
mRunnable->mConsole = nullptr;
|
||||
Cancel();
|
||||
|
||||
aWorkerPrivate->RemoveFeature(mRunnable);
|
||||
return true;
|
||||
|
|
|
@ -1018,6 +1018,11 @@ EmptyBlobImpl::CreateSlice(uint64_t aStart, uint64_t aLength,
|
|||
{
|
||||
MOZ_ASSERT(!aStart && !aLength);
|
||||
RefPtr<BlobImpl> impl = new EmptyBlobImpl(aContentType);
|
||||
|
||||
DebugOnly<bool> isMutable;
|
||||
MOZ_ASSERT(NS_SUCCEEDED(impl->GetMutable(&isMutable)));
|
||||
MOZ_ASSERT(!isMutable);
|
||||
|
||||
return impl.forget();
|
||||
}
|
||||
|
||||
|
@ -1025,6 +1030,11 @@ void
|
|||
EmptyBlobImpl::GetInternalStream(nsIInputStream** aStream,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
if (NS_WARN_IF(!aStream)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = NS_NewCStringInputStream(aStream, EmptyCString());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(rv);
|
||||
|
|
|
@ -754,7 +754,17 @@ public:
|
|||
|
||||
explicit EmptyBlobImpl(const nsAString& aContentType)
|
||||
: BlobImplBase(aContentType, 0 /* aLength */)
|
||||
{}
|
||||
{
|
||||
mImmutable = true;
|
||||
}
|
||||
|
||||
EmptyBlobImpl(const nsAString& aName,
|
||||
const nsAString& aContentType,
|
||||
int64_t aLastModifiedDate)
|
||||
: BlobImplBase(aName, aContentType, 0, aLastModifiedDate)
|
||||
{
|
||||
mImmutable = true;
|
||||
}
|
||||
|
||||
virtual void GetInternalStream(nsIInputStream** aStream,
|
||||
ErrorResult& aRv) override;
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
#include <algorithm>
|
||||
#include "nsPIDOMWindow.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class MultipartBlobImpl final : public BlobImplBase
|
||||
{
|
||||
|
@ -104,11 +104,6 @@ public:
|
|||
mName = aName;
|
||||
}
|
||||
|
||||
void SetFromNsIFile(bool aValue)
|
||||
{
|
||||
mIsFromNsIFile = aValue;
|
||||
}
|
||||
|
||||
virtual bool MayBeClonedToOtherThreads() const override;
|
||||
|
||||
protected:
|
||||
|
@ -137,4 +132,7 @@ protected:
|
|||
bool mIsFromNsIFile;
|
||||
};
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
|
||||
#endif // mozilla_dom_MultipartBlobImpl_h
|
||||
|
|
|
@ -454,7 +454,6 @@ ResponsiveImageSelector::ComputeFinalWidthForCurrentViewport(double *aWidth)
|
|||
mSizeValues[i]);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(effectiveWidth >= 0);
|
||||
*aWidth = nsPresContext::AppUnitsToDoubleCSSPixels(std::max(effectiveWidth, 0));
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -12566,49 +12566,37 @@ nsDocument::ShouldLockPointer(Element* aElement, Element* aCurrentLock,
|
|||
bool
|
||||
nsDocument::SetPointerLock(Element* aElement, int aCursorStyle)
|
||||
{
|
||||
// NOTE: aElement will be nullptr when unlocking.
|
||||
nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
|
||||
if (!window) {
|
||||
NS_WARNING("SetPointerLock(): No Window");
|
||||
return false;
|
||||
MOZ_ASSERT(!aElement || aElement->OwnerDoc() == this,
|
||||
"We should be either unlocking pointer (aElement is nullptr), "
|
||||
"or locking pointer to an element in this document");
|
||||
#ifdef DEBUG
|
||||
if (!aElement) {
|
||||
nsCOMPtr<nsIDocument> pointerLockedDoc =
|
||||
do_QueryReferent(EventStateManager::sPointerLockedDoc);
|
||||
MOZ_ASSERT(pointerLockedDoc == this);
|
||||
}
|
||||
#endif
|
||||
|
||||
nsIDocShell *docShell = window->GetDocShell();
|
||||
if (!docShell) {
|
||||
NS_WARNING("SetPointerLock(): No DocShell (window already closed?)");
|
||||
return false;
|
||||
}
|
||||
|
||||
RefPtr<nsPresContext> presContext;
|
||||
docShell->GetPresContext(getter_AddRefs(presContext));
|
||||
if (!presContext) {
|
||||
NS_WARNING("SetPointerLock(): Unable to get presContext in \
|
||||
domWindow->GetDocShell()->GetPresContext()");
|
||||
return false;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
|
||||
nsIPresShell* shell = GetShell();
|
||||
if (!shell) {
|
||||
NS_WARNING("SetPointerLock(): Unable to find presContext->PresShell()");
|
||||
NS_WARNING("SetPointerLock(): No PresShell");
|
||||
return false;
|
||||
}
|
||||
nsPresContext* presContext = shell->GetPresContext();
|
||||
if (!presContext) {
|
||||
NS_WARNING("SetPointerLock(): Unable to get PresContext");
|
||||
return false;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWidget> widget;
|
||||
nsIFrame* rootFrame = shell->GetRootFrame();
|
||||
if (!rootFrame) {
|
||||
NS_WARNING("SetPointerLock(): Unable to get root frame");
|
||||
return false;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWidget> widget = rootFrame->GetNearestWidget();
|
||||
if (!widget) {
|
||||
NS_WARNING("SetPointerLock(): Unable to find widget in \
|
||||
shell->GetRootFrame()->GetNearestWidget();");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aElement && (aElement->OwnerDoc() != this)) {
|
||||
NS_WARNING("SetPointerLock(): Element not in this document.");
|
||||
return false;
|
||||
if (!NS_WARN_IF(!rootFrame)) {
|
||||
widget = rootFrame->GetNearestWidget();
|
||||
NS_WARN_IF_FALSE(widget, "SetPointerLock(): Unable to find widget "
|
||||
"in shell->GetRootFrame()->GetNearestWidget();");
|
||||
if (aElement && !widget) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the cursor and set pointer lock for future mouse events
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
file_bug1011748_redirect.sjs
|
||||
file_bug1011748_OK.sjs
|
||||
file_messagemanager_unload.html
|
||||
file_use_counter_outer.html
|
||||
file_use_counter_svg_getElementById.svg
|
||||
|
@ -26,3 +28,4 @@ skip-if = true # Intermittent failures - bug 987493. Restore the skip-if above o
|
|||
[browser_use_counters.js]
|
||||
[browser_bug1238440.js]
|
||||
skip-if = e10s
|
||||
[browser_bug1011748.js]
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
const gHttpTestRoot = "http://example.com/browser/dom/base/test/";
|
||||
|
||||
add_task(function* () {
|
||||
var statusTexts = [];
|
||||
var xhr = new XMLHttpRequest();
|
||||
var observer = {
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
try {
|
||||
var channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
|
||||
channel.getResponseHeader("Location");
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
statusTexts.push(xhr.statusText);
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "http-on-examine-response", false);
|
||||
yield new Promise((resolve) => {
|
||||
xhr.addEventListener("load", function() {
|
||||
statusTexts.push(this.statusText);
|
||||
is(statusTexts[0], "", "Empty statusText value for HTTP 302");
|
||||
is(statusTexts[1], "OK", "OK statusText value for the redirect.");
|
||||
resolve();
|
||||
});
|
||||
xhr.open("GET", gHttpTestRoot+ "file_bug1011748_redirect.sjs", true);
|
||||
xhr.send();
|
||||
});
|
||||
|
||||
Services.obs.removeObserver(observer, "http-on-examine-response");
|
||||
});
|
|
@ -673,17 +673,12 @@ skip-if = buildapp == 'b2g'
|
|||
[test_bug698384.html]
|
||||
[test_bug704063.html]
|
||||
[test_bug704320_http_http.html]
|
||||
support-files = referrerHelper.js
|
||||
[test_bug704320_http_https.html]
|
||||
support-files = referrerHelper.js
|
||||
[test_bug704320_https_http.html]
|
||||
support-files = referrerHelper.js
|
||||
skip-if = buildapp == 'b2g' # b2g (https://example.com not working bug 1162353)
|
||||
[test_bug704320_https_https.html]
|
||||
support-files = referrerHelper.js
|
||||
skip-if = buildapp == 'b2g' # b2g (https://example.com not working bug 1162353)
|
||||
[test_bug704320_policyset.html]
|
||||
support-files = referrerHelper.js
|
||||
[test_bug704320_policyset2.html]
|
||||
skip-if = os == "mac" # fails intermittently - bug 1101288
|
||||
[test_bug704320_preload.html]
|
||||
|
@ -739,9 +734,7 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #bug 901343, specialpowers.wr
|
|||
[test_bug1101364.html]
|
||||
skip-if = buildapp == 'b2g' || toolkit == 'android'
|
||||
[test_bug1163743.html]
|
||||
support-files = referrerHelper.js
|
||||
[test_bug1165501.html]
|
||||
support-files = referrerHelper.js
|
||||
[test_img_referrer.html]
|
||||
[test_anchor_area_referrer.html]
|
||||
[test_anchor_area_referrer_changing.html]
|
||||
|
@ -857,9 +850,6 @@ support-files = bug444546.sjs
|
|||
[test_bug503473.html]
|
||||
disabled = Disabled due to making the harness time out
|
||||
support-files = file_bug503473-frame.sjs
|
||||
[test_bug1011748.html]
|
||||
skip-if = buildapp == 'b2g' || e10s
|
||||
support-files = file_bug1011748_redirect.sjs file_bug1011748_OK.sjs
|
||||
[test_bug1025933.html]
|
||||
[test_bug1037687.html]
|
||||
[test_element.matches.html]
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1011748
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 1011748</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 1011748 **/
|
||||
"use strict";
|
||||
|
||||
var observer = {
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
try {
|
||||
var channel = aSubject.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
|
||||
channel.getResponseHeader("Location");
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
statusTexts.push(xhr.statusText);
|
||||
}
|
||||
};
|
||||
|
||||
var statusTexts = [];
|
||||
SpecialPowers.addObserver(observer, "http-on-examine-response", false);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener("load", function() {
|
||||
statusTexts.push(this.statusText);
|
||||
SpecialPowers.removeObserver(observer, "http-on-examine-response");
|
||||
is(statusTexts[0], "", "Empty statusText value for HTTP 302");
|
||||
is(statusTexts[1], "OK", "OK statusText value for the redirect.");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
xhr.open("GET", "file_bug1011748_redirect.sjs", true);
|
||||
xhr.send();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1011748">Mozilla Bug 1011748</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -83,7 +83,6 @@ support-files =
|
|||
browserElement_XFrameOptionsAllowFrom.js
|
||||
browserElement_XFrameOptionsDeny.js
|
||||
browserElement_XFrameOptionsSameOrigin.js
|
||||
browserElement_XFrameOptionsSameOrigin.js
|
||||
browserElement_GetContentDimensions.js
|
||||
browserElement_AudioChannel.js
|
||||
browserElement_AudioChannel_nested.js
|
||||
|
|
|
@ -2438,7 +2438,7 @@ CanvasRenderingContext2D::ParseFilter(const nsAString& aString,
|
|||
return false;
|
||||
}
|
||||
|
||||
aFilterChain = sc->StyleSVGReset()->mFilters;
|
||||
aFilterChain = sc->StyleEffects()->mFilters;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -756,7 +756,13 @@ TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, const char* f
|
|||
// call into GL, instead of trying to keep MakeCurrent-ed.
|
||||
|
||||
RefPtr<gfx::DataSourceSurface> dataSurf = mSurf->GetDataSurface();
|
||||
MOZ_ASSERT(dataSurf);
|
||||
|
||||
if (!dataSurf) {
|
||||
// Since GetDataSurface didn't return error code, assume system
|
||||
// is out of memory
|
||||
*out_glError = LOCAL_GL_OUT_OF_MEMORY;
|
||||
return;
|
||||
}
|
||||
|
||||
GLenum error;
|
||||
if (UploadDataSurface(isSubImage, webgl, target, level, dui, xOffset, yOffset,
|
||||
|
|
|
@ -1909,16 +1909,24 @@ ContentEventHandler::ConvertToRootRelativeOffset(nsIFrame* aFrame,
|
|||
{
|
||||
NS_ASSERTION(aFrame, "aFrame must not be null");
|
||||
|
||||
nsPresContext* rootPresContext = aFrame->PresContext()->GetRootPresContext();
|
||||
if (NS_WARN_IF(!rootPresContext)) {
|
||||
nsPresContext* thisPC = aFrame->PresContext();
|
||||
nsPresContext* rootPC = thisPC->GetRootPresContext();
|
||||
if (NS_WARN_IF(!rootPC)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
nsIFrame* rootFrame = rootPresContext->PresShell()->GetRootFrame();
|
||||
nsIFrame* rootFrame = rootPC->PresShell()->GetRootFrame();
|
||||
if (NS_WARN_IF(!rootFrame)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, aRect, rootFrame);
|
||||
|
||||
// TransformFrameRectToAncestor returned the rect in the ancestor's appUnits,
|
||||
// but we want it in aFrame's units (in case of different full-zoom factors),
|
||||
// so convert back.
|
||||
aRect = aRect.ScaleToOtherAppUnitsRoundOut(rootPC->AppUnitsPerDevPixel(),
|
||||
thisPC->AppUnitsPerDevPixel());
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -264,8 +264,8 @@ protected:
|
|||
nsresult GetStartFrameAndOffset(const nsRange* aRange,
|
||||
nsIFrame*& aFrame,
|
||||
int32_t& aOffsetInFrame);
|
||||
// Convert the frame relative offset to the root frame of the root presContext
|
||||
// relative offset.
|
||||
// Convert the frame relative offset to be relative to the root frame of the
|
||||
// root presContext (but still measured in appUnits of aFrame's presContext).
|
||||
nsresult ConvertToRootRelativeOffset(nsIFrame* aFrame,
|
||||
nsRect& aRect);
|
||||
// Expand aXPOffset to the nearest offset in cluster boundary. aForward is
|
||||
|
|
|
@ -4339,10 +4339,6 @@ EventStateManager::SetPointerLock(nsIWidget* aWidget,
|
|||
// NOTE: aElement will be nullptr when unlocking.
|
||||
sIsPointerLocked = !!aElement;
|
||||
|
||||
if (!aWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset mouse wheel transaction
|
||||
WheelTransaction::EndTransaction();
|
||||
|
||||
|
@ -4351,6 +4347,8 @@ EventStateManager::SetPointerLock(nsIWidget* aWidget,
|
|||
do_GetService("@mozilla.org/widget/dragservice;1");
|
||||
|
||||
if (sIsPointerLocked) {
|
||||
MOZ_ASSERT(aWidget, "Locking pointer requires a widget");
|
||||
|
||||
// Store the last known ref point so we can reposition the pointer after unlock.
|
||||
mPreLockPoint = sLastRefPoint;
|
||||
|
||||
|
@ -4358,8 +4356,8 @@ EventStateManager::SetPointerLock(nsIWidget* aWidget,
|
|||
// set the mouse to the center of the window, so that the mouse event
|
||||
// doesn't report any movement.
|
||||
sLastRefPoint = GetWindowClientRectCenter(aWidget);
|
||||
aWidget->SynthesizeNativeMouseMove(sLastRefPoint + aWidget->WidgetToScreenOffset(),
|
||||
nullptr);
|
||||
aWidget->SynthesizeNativeMouseMove(
|
||||
sLastRefPoint + aWidget->WidgetToScreenOffset(), nullptr);
|
||||
|
||||
// Retarget all events to this element via capture.
|
||||
nsIPresShell::SetCapturingContent(aElement, CAPTURE_POINTERLOCK);
|
||||
|
@ -4374,8 +4372,10 @@ EventStateManager::SetPointerLock(nsIWidget* aWidget,
|
|||
// pre-pointerlock position, so that the synthetic mouse event reports
|
||||
// no movement.
|
||||
sLastRefPoint = mPreLockPoint;
|
||||
aWidget->SynthesizeNativeMouseMove(mPreLockPoint + aWidget->WidgetToScreenOffset(),
|
||||
nullptr);
|
||||
if (aWidget) {
|
||||
aWidget->SynthesizeNativeMouseMove(
|
||||
mPreLockPoint + aWidget->WidgetToScreenOffset(), nullptr);
|
||||
}
|
||||
|
||||
// Don't retarget events to this element any more.
|
||||
nsIPresShell::SetCapturingContent(nullptr, CAPTURE_POINTERLOCK);
|
||||
|
|
|
@ -294,6 +294,15 @@ public:
|
|||
static nsWeakPtr sPointerLockedElement;
|
||||
static nsWeakPtr sPointerLockedDoc;
|
||||
|
||||
/**
|
||||
* If the absolute values of mMultiplierX and/or mMultiplierY are equal or
|
||||
* larger than this value, the computed scroll amount isn't rounded down to
|
||||
* the page width or height.
|
||||
*/
|
||||
enum {
|
||||
MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL = 1000
|
||||
};
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Prefs class capsules preference management.
|
||||
|
@ -560,15 +569,6 @@ protected:
|
|||
|
||||
void Reset();
|
||||
|
||||
/**
|
||||
* If the abosolute values of mMultiplierX and/or mMultiplierY are equals or
|
||||
* larger than this value, the computed scroll amount isn't rounded down to
|
||||
* the page width or height.
|
||||
*/
|
||||
enum {
|
||||
MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL = 1000
|
||||
};
|
||||
|
||||
bool mInit[COUNT_OF_MULTIPLIERS];
|
||||
double mMultiplierX[COUNT_OF_MULTIPLIERS];
|
||||
double mMultiplierY[COUNT_OF_MULTIPLIERS];
|
||||
|
|
|
@ -1352,8 +1352,8 @@ function doTestZoomedScroll(aCallback)
|
|||
function doTestWholeScroll(aCallback)
|
||||
{
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["mousewheel.default.delta_multiplier_x", 99999999],
|
||||
["mousewheel.default.delta_multiplier_y", 99999999]]},
|
||||
["mousewheel.default.delta_multiplier_x", 999999],
|
||||
["mousewheel.default.delta_multiplier_y", 999999]]},
|
||||
function() { doTestWholeScroll2(aCallback); });
|
||||
}
|
||||
|
||||
|
|
|
@ -493,7 +493,7 @@ HTMLTableCellElement::MapAttributesIntoRule(const nsMappedAttributes* aAttribute
|
|||
}
|
||||
}
|
||||
}
|
||||
if (aData->mSIDs & NS_STYLE_INHERIT_BIT(TextReset)) {
|
||||
if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
|
||||
nsCSSValue* verticalAlign = aData->ValueForVerticalAlign();
|
||||
if (verticalAlign->GetUnit() == eCSSUnit_Null) {
|
||||
// valign: enum
|
||||
|
|
|
@ -111,7 +111,7 @@ HTMLTableColElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes
|
|||
textAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
|
||||
}
|
||||
}
|
||||
if (aData->mSIDs & NS_STYLE_INHERIT_BIT(TextReset)) {
|
||||
if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
|
||||
nsCSSValue* verticalAlign = aData->ValueForVerticalAlign();
|
||||
if (verticalAlign->GetUnit() == eCSSUnit_Null) {
|
||||
// valign: enum
|
||||
|
|
|
@ -286,7 +286,7 @@ HTMLTableRowElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes
|
|||
textAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
|
||||
}
|
||||
}
|
||||
if (aData->mSIDs & NS_STYLE_INHERIT_BIT(TextReset)) {
|
||||
if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
|
||||
nsCSSValue* verticalAlign = aData->ValueForVerticalAlign();
|
||||
if (verticalAlign->GetUnit() == eCSSUnit_Null) {
|
||||
// valign: enum
|
||||
|
|
|
@ -186,7 +186,7 @@ HTMLTableSectionElement::MapAttributesIntoRule(const nsMappedAttributes* aAttrib
|
|||
textAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
|
||||
}
|
||||
}
|
||||
if (aData->mSIDs & NS_STYLE_INHERIT_BIT(TextReset)) {
|
||||
if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
|
||||
nsCSSValue* verticalAlign = aData->ValueForVerticalAlign();
|
||||
if (verticalAlign->GetUnit() == eCSSUnit_Null) {
|
||||
// valign: enum
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<img srcset="data:,a 2400w" sizes="(min-width: 1px) calc(300px - 100vw)">
|
|
@ -76,4 +76,4 @@ load 1032654.html
|
|||
pref(dom.image.srcset.enabled,true) load 1141260.html
|
||||
load 1228876.html
|
||||
load 1230110.html
|
||||
|
||||
load 1237633.html
|
||||
|
|
|
@ -1431,32 +1431,27 @@ void
|
|||
nsGenericHTMLElement::MapImageAlignAttributeInto(const nsMappedAttributes* aAttributes,
|
||||
nsRuleData* aRuleData)
|
||||
{
|
||||
if (aRuleData->mSIDs & (NS_STYLE_INHERIT_BIT(Display) |
|
||||
NS_STYLE_INHERIT_BIT(TextReset))) {
|
||||
if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
|
||||
const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
|
||||
if (value && value->Type() == nsAttrValue::eEnum) {
|
||||
int32_t align = value->GetEnumValue();
|
||||
if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
|
||||
nsCSSValue* cssFloat = aRuleData->ValueForFloat();
|
||||
if (cssFloat->GetUnit() == eCSSUnit_Null) {
|
||||
if (align == NS_STYLE_TEXT_ALIGN_LEFT) {
|
||||
cssFloat->SetIntValue(NS_STYLE_FLOAT_LEFT, eCSSUnit_Enumerated);
|
||||
} else if (align == NS_STYLE_TEXT_ALIGN_RIGHT) {
|
||||
cssFloat->SetIntValue(NS_STYLE_FLOAT_RIGHT, eCSSUnit_Enumerated);
|
||||
}
|
||||
nsCSSValue* cssFloat = aRuleData->ValueForFloat();
|
||||
if (cssFloat->GetUnit() == eCSSUnit_Null) {
|
||||
if (align == NS_STYLE_TEXT_ALIGN_LEFT) {
|
||||
cssFloat->SetIntValue(NS_STYLE_FLOAT_LEFT, eCSSUnit_Enumerated);
|
||||
} else if (align == NS_STYLE_TEXT_ALIGN_RIGHT) {
|
||||
cssFloat->SetIntValue(NS_STYLE_FLOAT_RIGHT, eCSSUnit_Enumerated);
|
||||
}
|
||||
}
|
||||
if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(TextReset)) {
|
||||
nsCSSValue* verticalAlign = aRuleData->ValueForVerticalAlign();
|
||||
if (verticalAlign->GetUnit() == eCSSUnit_Null) {
|
||||
switch (align) {
|
||||
case NS_STYLE_TEXT_ALIGN_LEFT:
|
||||
case NS_STYLE_TEXT_ALIGN_RIGHT:
|
||||
break;
|
||||
default:
|
||||
verticalAlign->SetIntValue(align, eCSSUnit_Enumerated);
|
||||
break;
|
||||
}
|
||||
nsCSSValue* verticalAlign = aRuleData->ValueForVerticalAlign();
|
||||
if (verticalAlign->GetUnit() == eCSSUnit_Null) {
|
||||
switch (align) {
|
||||
case NS_STYLE_TEXT_ALIGN_LEFT:
|
||||
case NS_STYLE_TEXT_ALIGN_RIGHT:
|
||||
break;
|
||||
default:
|
||||
verticalAlign->SetIntValue(align, eCSSUnit_Enumerated);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,18 @@
|
|||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
|
||||
<script type="text/javascript" src="file_fullscreen-utils.js"></script>
|
||||
<style>
|
||||
html {
|
||||
overflow: hidden;
|
||||
}
|
||||
#placeholder {
|
||||
height: 1000vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="fullscreen"></div>
|
||||
<div id="placeholder"></div>
|
||||
<script>
|
||||
|
||||
const gStyle = document.getElementById("style");
|
||||
|
@ -77,6 +86,15 @@ function enterFullscreen() {
|
|||
info("The backdrop should disappear with the fullscreen element");
|
||||
assertWindowPureColor(window, "white");
|
||||
|
||||
gFullscreen.style.display = "";
|
||||
setBackdropStyle("position: absolute");
|
||||
info("Changing position shouldn't immediately affect the view");
|
||||
assertWindowPureColor(window, "black");
|
||||
|
||||
window.scroll(0, screen.height);
|
||||
info("Scrolled up the absolutely-positioned element");
|
||||
assertWindowPureColor(window, "white");
|
||||
|
||||
addFullscreenChangeContinuation("exit", exitFullscreen);
|
||||
document.exitFullscreen();
|
||||
}
|
||||
|
|
|
@ -10453,7 +10453,7 @@ DatabaseConnection::Close()
|
|||
AssertIsOnConnectionThread();
|
||||
MOZ_ASSERT(mStorageConnection);
|
||||
MOZ_ASSERT(!mDEBUGSavepointCount);
|
||||
MOZ_RELEASE_ASSERT(!mInWriteTransaction);
|
||||
MOZ_ASSERT(!mInWriteTransaction);
|
||||
|
||||
PROFILER_LABEL("IndexedDB",
|
||||
"DatabaseConnection::Close",
|
||||
|
@ -12139,7 +12139,7 @@ ConnectionPool::NoteIdleDatabase(DatabaseInfo* aDatabaseInfo)
|
|||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aDatabaseInfo);
|
||||
MOZ_RELEASE_ASSERT(!aDatabaseInfo->TotalTransactionCount());
|
||||
MOZ_ASSERT(!aDatabaseInfo->TotalTransactionCount());
|
||||
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mThread);
|
||||
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mRunnable);
|
||||
MOZ_ASSERT(!mIdleDatabases.Contains(aDatabaseInfo));
|
||||
|
@ -12313,7 +12313,7 @@ ConnectionPool::PerformIdleDatabaseMaintenance(DatabaseInfo* aDatabaseInfo)
|
|||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aDatabaseInfo);
|
||||
MOZ_RELEASE_ASSERT(!aDatabaseInfo->TotalTransactionCount());
|
||||
MOZ_ASSERT(!aDatabaseInfo->TotalTransactionCount());
|
||||
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mThread);
|
||||
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mRunnable);
|
||||
MOZ_ASSERT(aDatabaseInfo->mIdle);
|
||||
|
@ -12340,7 +12340,7 @@ ConnectionPool::CloseDatabase(DatabaseInfo* aDatabaseInfo)
|
|||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aDatabaseInfo);
|
||||
MOZ_RELEASE_ASSERT(!aDatabaseInfo->TotalTransactionCount());
|
||||
MOZ_ASSERT(!aDatabaseInfo->TotalTransactionCount());
|
||||
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mThread);
|
||||
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mRunnable);
|
||||
MOZ_ASSERT(!aDatabaseInfo->mClosing);
|
||||
|
@ -13965,7 +13965,7 @@ Database::RecvPBackgroundIDBTransactionConstructor(
|
|||
aMode == IDBTransaction::READ_WRITE ||
|
||||
aMode == IDBTransaction::READ_WRITE_FLUSH ||
|
||||
aMode == IDBTransaction::CLEANUP);
|
||||
MOZ_RELEASE_ASSERT(!mClosed);
|
||||
MOZ_ASSERT(!mClosed);
|
||||
|
||||
if (IsInvalidated()) {
|
||||
// This is an expected race. We don't want the child to die here, just don't
|
||||
|
|
|
@ -657,58 +657,6 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
class EmptyBlobImpl final
|
||||
: public BlobImplBase
|
||||
{
|
||||
public:
|
||||
explicit EmptyBlobImpl(const nsAString& aContentType)
|
||||
: BlobImplBase(aContentType, 0)
|
||||
{
|
||||
mImmutable = true;
|
||||
}
|
||||
|
||||
EmptyBlobImpl(const nsAString& aName,
|
||||
const nsAString& aContentType,
|
||||
int64_t aLastModifiedDate)
|
||||
: BlobImplBase(aName, aContentType, 0, aLastModifiedDate)
|
||||
{
|
||||
mImmutable = true;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual already_AddRefed<BlobImpl>
|
||||
CreateSlice(uint64_t /* aStart */,
|
||||
uint64_t aLength,
|
||||
const nsAString& aContentType,
|
||||
ErrorResult& /* aRv */) override
|
||||
{
|
||||
MOZ_ASSERT(!aLength);
|
||||
|
||||
RefPtr<BlobImpl> sliceImpl = new EmptyBlobImpl(aContentType);
|
||||
|
||||
DebugOnly<bool> isMutable;
|
||||
MOZ_ASSERT(NS_SUCCEEDED(sliceImpl->GetMutable(&isMutable)));
|
||||
MOZ_ASSERT(!isMutable);
|
||||
|
||||
return sliceImpl.forget();
|
||||
}
|
||||
|
||||
virtual void
|
||||
GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv) override
|
||||
{
|
||||
if (NS_WARN_IF(!aStream)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
nsString emptyString;
|
||||
aRv = NS_NewStringInputStream(aStream, emptyString);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct MOZ_STACK_CLASS CreateBlobImplMetadata final
|
||||
{
|
||||
nsString mContentType;
|
||||
|
|
|
@ -106,12 +106,6 @@ using namespace mozilla::services;
|
|||
using namespace mozilla::widget;
|
||||
using namespace mozilla::jsipc;
|
||||
|
||||
#if DEUBG
|
||||
#define LOG(args...) printf_stderr(args)
|
||||
#else
|
||||
#define LOG(...)
|
||||
#endif
|
||||
|
||||
// The flags passed by the webProgress notifications are 16 bits shifted
|
||||
// from the ones registered by webProgressListeners.
|
||||
#define NOTIFY_FLAG_SHIFT 16
|
||||
|
@ -460,121 +454,6 @@ TabParent::IsVisible() const
|
|||
return visible;
|
||||
}
|
||||
|
||||
static void LogChannelRelevantInfo(nsIURI* aURI,
|
||||
nsIPrincipal* aLoadingPrincipal,
|
||||
nsIPrincipal* aChannelResultPrincipal,
|
||||
nsContentPolicyType aContentPolicyType) {
|
||||
nsCString loadingOrigin;
|
||||
aLoadingPrincipal->GetOrigin(loadingOrigin);
|
||||
|
||||
nsCString uriString;
|
||||
aURI->GetAsciiSpec(uriString);
|
||||
LOG("Loading %s from origin %s (type: %d)\n", uriString.get(),
|
||||
loadingOrigin.get(),
|
||||
aContentPolicyType);
|
||||
|
||||
nsCString resultPrincipalOrigin;
|
||||
aChannelResultPrincipal->GetOrigin(resultPrincipalOrigin);
|
||||
LOG("Result principal origin: %s\n", resultPrincipalOrigin.get());
|
||||
}
|
||||
|
||||
// This is similar to nsIScriptSecurityManager.getChannelResultPrincipal
|
||||
// but taking signedPkg into account. The reason we can't rely on channel
|
||||
// loadContext/loadInfo is it's dangerous to mutate them on parent process.
|
||||
static already_AddRefed<nsIPrincipal>
|
||||
GetChannelPrincipalWithSingedPkg(nsIChannel* aChannel, const nsACString& aSignedPkg)
|
||||
{
|
||||
NeckoOriginAttributes neckoAttrs;
|
||||
NS_GetOriginAttributes(aChannel, neckoAttrs);
|
||||
|
||||
PrincipalOriginAttributes attrs;
|
||||
attrs.InheritFromNecko(neckoAttrs);
|
||||
attrs.mSignedPkg = NS_ConvertUTF8toUTF16(aSignedPkg);
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal =
|
||||
BasePrincipal::CreateCodebasePrincipal(uri, attrs);
|
||||
|
||||
return principal.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
TabParent::ShouldSwitchProcess(nsIChannel* aChannel, const nsACString& aSignedPkg)
|
||||
{
|
||||
// If we lack of any information which is required to decide the need of
|
||||
// process switch, consider that we should switch process.
|
||||
|
||||
// Prepare the channel loading principal.
|
||||
nsCOMPtr<nsILoadInfo> loadInfo;
|
||||
aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
|
||||
NS_ENSURE_TRUE(loadInfo, true);
|
||||
nsCOMPtr<nsIPrincipal> loadingPrincipal;
|
||||
loadInfo->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal));
|
||||
NS_ENSURE_TRUE(loadingPrincipal, true);
|
||||
|
||||
// Prepare the channel result principal.
|
||||
nsCOMPtr<nsIPrincipal> channelPrincipal =
|
||||
GetChannelPrincipalWithSingedPkg(aChannel, aSignedPkg);
|
||||
|
||||
// Log the debug info which is used to decide the need of proces switch.
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
aChannel->GetURI(getter_AddRefs(uri));
|
||||
LogChannelRelevantInfo(uri, loadingPrincipal, channelPrincipal,
|
||||
loadInfo->InternalContentPolicyType());
|
||||
|
||||
// Check if the signed package is loaded from the same origin.
|
||||
bool sameOrigin = false;
|
||||
loadingPrincipal->Equals(channelPrincipal, &sameOrigin);
|
||||
if (sameOrigin) {
|
||||
LOG("Loading singed package from the same origin. Don't switch process.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this is not a top level document, there's no need to switch process.
|
||||
if (nsIContentPolicy::TYPE_DOCUMENT != loadInfo->InternalContentPolicyType()) {
|
||||
LOG("Subresource of a document. No need to switch process.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
DocShellOriginAttributes attrs = OriginAttributesRef();
|
||||
if (attrs.mSignedPkg == NS_ConvertUTF8toUTF16(aSignedPkg)) {
|
||||
// This tab is made for the incoming signed content.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
TabParent::OnStartSignedPackageRequest(nsIChannel* aChannel,
|
||||
const nsACString& aPackageId)
|
||||
{
|
||||
if (!ShouldSwitchProcess(aChannel, aPackageId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
aChannel->GetURI(getter_AddRefs(uri));
|
||||
|
||||
aChannel->Cancel(NS_BINDING_FAILED);
|
||||
|
||||
nsCString uriString;
|
||||
uri->GetAsciiSpec(uriString);
|
||||
LOG("We decide to switch process. Call nsFrameLoader::SwitchProcessAndLoadURIs: %s\n",
|
||||
uriString.get());
|
||||
|
||||
RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
|
||||
NS_ENSURE_TRUE_VOID(frameLoader);
|
||||
|
||||
nsresult rv = frameLoader->SwitchProcessAndLoadURI(uri, aPackageId);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to switch process.");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TabParent::DestroyInternal()
|
||||
{
|
||||
|
|
|
@ -544,11 +544,6 @@ public:
|
|||
|
||||
layout::RenderFrameParent* GetRenderFrame();
|
||||
|
||||
// Called by HttpChannelParent. The function may use a new process to
|
||||
// reload the URI associated with the given channel.
|
||||
void OnStartSignedPackageRequest(nsIChannel* aChannel,
|
||||
const nsACString& aPackageId);
|
||||
|
||||
void AudioChannelChangeNotification(nsPIDOMWindowOuter* aWindow,
|
||||
AudioChannel aAudioChannel,
|
||||
float aVolume,
|
||||
|
@ -592,10 +587,6 @@ protected:
|
|||
bool InitBrowserConfiguration(const nsCString& aURI,
|
||||
BrowserConfiguration& aConfiguration);
|
||||
|
||||
// Decide whether we have to use a new process to reload the URI associated
|
||||
// with the given channel.
|
||||
bool ShouldSwitchProcess(nsIChannel* aChannel, const nsACString& aSignedPkg);
|
||||
|
||||
ContentCacheInParent mContentCache;
|
||||
|
||||
nsIntRect mRect;
|
||||
|
|
|
@ -29,3 +29,6 @@ openH264_description2=This plugin is automatically installed by Mozilla to compl
|
|||
|
||||
eme-adobe_name=Primetime Content Decryption Module provided by Adobe Systems, Incorporated
|
||||
eme-adobe_description=Play back protected web video.
|
||||
|
||||
widevine_name=WidevineCdm
|
||||
widevine_description=Widevine Content Decryption Module provided by Google Inc.
|
||||
|
|
|
@ -17,7 +17,15 @@ class AudioCompactor
|
|||
public:
|
||||
explicit AudioCompactor(MediaQueue<AudioData>& aQueue)
|
||||
: mQueue(aQueue)
|
||||
{ }
|
||||
{
|
||||
// Determine padding size used by AlignedBuffer.
|
||||
size_t paddedSize = AlignedAudioBuffer::AlignmentPaddingSize();
|
||||
mSamplesPadding = paddedSize / sizeof(AudioDataValue);
|
||||
if (mSamplesPadding * sizeof(AudioDataValue) < paddedSize) {
|
||||
// Round up.
|
||||
mSamplesPadding++;
|
||||
}
|
||||
}
|
||||
|
||||
// Push audio data into the underlying queue with minimal heap allocation
|
||||
// slop. This method is responsible for allocating AudioDataValue[] buffers.
|
||||
|
@ -41,7 +49,13 @@ public:
|
|||
|
||||
while (aFrames > 0) {
|
||||
uint32_t samples = GetChunkSamples(aFrames, aChannels, maxSlop);
|
||||
auto buffer = MakeUnique<AudioDataValue[]>(samples);
|
||||
if (aFrames * aChannels > mSamplesPadding) {
|
||||
samples -= mSamplesPadding;
|
||||
}
|
||||
AlignedAudioBuffer buffer(samples);
|
||||
if (!buffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy audio data to buffer using caller-provided functor.
|
||||
uint32_t framesCopied = aCopyFunc(buffer.get(), samples);
|
||||
|
@ -115,6 +129,7 @@ private:
|
|||
}
|
||||
|
||||
MediaQueue<AudioData> &mQueue;
|
||||
size_t mSamplesPadding;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -0,0 +1,229 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 "AudioConverter.h"
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Parts derived from MythTV AudioConvert Class
|
||||
* Created by Jean-Yves Avenard.
|
||||
*
|
||||
* Copyright (C) Bubblestuff Pty Ltd 2013
|
||||
* Copyright (C) foobum@gmail.com 2010
|
||||
*/
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
AudioConverter::AudioConverter(const AudioConfig& aIn, const AudioConfig& aOut)
|
||||
: mIn(aIn)
|
||||
, mOut(aOut)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(aIn.Rate() == aOut.Rate() &&
|
||||
aIn.Format() == aOut.Format() &&
|
||||
aIn.Interleaved() == aOut.Interleaved(),
|
||||
"No format or rate conversion is supported at this stage");
|
||||
MOZ_DIAGNOSTIC_ASSERT((aIn.Channels() > aOut.Channels() && aOut.Channels() <= 2) ||
|
||||
aIn.Channels() == aOut.Channels(),
|
||||
"Only downmixing to mono or stereo is supported at this stage");
|
||||
MOZ_DIAGNOSTIC_ASSERT(aOut.Interleaved(), "planar audio format not supported");
|
||||
mIn.Layout().MappingTable(mOut.Layout(), mChannelOrderMap);
|
||||
}
|
||||
|
||||
bool
|
||||
AudioConverter::CanWorkInPlace() const
|
||||
{
|
||||
return mIn.Channels() * mIn.Rate() * AudioConfig::SampleSize(mIn.Format()) >=
|
||||
mOut.Channels() * mOut.Rate() * AudioConfig::SampleSize(mOut.Format());
|
||||
}
|
||||
|
||||
size_t
|
||||
AudioConverter::Process(void* aOut, const void* aIn, size_t aBytes)
|
||||
{
|
||||
if (!CanWorkInPlace()) {
|
||||
return 0;
|
||||
}
|
||||
if (mIn.Channels() > mOut.Channels()) {
|
||||
return DownmixAudio(aOut, aIn, aBytes);
|
||||
} else if (mIn.Layout() != mOut.Layout() &&
|
||||
CanReorderAudio()) {
|
||||
ReOrderInterleavedChannels(aOut, aIn, aBytes);
|
||||
}
|
||||
return aBytes;
|
||||
}
|
||||
|
||||
// Reorder interleaved channels.
|
||||
// Can work in place (e.g aOut == aIn).
|
||||
template <class AudioDataType>
|
||||
void
|
||||
_ReOrderInterleavedChannels(AudioDataType* aOut, const AudioDataType* aIn,
|
||||
uint32_t aFrames, uint32_t aChannels,
|
||||
const uint8_t* aChannelOrderMap)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(aChannels <= MAX_AUDIO_CHANNELS);
|
||||
AudioDataType val[MAX_AUDIO_CHANNELS];
|
||||
for (uint32_t i = 0; i < aFrames; i++) {
|
||||
for (uint32_t j = 0; j < aChannels; j++) {
|
||||
val[j] = aIn[aChannelOrderMap[j]];
|
||||
}
|
||||
for (uint32_t j = 0; j < aChannels; j++) {
|
||||
aOut[j] = val[j];
|
||||
}
|
||||
aOut += aChannels;
|
||||
aIn += aChannels;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioConverter::ReOrderInterleavedChannels(void* aOut, const void* aIn,
|
||||
size_t aDataSize) const
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(mIn.Channels() == mOut.Channels());
|
||||
|
||||
if (mOut.Layout() == mIn.Layout()) {
|
||||
return;
|
||||
}
|
||||
if (mOut.Channels() == 1) {
|
||||
// If channel count is 1, planar and non-planar formats are the same and
|
||||
// there's nothing to reorder.
|
||||
if (aOut != aIn) {
|
||||
memmove(aOut, aIn, aDataSize);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t bits = AudioConfig::FormatToBits(mOut.Format());
|
||||
switch (bits) {
|
||||
case 8:
|
||||
_ReOrderInterleavedChannels((uint8_t*)aOut, (const uint8_t*)aIn,
|
||||
aDataSize/sizeof(uint8_t)/mIn.Channels(),
|
||||
mIn.Channels(), mChannelOrderMap);
|
||||
break;
|
||||
case 16:
|
||||
_ReOrderInterleavedChannels((int16_t*)aOut,(const int16_t*)aIn,
|
||||
aDataSize/sizeof(int16_t)/mIn.Channels(),
|
||||
mIn.Channels(), mChannelOrderMap);
|
||||
break;
|
||||
default:
|
||||
MOZ_DIAGNOSTIC_ASSERT(AudioConfig::SampleSize(mOut.Format()) == 4);
|
||||
_ReOrderInterleavedChannels((int32_t*)aOut,(const int32_t*)aIn,
|
||||
aDataSize/sizeof(int32_t)/mIn.Channels(),
|
||||
mIn.Channels(), mChannelOrderMap);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int16_t clipTo15(int32_t aX)
|
||||
{
|
||||
return aX < -32768 ? -32768 : aX <= 32767 ? aX : 32767;
|
||||
}
|
||||
|
||||
size_t
|
||||
AudioConverter::DownmixAudio(void* aOut, const void* aIn, size_t aDataSize) const
|
||||
{
|
||||
MOZ_ASSERT(mIn.Format() == AudioConfig::FORMAT_S16 ||
|
||||
mIn.Format() == AudioConfig::FORMAT_FLT);
|
||||
MOZ_ASSERT(mIn.Channels() >= mOut.Channels());
|
||||
MOZ_ASSERT(mIn.Layout() == AudioConfig::ChannelLayout(mIn.Channels()),
|
||||
"Can only downmix input data in SMPTE layout");
|
||||
MOZ_ASSERT(mOut.Layout() == AudioConfig::ChannelLayout(2) ||
|
||||
mOut.Layout() == AudioConfig::ChannelLayout(1));
|
||||
|
||||
uint32_t channels = mIn.Channels();
|
||||
uint32_t frames =
|
||||
aDataSize / AudioConfig::SampleSize(mOut.Format()) / channels;
|
||||
|
||||
if (channels == 1 && mOut.Channels() == 1) {
|
||||
if (aOut != aIn) {
|
||||
memmove(aOut, aIn, aDataSize);
|
||||
}
|
||||
return aDataSize;
|
||||
}
|
||||
|
||||
if (channels > 2) {
|
||||
if (mIn.Format() == AudioConfig::FORMAT_FLT) {
|
||||
// Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows 5-8.
|
||||
static const float dmatrix[6][8][2]= {
|
||||
/*3*/{{0.5858f,0},{0,0.5858f},{0.4142f,0.4142f}},
|
||||
/*4*/{{0.4226f,0},{0,0.4226f},{0.366f, 0.2114f},{0.2114f,0.366f}},
|
||||
/*5*/{{0.6510f,0},{0,0.6510f},{0.4600f,0.4600f},{0.5636f,0.3254f},{0.3254f,0.5636f}},
|
||||
/*6*/{{0.5290f,0},{0,0.5290f},{0.3741f,0.3741f},{0.3741f,0.3741f},{0.4582f,0.2645f},{0.2645f,0.4582f}},
|
||||
/*7*/{{0.4553f,0},{0,0.4553f},{0.3220f,0.3220f},{0.3220f,0.3220f},{0.2788f,0.2788f},{0.3943f,0.2277f},{0.2277f,0.3943f}},
|
||||
/*8*/{{0.3886f,0},{0,0.3886f},{0.2748f,0.2748f},{0.2748f,0.2748f},{0.3366f,0.1943f},{0.1943f,0.3366f},{0.3366f,0.1943f},{0.1943f,0.3366f}},
|
||||
};
|
||||
// Re-write the buffer with downmixed data
|
||||
const float* in = static_cast<const float*>(aIn);
|
||||
float* out = static_cast<float*>(aOut);
|
||||
for (uint32_t i = 0; i < frames; i++) {
|
||||
float sampL = 0.0;
|
||||
float sampR = 0.0;
|
||||
for (uint32_t j = 0; j < channels; j++) {
|
||||
sampL += in[i*mIn.Channels()+j]*dmatrix[mIn.Channels()-3][j][0];
|
||||
sampR += in[i*mIn.Channels()+j]*dmatrix[mIn.Channels()-3][j][1];
|
||||
}
|
||||
*out++ = sampL;
|
||||
*out++ = sampR;
|
||||
}
|
||||
} else if (mIn.Format() == AudioConfig::FORMAT_S16) {
|
||||
// Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows 5-8.
|
||||
// Coefficients in Q14.
|
||||
static const int16_t dmatrix[6][8][2]= {
|
||||
/*3*/{{9598, 0},{0, 9598},{6786,6786}},
|
||||
/*4*/{{6925, 0},{0, 6925},{5997,3462},{3462,5997}},
|
||||
/*5*/{{10663,0},{0, 10663},{7540,7540},{9234,5331},{5331,9234}},
|
||||
/*6*/{{8668, 0},{0, 8668},{6129,6129},{6129,6129},{7507,4335},{4335,7507}},
|
||||
/*7*/{{7459, 0},{0, 7459},{5275,5275},{5275,5275},{4568,4568},{6460,3731},{3731,6460}},
|
||||
/*8*/{{6368, 0},{0, 6368},{4502,4502},{4502,4502},{5514,3184},{3184,5514},{5514,3184},{3184,5514}}
|
||||
};
|
||||
// Re-write the buffer with downmixed data
|
||||
const int16_t* in = static_cast<const int16_t*>(aIn);
|
||||
int16_t* out = static_cast<int16_t*>(aOut);
|
||||
for (uint32_t i = 0; i < frames; i++) {
|
||||
int32_t sampL = 0;
|
||||
int32_t sampR = 0;
|
||||
for (uint32_t j = 0; j < channels; j++) {
|
||||
sampL+=in[i*channels+j]*dmatrix[channels-3][j][0];
|
||||
sampR+=in[i*channels+j]*dmatrix[channels-3][j][1];
|
||||
}
|
||||
*out++ = clipTo15((sampL + 8192)>>14);
|
||||
*out++ = clipTo15((sampR + 8192)>>14);
|
||||
}
|
||||
} else {
|
||||
MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
|
||||
}
|
||||
|
||||
// If we are to continue downmixing to mono, start working on the output
|
||||
// buffer.
|
||||
aIn = aOut;
|
||||
channels = 2;
|
||||
}
|
||||
|
||||
if (mOut.Channels() == 1) {
|
||||
if (mIn.Format() == AudioConfig::FORMAT_FLT) {
|
||||
const float* in = static_cast<const float*>(aIn);
|
||||
float* out = static_cast<float*>(aOut);
|
||||
for (uint32_t fIdx = 0; fIdx < frames; ++fIdx) {
|
||||
float sample = 0.0;
|
||||
// The sample of the buffer would be interleaved.
|
||||
sample = (in[fIdx*channels] + in[fIdx*channels + 1]) * 0.5;
|
||||
*out++ = sample;
|
||||
}
|
||||
} else if (mIn.Format() == AudioConfig::FORMAT_S16) {
|
||||
const int16_t* in = static_cast<const int16_t*>(aIn);
|
||||
int16_t* out = static_cast<int16_t*>(aOut);
|
||||
for (uint32_t fIdx = 0; fIdx < frames; ++fIdx) {
|
||||
int32_t sample = 0.0;
|
||||
// The sample of the buffer would be interleaved.
|
||||
sample = (in[fIdx*channels] + in[fIdx*channels + 1]) * 0.5;
|
||||
*out++ = sample;
|
||||
}
|
||||
} else {
|
||||
MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
|
||||
}
|
||||
}
|
||||
return frames * AudioConfig::SampleSize(mOut.Format()) * mOut.Channels();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,151 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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(AudioConverter_h)
|
||||
#define AudioConverter_h
|
||||
|
||||
#include "MediaInfo.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
template <AudioConfig::SampleFormat T> struct AudioDataBufferTypeChooser;
|
||||
template <> struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_U8>
|
||||
{ typedef uint8_t Type; };
|
||||
template <> struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_S16>
|
||||
{ typedef int16_t Type; };
|
||||
template <> struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_S24LSB>
|
||||
{ typedef int32_t Type; };
|
||||
template <> struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_S24>
|
||||
{ typedef int32_t Type; };
|
||||
template <> struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_S32>
|
||||
{ typedef int32_t Type; };
|
||||
template <> struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_FLT>
|
||||
{ typedef float Type; };
|
||||
|
||||
// 'Value' is the type used externally to deal with stored value.
|
||||
// AudioDataBuffer can perform conversion between different SampleFormat content.
|
||||
template <AudioConfig::SampleFormat Format, typename Value = typename AudioDataBufferTypeChooser<Format>::Type>
|
||||
class AudioDataBuffer
|
||||
{
|
||||
public:
|
||||
AudioDataBuffer() {}
|
||||
AudioDataBuffer(Value* aBuffer, size_t aLength)
|
||||
: mBuffer(aBuffer, aLength)
|
||||
{}
|
||||
explicit AudioDataBuffer(const AudioDataBuffer& aOther)
|
||||
: mBuffer(aOther.mBuffer)
|
||||
{}
|
||||
AudioDataBuffer(AudioDataBuffer&& aOther)
|
||||
: mBuffer(Move(aOther.mBuffer))
|
||||
{}
|
||||
template <AudioConfig::SampleFormat OtherFormat, typename OtherValue>
|
||||
explicit AudioDataBuffer(const AudioDataBuffer<OtherFormat, OtherValue>& other)
|
||||
{
|
||||
// TODO: Convert from different type, may use asm routines.
|
||||
MOZ_CRASH("Conversion not implemented yet");
|
||||
}
|
||||
|
||||
// A u8, s16 and float aligned buffer can only be treated as
|
||||
// FORMAT_U8, FORMAT_S16 and FORMAT_FLT respectively.
|
||||
// So allow them as copy and move constructors.
|
||||
explicit AudioDataBuffer(const AlignedByteBuffer& aBuffer)
|
||||
: mBuffer(aBuffer)
|
||||
{
|
||||
static_assert(Format == AudioConfig::FORMAT_U8,
|
||||
"Conversion not implemented yet");
|
||||
}
|
||||
explicit AudioDataBuffer(const AlignedShortBuffer& aBuffer)
|
||||
: mBuffer(aBuffer)
|
||||
{
|
||||
static_assert(Format == AudioConfig::FORMAT_S16,
|
||||
"Conversion not implemented yet");
|
||||
}
|
||||
explicit AudioDataBuffer(const AlignedFloatBuffer& aBuffer)
|
||||
: mBuffer(aBuffer)
|
||||
{
|
||||
static_assert(Format == AudioConfig::FORMAT_FLT,
|
||||
"Conversion not implemented yet");
|
||||
}
|
||||
explicit AudioDataBuffer(AlignedByteBuffer&& aBuffer)
|
||||
: mBuffer(Move(aBuffer))
|
||||
{
|
||||
static_assert(Format == AudioConfig::FORMAT_U8,
|
||||
"Conversion not implemented yet");
|
||||
}
|
||||
explicit AudioDataBuffer(AlignedShortBuffer&& aBuffer)
|
||||
: mBuffer(Move(aBuffer))
|
||||
{
|
||||
static_assert(Format == AudioConfig::FORMAT_S16,
|
||||
"Conversion not implemented yet");
|
||||
}
|
||||
explicit AudioDataBuffer(const AlignedFloatBuffer&& aBuffer)
|
||||
: mBuffer(Move(aBuffer))
|
||||
{
|
||||
static_assert(Format == AudioConfig::FORMAT_FLT,
|
||||
"Conversion not implemented yet");
|
||||
}
|
||||
|
||||
Value* Data() const { return mBuffer.Data(); }
|
||||
size_t Length() const { return mBuffer.Length(); }
|
||||
size_t Size() const { return mBuffer.Size(); }
|
||||
AlignedBuffer<Value> Forget()
|
||||
{
|
||||
// Correct type -> Just give values as-is.
|
||||
return Move(mBuffer);
|
||||
}
|
||||
private:
|
||||
AlignedBuffer<Value> mBuffer;
|
||||
};
|
||||
|
||||
typedef AudioDataBuffer<AudioConfig::FORMAT_DEFAULT> AudioSampleBuffer;
|
||||
|
||||
class AudioConverter {
|
||||
public:
|
||||
AudioConverter(const AudioConfig& aIn, const AudioConfig& aOut);
|
||||
|
||||
// Attempt to convert the AudioDataBuffer in place.
|
||||
// Will return 0 if the conversion wasn't possible.
|
||||
// Process may allocate memory internally should intermediary steps be
|
||||
// required.
|
||||
template <AudioConfig::SampleFormat Type, typename Value>
|
||||
size_t Process(AudioDataBuffer<Type, Value>& aBuffer)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format() && mIn.Format() == Type);
|
||||
return Process(aBuffer.Data(), aBuffer.Data(), aBuffer.Size());
|
||||
}
|
||||
template <typename Value>
|
||||
size_t Process(Value* aBuffer, size_t aSamples)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format());
|
||||
return Process(aBuffer, aBuffer, aSamples * AudioConfig::SampleSize(mIn.Format()));
|
||||
}
|
||||
bool CanWorkInPlace() const;
|
||||
bool CanReorderAudio() const
|
||||
{
|
||||
return mIn.Layout().MappingTable(mOut.Layout());
|
||||
}
|
||||
|
||||
private:
|
||||
const AudioConfig mIn;
|
||||
const AudioConfig mOut;
|
||||
uint8_t mChannelOrderMap[MAX_AUDIO_CHANNELS];
|
||||
/**
|
||||
* Process
|
||||
* Parameters:
|
||||
* aOut : destination buffer where converted samples will be copied
|
||||
* aIn : source buffer
|
||||
* aBytes: size in bytes of source buffer
|
||||
*
|
||||
* Return Value: size in bytes of samples converted or 0 if error
|
||||
*/
|
||||
size_t Process(void* aOut, const void* aIn, size_t aBytes);
|
||||
void ReOrderInterleavedChannels(void* aOut, const void* aIn, size_t aDataSize) const;
|
||||
size_t DownmixAudio(void* aOut, const void* aIn, size_t aDataSize) const;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* AudioConverter_h */
|
|
@ -18,6 +18,7 @@
|
|||
#include "CubebUtils.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "gfxPrefs.h"
|
||||
#include "AudioConverter.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -352,6 +353,9 @@ AudioStream::Init(uint32_t aNumChannels, uint32_t aRate,
|
|||
params.format = ToCubebFormat<AUDIO_OUTPUT_FORMAT>::value;
|
||||
mAudioClock.Init();
|
||||
|
||||
AudioConfig inConfig(mChannels, mInRate);
|
||||
AudioConfig outConfig(mOutChannels, mOutRate);
|
||||
mAudioConverter = MakeUnique<AudioConverter>(inConfig, outConfig);
|
||||
return OpenCubeb(params);
|
||||
}
|
||||
|
||||
|
@ -557,10 +561,10 @@ AudioStream::Downmix(Chunk* aChunk)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (aChunk->Channels() > 2 && aChunk->Channels() <= 8) {
|
||||
DownmixAudioToStereo(aChunk->GetWritable(),
|
||||
aChunk->Channels(),
|
||||
aChunk->Frames());
|
||||
if (aChunk->Channels() > 2) {
|
||||
MOZ_ASSERT(mAudioConverter);
|
||||
mAudioConverter->Process(aChunk->GetWritable(),
|
||||
aChunk->Channels() * aChunk->Frames());
|
||||
}
|
||||
|
||||
if (aChunk->Channels() >= 2 && mIsMonoAudioEnabled) {
|
||||
|
|
|
@ -29,6 +29,8 @@ struct CubebDestroyPolicy
|
|||
|
||||
class AudioStream;
|
||||
class FrameHistory;
|
||||
class AudioConfig;
|
||||
class AudioConverter;
|
||||
|
||||
class AudioClock
|
||||
{
|
||||
|
@ -367,10 +369,12 @@ private:
|
|||
|
||||
StreamState mState;
|
||||
bool mIsFirst;
|
||||
// Get this value from the preferece, if true, we would downmix the stereo.
|
||||
// Get this value from the preference, if true, we would downmix the stereo.
|
||||
bool mIsMonoAudioEnabled;
|
||||
|
||||
DataSource& mDataSource;
|
||||
|
||||
UniquePtr<AudioConverter> mAudioConverter;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -347,7 +347,7 @@ MP3TrackDemuxer::Duration() const {
|
|||
|
||||
int64_t numFrames = 0;
|
||||
const auto numAudioFrames = mParser.VBRInfo().NumAudioFrames();
|
||||
if (numAudioFrames) {
|
||||
if (mParser.VBRInfo().IsValid()) {
|
||||
// VBR headers don't include the VBR header frame.
|
||||
numFrames = numAudioFrames.value() + 1;
|
||||
} else {
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#endif
|
||||
#include "VideoUtils.h"
|
||||
#include "ImageContainer.h"
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include <cutils/properties.h>
|
||||
|
@ -47,7 +46,8 @@ AudioData::EnsureAudioBuffer()
|
|||
size_t
|
||||
AudioData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
size_t size = aMallocSizeOf(this) + aMallocSizeOf(mAudioData.get());
|
||||
size_t size =
|
||||
aMallocSizeOf(this) + mAudioData.SizeOfExcludingThis(aMallocSizeOf);
|
||||
if (mAudioBuffer) {
|
||||
size += mAudioBuffer->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
@ -487,35 +487,17 @@ VideoData::Create(const VideoInfo& aInfo,
|
|||
}
|
||||
#endif // MOZ_OMX_DECODER
|
||||
|
||||
// Alignment value - 1. 0 means that data isn't aligned.
|
||||
// For 32-bytes aligned, use 31U.
|
||||
#define RAW_DATA_ALIGNMENT 31U
|
||||
|
||||
MediaRawData::MediaRawData()
|
||||
: MediaData(RAW_DATA, 0)
|
||||
, mCrypto(mCryptoInternal)
|
||||
, mData(nullptr)
|
||||
, mSize(0)
|
||||
, mBuffer(nullptr)
|
||||
, mCapacity(0)
|
||||
{
|
||||
}
|
||||
|
||||
MediaRawData::MediaRawData(const uint8_t* aData, size_t aSize)
|
||||
: MediaData(RAW_DATA, 0)
|
||||
, mCrypto(mCryptoInternal)
|
||||
, mData(nullptr)
|
||||
, mSize(0)
|
||||
, mBuffer(nullptr)
|
||||
, mCapacity(0)
|
||||
, mBuffer(aData, aSize)
|
||||
{
|
||||
if (!EnsureCapacity(aSize)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We ensure sufficient capacity above so this shouldn't fail.
|
||||
memcpy(mData, aData, aSize);
|
||||
mSize = aSize;
|
||||
}
|
||||
|
||||
already_AddRefed<MediaRawData>
|
||||
|
@ -530,46 +512,12 @@ MediaRawData::Clone() const
|
|||
s->mExtraData = mExtraData;
|
||||
s->mCryptoInternal = mCryptoInternal;
|
||||
s->mTrackInfo = mTrackInfo;
|
||||
if (mSize) {
|
||||
if (!s->EnsureCapacity(mSize)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
memcpy(s->mData, mData, mSize);
|
||||
s->mSize = mSize;
|
||||
if (!s->mBuffer.Append(mBuffer.Data(), mBuffer.Length())) {
|
||||
return nullptr;
|
||||
}
|
||||
return s.forget();
|
||||
}
|
||||
|
||||
// EnsureCapacity ensures that the buffer is big enough to hold
|
||||
// aSize. It doesn't set the mSize. It's up to the caller to adjust it.
|
||||
bool
|
||||
MediaRawData::EnsureCapacity(size_t aSize)
|
||||
{
|
||||
const size_t sizeNeeded = aSize + RAW_DATA_ALIGNMENT * 2;
|
||||
|
||||
if (mData && mCapacity >= sizeNeeded) {
|
||||
return true;
|
||||
}
|
||||
auto newBuffer = MakeUniqueFallible<uint8_t[]>(sizeNeeded);
|
||||
if (!newBuffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find alignment address.
|
||||
const uintptr_t alignmask = RAW_DATA_ALIGNMENT;
|
||||
uint8_t* newData = reinterpret_cast<uint8_t*>(
|
||||
(reinterpret_cast<uintptr_t>(newBuffer.get()) + alignmask) & ~alignmask);
|
||||
MOZ_ASSERT(uintptr_t(newData) % (RAW_DATA_ALIGNMENT+1) == 0);
|
||||
memcpy(newData, mData, mSize);
|
||||
|
||||
mBuffer = Move(newBuffer);
|
||||
mCapacity = sizeNeeded;
|
||||
mData = newData;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MediaRawData::~MediaRawData()
|
||||
{
|
||||
}
|
||||
|
@ -578,7 +526,7 @@ size_t
|
|||
MediaRawData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
size_t size = aMallocSizeOf(this);
|
||||
size += aMallocSizeOf(mBuffer.get());
|
||||
size += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@ -594,69 +542,34 @@ MediaRawDataWriter::MediaRawDataWriter(MediaRawData* aMediaRawData)
|
|||
{
|
||||
}
|
||||
|
||||
bool
|
||||
MediaRawDataWriter::EnsureSize(size_t aSize)
|
||||
{
|
||||
if (aSize <= mTarget->mSize) {
|
||||
return true;
|
||||
}
|
||||
if (!mTarget->EnsureCapacity(aSize)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
MediaRawDataWriter::SetSize(size_t aSize)
|
||||
{
|
||||
if (aSize > mTarget->mSize && !EnsureSize(aSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mTarget->mSize = aSize;
|
||||
return true;
|
||||
return mTarget->mBuffer.SetLength(aSize);
|
||||
}
|
||||
|
||||
bool
|
||||
MediaRawDataWriter::Prepend(const uint8_t* aData, size_t aSize)
|
||||
{
|
||||
if (!EnsureSize(aSize + mTarget->mSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Shift the data to the right by aSize to leave room for the new data.
|
||||
memmove(mTarget->mData + aSize, mTarget->mData, mTarget->mSize);
|
||||
memcpy(mTarget->mData, aData, aSize);
|
||||
|
||||
mTarget->mSize += aSize;
|
||||
return true;
|
||||
return mTarget->mBuffer.Prepend(aData, aSize);
|
||||
}
|
||||
|
||||
bool
|
||||
MediaRawDataWriter::Replace(const uint8_t* aData, size_t aSize)
|
||||
{
|
||||
// If aSize is smaller than our current size, we leave the buffer as is,
|
||||
// only adjusting the reported size.
|
||||
if (!EnsureSize(aSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(mTarget->mData, aData, aSize);
|
||||
mTarget->mSize = aSize;
|
||||
return true;
|
||||
return mTarget->mBuffer.Replace(aData, aSize);
|
||||
}
|
||||
|
||||
void
|
||||
MediaRawDataWriter::Clear()
|
||||
{
|
||||
mTarget->mSize = 0;
|
||||
mTarget->mData = nullptr;
|
||||
mTarget->mBuffer.Clear();
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
MediaRawDataWriter::Data()
|
||||
{
|
||||
return mTarget->mData;
|
||||
return mTarget->mBuffer.Data();
|
||||
}
|
||||
|
||||
size_t
|
||||
|
|
|
@ -14,7 +14,10 @@
|
|||
#include "SharedBuffer.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -26,6 +29,237 @@ class ImageContainer;
|
|||
class MediaByteBuffer;
|
||||
class SharedTrackInfo;
|
||||
|
||||
// AlignedBuffer:
|
||||
// Memory allocations are fallibles. Methods return a boolean indicating if
|
||||
// memory allocations were successful. Return values should always be checked.
|
||||
// AlignedBuffer::mData will be nullptr if no memory has been allocated or if
|
||||
// an error occurred during construction.
|
||||
// Existing data is only ever modified if new memory allocation has succeeded
|
||||
// and preserved if not.
|
||||
//
|
||||
// The memory referenced by mData will always be Alignment bytes aligned and the
|
||||
// underlying buffer will always have a size such that Alignment bytes blocks
|
||||
// can be used to read the content, regardless of the mSize value. Buffer is
|
||||
// zeroed on creation, elements are not individually constructed.
|
||||
// An Alignment value of 0 means that the data isn't aligned.
|
||||
//
|
||||
// Type must be trivially copyable.
|
||||
//
|
||||
// AlignedBuffer can typically be used in place of UniquePtr<Type[]> however
|
||||
// care must be taken as all memory allocations are fallible.
|
||||
// Example:
|
||||
// auto buffer = MakeUniqueFallible<float[]>(samples)
|
||||
// becomes: AlignedFloatBuffer buffer(samples)
|
||||
//
|
||||
// auto buffer = MakeUnique<float[]>(samples)
|
||||
// becomes:
|
||||
// AlignedFloatBuffer buffer(samples);
|
||||
// if (!buffer) { return NS_ERROR_OUT_OF_MEMORY; }
|
||||
|
||||
template <typename Type, int Alignment = 32>
|
||||
class AlignedBuffer
|
||||
{
|
||||
public:
|
||||
AlignedBuffer()
|
||||
: mData(nullptr)
|
||||
, mLength(0)
|
||||
, mBuffer(nullptr)
|
||||
, mCapacity(0)
|
||||
{}
|
||||
|
||||
explicit AlignedBuffer(size_t aLength)
|
||||
: mData(nullptr)
|
||||
, mLength(0)
|
||||
, mBuffer(nullptr)
|
||||
, mCapacity(0)
|
||||
{
|
||||
if (EnsureCapacity(aLength)) {
|
||||
mLength = aLength;
|
||||
}
|
||||
}
|
||||
|
||||
AlignedBuffer(const Type* aData, size_t aLength)
|
||||
: AlignedBuffer(aLength)
|
||||
{
|
||||
if (!mData) {
|
||||
return;
|
||||
}
|
||||
PodCopy(mData, aData, aLength);
|
||||
}
|
||||
|
||||
AlignedBuffer(const AlignedBuffer& aOther)
|
||||
: AlignedBuffer(aOther.Data(), aOther.Length())
|
||||
{}
|
||||
|
||||
AlignedBuffer(AlignedBuffer&& aOther)
|
||||
: mData(aOther.mData)
|
||||
, mLength(aOther.mLength)
|
||||
, mBuffer(Move(aOther.mBuffer))
|
||||
, mCapacity(aOther.mCapacity)
|
||||
{
|
||||
aOther.mData = nullptr;
|
||||
aOther.mLength = 0;
|
||||
aOther.mCapacity = 0;
|
||||
}
|
||||
|
||||
AlignedBuffer& operator=(AlignedBuffer&& aOther)
|
||||
{
|
||||
this->~AlignedBuffer();
|
||||
new (this) AlignedBuffer(Move(aOther));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Type* Data() const { return mData; }
|
||||
size_t Length() const { return mLength; }
|
||||
size_t Size() const { return mLength * sizeof(Type); }
|
||||
Type& operator[](size_t aIndex)
|
||||
{
|
||||
MOZ_ASSERT(aIndex < mLength);
|
||||
return mData[aIndex];
|
||||
}
|
||||
const Type& operator[](size_t aIndex) const
|
||||
{
|
||||
MOZ_ASSERT(aIndex < mLength);
|
||||
return mData[aIndex];
|
||||
}
|
||||
// Set length of buffer, allocating memory as required.
|
||||
// If length is increased, new buffer area is filled with 0.
|
||||
bool SetLength(size_t aLength)
|
||||
{
|
||||
if (aLength > mLength && !EnsureCapacity(aLength)) {
|
||||
return false;
|
||||
}
|
||||
mLength = aLength;
|
||||
return true;
|
||||
}
|
||||
// Add aData at the beginning of buffer.
|
||||
bool Prepend(const Type* aData, size_t aLength)
|
||||
{
|
||||
if (!EnsureCapacity(aLength + mLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Shift the data to the right by aLength to leave room for the new data.
|
||||
PodMove(mData + aLength, mData, mLength);
|
||||
PodCopy(mData, aData, aLength);
|
||||
|
||||
mLength += aLength;
|
||||
return true;
|
||||
}
|
||||
// Add aData at the end of buffer.
|
||||
bool Append(const Type* aData, size_t aLength)
|
||||
{
|
||||
if (!EnsureCapacity(aLength + mLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PodCopy(mData + mLength, aData, aLength);
|
||||
|
||||
mLength += aLength;
|
||||
return true;
|
||||
}
|
||||
// Replace current content with aData.
|
||||
bool Replace(const Type* aData, size_t aLength)
|
||||
{
|
||||
// If aLength is smaller than our current length, we leave the buffer as is,
|
||||
// only adjusting the reported length.
|
||||
if (!EnsureCapacity(aLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PodCopy(mData, aData, aLength);
|
||||
mLength = aLength;
|
||||
return true;
|
||||
}
|
||||
// Clear the memory buffer. Will set target mData and mLength to 0.
|
||||
void Clear()
|
||||
{
|
||||
mLength = 0;
|
||||
mData = nullptr;
|
||||
}
|
||||
|
||||
// Methods for reporting memory.
|
||||
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
size_t size = aMallocSizeOf(this);
|
||||
size += aMallocSizeOf(mBuffer.get());
|
||||
return size;
|
||||
}
|
||||
// AlignedBuffer is typically allocated on the stack. As such, you likely
|
||||
// want to use SizeOfExcludingThis
|
||||
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
return aMallocSizeOf(mBuffer.get());
|
||||
}
|
||||
size_t ComputedSizeOfExcludingThis() const
|
||||
{
|
||||
return mCapacity;
|
||||
}
|
||||
|
||||
// For backward compatibility with UniquePtr<Type[]>
|
||||
Type* get() const { return mData; }
|
||||
explicit operator bool() const { return mData != nullptr; }
|
||||
|
||||
// Size in bytes of extra space allocated for padding.
|
||||
static size_t AlignmentPaddingSize()
|
||||
{
|
||||
return AlignmentOffset() * 2;
|
||||
}
|
||||
|
||||
private:
|
||||
static size_t AlignmentOffset()
|
||||
{
|
||||
return Alignment ? Alignment - 1 : 0;
|
||||
}
|
||||
|
||||
// Ensure that the backend buffer can hold aLength data. Will update mData.
|
||||
// Will enforce that the start of allocated data is always Alignment bytes
|
||||
// aligned and that it has sufficient end padding to allow for Alignment bytes
|
||||
// block read as required by some data decoders.
|
||||
// Returns false if memory couldn't be allocated.
|
||||
bool EnsureCapacity(size_t aLength)
|
||||
{
|
||||
const CheckedInt<size_t> sizeNeeded =
|
||||
CheckedInt<size_t>(aLength) * sizeof(Type) + AlignmentPaddingSize();
|
||||
|
||||
if (!sizeNeeded.isValid()) {
|
||||
// overflow.
|
||||
return false;
|
||||
}
|
||||
if (mData && mCapacity >= sizeNeeded.value()) {
|
||||
return true;
|
||||
}
|
||||
auto newBuffer = MakeUniqueFallible<uint8_t[]>(sizeNeeded.value());
|
||||
if (!newBuffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find alignment address.
|
||||
const uintptr_t alignmask = AlignmentOffset();
|
||||
Type* newData = reinterpret_cast<Type*>(
|
||||
(reinterpret_cast<uintptr_t>(newBuffer.get()) + alignmask) & ~alignmask);
|
||||
MOZ_ASSERT(uintptr_t(newData) % (AlignmentOffset()+1) == 0);
|
||||
|
||||
PodZero(newData + mLength, aLength - mLength);
|
||||
PodCopy(newData, mData, mLength);
|
||||
|
||||
mBuffer = Move(newBuffer);
|
||||
mCapacity = sizeNeeded.value();
|
||||
mData = newData;
|
||||
|
||||
return true;
|
||||
}
|
||||
Type* mData;
|
||||
size_t mLength;
|
||||
UniquePtr<uint8_t[]> mBuffer;
|
||||
size_t mCapacity;
|
||||
};
|
||||
|
||||
typedef AlignedBuffer<uint8_t> AlignedByteBuffer;
|
||||
typedef AlignedBuffer<float> AlignedFloatBuffer;
|
||||
typedef AlignedBuffer<int16_t> AlignedShortBuffer;
|
||||
typedef AlignedBuffer<AudioDataValue> AlignedAudioBuffer;
|
||||
|
||||
// Container that holds media samples.
|
||||
class MediaData {
|
||||
public:
|
||||
|
@ -124,7 +358,7 @@ public:
|
|||
int64_t aTime,
|
||||
int64_t aDuration,
|
||||
uint32_t aFrames,
|
||||
UniquePtr<AudioDataValue[]> aData,
|
||||
AlignedAudioBuffer&& aData,
|
||||
uint32_t aChannels,
|
||||
uint32_t aRate)
|
||||
: MediaData(sType, aOffset, aTime, aDuration, aFrames)
|
||||
|
@ -159,7 +393,7 @@ public:
|
|||
// mChannels channels, each with mFrames frames
|
||||
RefPtr<SharedBuffer> mAudioBuffer;
|
||||
// mFrames frames, each with mChannels values
|
||||
UniquePtr<AudioDataValue[]> mAudioData;
|
||||
AlignedAudioBuffer mAudioData;
|
||||
|
||||
protected:
|
||||
~AudioData() {}
|
||||
|
@ -338,12 +572,11 @@ public:
|
|||
nsTArray<nsCString> mSessionIds;
|
||||
};
|
||||
|
||||
|
||||
// MediaRawData is a MediaData container used to store demuxed, still compressed
|
||||
// samples.
|
||||
// Use MediaRawData::CreateWriter() to obtain a MediaRawDataWriter object that
|
||||
// provides methods to modify and manipulate the data.
|
||||
// Memory allocations are fallibles. Methods return a boolean indicating if
|
||||
// Memory allocations are fallible. Methods return a boolean indicating if
|
||||
// memory allocations were successful. Return values should always be checked.
|
||||
// MediaRawData::mData will be nullptr if no memory has been allocated or if
|
||||
// an error occurred during construction.
|
||||
|
@ -396,12 +629,12 @@ public:
|
|||
MediaRawData(const uint8_t* aData, size_t mSize);
|
||||
|
||||
// Pointer to data or null if not-yet allocated
|
||||
const uint8_t* Data() const { return mData; }
|
||||
const uint8_t* Data() const { return mBuffer.Data(); }
|
||||
// Size of buffer.
|
||||
size_t Size() const { return mSize; }
|
||||
size_t Size() const { return mBuffer.Length(); }
|
||||
size_t ComputedSizeOfIncludingThis() const
|
||||
{
|
||||
return sizeof(*this) + mCapacity;
|
||||
return sizeof(*this) + mBuffer.ComputedSizeOfExcludingThis();
|
||||
}
|
||||
|
||||
const CryptoSample& mCrypto;
|
||||
|
@ -421,16 +654,7 @@ protected:
|
|||
|
||||
private:
|
||||
friend class MediaRawDataWriter;
|
||||
// Ensure that the backend buffer can hold aSize data. Will update mData.
|
||||
// Will enforce that the start of allocated data is always 32 bytes
|
||||
// aligned and that it has sufficient end padding to allow for 32 bytes block
|
||||
// read as required by some data decoders.
|
||||
// Returns false if memory couldn't be allocated.
|
||||
bool EnsureCapacity(size_t aSize);
|
||||
uint8_t* mData;
|
||||
size_t mSize;
|
||||
UniquePtr<uint8_t[]> mBuffer;
|
||||
uint32_t mCapacity;
|
||||
AlignedByteBuffer mBuffer;
|
||||
CryptoSample mCryptoInternal;
|
||||
MediaRawData(const MediaRawData&); // Not implemented
|
||||
};
|
||||
|
|
|
@ -736,7 +736,7 @@ MediaDecoder::InitializeStateMachine()
|
|||
MOZ_ASSERT(NS_IsMainThread());
|
||||
NS_ASSERTION(mDecoderStateMachine, "Cannot initialize null state machine!");
|
||||
|
||||
nsresult rv = mDecoderStateMachine->Init();
|
||||
nsresult rv = mDecoderStateMachine->Init(this);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// If some parameters got set before the state machine got created,
|
||||
|
|
|
@ -295,11 +295,6 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
|
|||
MOZ_COUNT_CTOR(MediaDecoderStateMachine);
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
// Dispatch initialization that needs to happen on that task queue.
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<RefPtr<MediaDecoder>>(
|
||||
this, &MediaDecoderStateMachine::InitializationTask, aDecoder);
|
||||
mTaskQueue->Dispatch(r.forget());
|
||||
|
||||
InitVideoQueuePrefs();
|
||||
|
||||
mBufferingWait = IsRealTime() ? 0 : 15;
|
||||
|
@ -313,22 +308,6 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
|
|||
// timeEndPeriod() call.
|
||||
timeBeginPeriod(1);
|
||||
#endif
|
||||
|
||||
mAudioQueueListener = AudioQueue().PopEvent().Connect(
|
||||
mTaskQueue, this, &MediaDecoderStateMachine::OnAudioPopped);
|
||||
mVideoQueueListener = VideoQueue().PopEvent().Connect(
|
||||
mTaskQueue, this, &MediaDecoderStateMachine::OnVideoPopped);
|
||||
|
||||
mMetadataManager.Connect(mReader->TimedMetadataEvent(), OwnerThread());
|
||||
|
||||
mMediaSink = CreateMediaSink(mAudioCaptured);
|
||||
|
||||
#ifdef MOZ_EME
|
||||
mCDMProxyPromise.Begin(aDecoder->RequestCDMProxy()->Then(
|
||||
OwnerThread(), __func__, this,
|
||||
&MediaDecoderStateMachine::OnCDMProxyReady,
|
||||
&MediaDecoderStateMachine::OnCDMProxyNotReady));
|
||||
#endif
|
||||
}
|
||||
|
||||
MediaDecoderStateMachine::~MediaDecoderStateMachine()
|
||||
|
@ -1072,14 +1051,35 @@ bool MediaDecoderStateMachine::IsPlaying() const
|
|||
return mMediaSink->IsPlaying();
|
||||
}
|
||||
|
||||
nsresult MediaDecoderStateMachine::Init()
|
||||
nsresult MediaDecoderStateMachine::Init(MediaDecoder* aDecoder)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Dispatch initialization that needs to happen on that task queue.
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<RefPtr<MediaDecoder>>(
|
||||
this, &MediaDecoderStateMachine::InitializationTask, aDecoder);
|
||||
mTaskQueue->Dispatch(r.forget());
|
||||
|
||||
mAudioQueueListener = AudioQueue().PopEvent().Connect(
|
||||
mTaskQueue, this, &MediaDecoderStateMachine::OnAudioPopped);
|
||||
mVideoQueueListener = VideoQueue().PopEvent().Connect(
|
||||
mTaskQueue, this, &MediaDecoderStateMachine::OnVideoPopped);
|
||||
|
||||
mMetadataManager.Connect(mReader->TimedMetadataEvent(), OwnerThread());
|
||||
|
||||
mMediaSink = CreateMediaSink(mAudioCaptured);
|
||||
|
||||
#ifdef MOZ_EME
|
||||
mCDMProxyPromise.Begin(aDecoder->RequestCDMProxy()->Then(
|
||||
OwnerThread(), __func__, this,
|
||||
&MediaDecoderStateMachine::OnCDMProxyReady,
|
||||
&MediaDecoderStateMachine::OnCDMProxyNotReady));
|
||||
#endif
|
||||
|
||||
nsresult rv = mReader->Init();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(
|
||||
this, &MediaDecoderStateMachine::ReadMetadata);
|
||||
r = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::ReadMetadata);
|
||||
OwnerThread()->Dispatch(r.forget());
|
||||
|
||||
return NS_OK;
|
||||
|
@ -2511,7 +2511,10 @@ MediaDecoderStateMachine::DropAudioUpToSeekTarget(MediaData* aSample)
|
|||
}
|
||||
uint32_t frames = audio->mFrames - static_cast<uint32_t>(framesToPrune.value());
|
||||
uint32_t channels = audio->mChannels;
|
||||
auto audioData = MakeUnique<AudioDataValue[]>(frames * channels);
|
||||
AlignedAudioBuffer audioData(frames * channels);
|
||||
if (!audioData) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
memcpy(audioData.get(),
|
||||
audio->mAudioData.get() + (framesToPrune.value() * channels),
|
||||
frames * channels * sizeof(AudioDataValue));
|
||||
|
|
|
@ -142,7 +142,7 @@ public:
|
|||
MediaDecoderReader* aReader,
|
||||
bool aRealTime = false);
|
||||
|
||||
nsresult Init();
|
||||
nsresult Init(MediaDecoder* aDecoder);
|
||||
|
||||
// Enumeration for the valid decoding states
|
||||
enum State {
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 "MediaInfo.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
typedef AudioConfig::ChannelLayout ChannelLayout;
|
||||
|
||||
/**
|
||||
* AudioConfig::ChannelLayout
|
||||
*/
|
||||
|
||||
/*
|
||||
SMPTE channel layout (also known as wave order)
|
||||
DUAL-MONO L R
|
||||
DUAL-MONO-LFE L R LFE
|
||||
MONO M
|
||||
MONO-LFE M LFE
|
||||
STEREO L R
|
||||
STEREO-LFE L R LFE
|
||||
3F L R C
|
||||
3F-LFE L R C LFE
|
||||
2F1 L R S
|
||||
2F1-LFE L R LFE S
|
||||
3F1 L R C S
|
||||
3F1-LFE L R C LFE S
|
||||
2F2 L R LS RS
|
||||
2F2-LFE L R LFE LS RS
|
||||
3F2 L R C LS RS
|
||||
3F2-LFE L R C LFE LS RS
|
||||
3F3R-LFE L R C LFE BC LS RS
|
||||
3F4-LFE L R C LFE Rls Rrs LS RS
|
||||
*/
|
||||
|
||||
void
|
||||
AudioConfig::ChannelLayout::UpdateChannelMap()
|
||||
{
|
||||
mChannelMap = 0;
|
||||
mValid = mChannels.Length() <= MAX_AUDIO_CHANNELS;
|
||||
for (size_t i = 0; i < mChannels.Length() && i <= MAX_AUDIO_CHANNELS; i++) {
|
||||
uint32_t mask = 1 << mChannels[i];
|
||||
if (mChannels[i] == CHANNEL_INVALID || (mChannelMap & mask)) {
|
||||
mValid = false;
|
||||
}
|
||||
mChannelMap |= mask;
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ const AudioConfig::Channel*
|
||||
AudioConfig::ChannelLayout::SMPTEDefault(uint32_t aChannels) const
|
||||
{
|
||||
switch (aChannels) {
|
||||
case 1: // MONO
|
||||
{
|
||||
static const Channel config[] = { CHANNEL_MONO };
|
||||
return config;
|
||||
}
|
||||
case 2: // STEREO
|
||||
{
|
||||
static const Channel config[] = { CHANNEL_LEFT, CHANNEL_RIGHT };
|
||||
return config;
|
||||
}
|
||||
case 3: // 3F
|
||||
{
|
||||
static const Channel config[] = { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER };
|
||||
return config;
|
||||
}
|
||||
case 4: // 2F2
|
||||
{
|
||||
static const Channel config[] = { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LS, CHANNEL_RS };
|
||||
return config;
|
||||
}
|
||||
case 5: // 3F2
|
||||
{
|
||||
static const Channel config[] = { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LS, CHANNEL_RS };
|
||||
return config;
|
||||
}
|
||||
case 6: // 3F2-LFE
|
||||
{
|
||||
static const Channel config[] = { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_LS, CHANNEL_RS };
|
||||
return config;
|
||||
}
|
||||
case 7: // 3F3R-LFE
|
||||
{
|
||||
static const Channel config[] = { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RCENTER, CHANNEL_LS, CHANNEL_RS };
|
||||
return config;
|
||||
}
|
||||
case 8: // 3F4-LFE
|
||||
{
|
||||
static const Channel config[] = { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RLS, CHANNEL_RRS, CHANNEL_LS, CHANNEL_RS };
|
||||
return config;
|
||||
}
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
AudioConfig::ChannelLayout::MappingTable(const ChannelLayout& aOther,
|
||||
uint8_t* aMap) const
|
||||
{
|
||||
if (!IsValid() || !aOther.IsValid() ||
|
||||
Map() != aOther.Map()) {
|
||||
return false;
|
||||
}
|
||||
if (!aMap) {
|
||||
return true;
|
||||
}
|
||||
for (uint32_t i = 0; i < Count(); i++) {
|
||||
for (uint32_t j = 0; j < Count(); j++) {
|
||||
if (aOther[j] == mChannels[i]) {
|
||||
aMap[j] = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioConfig::ChannelConfig
|
||||
*/
|
||||
|
||||
/* static */ const char*
|
||||
AudioConfig::FormatToString(AudioConfig::SampleFormat aFormat)
|
||||
{
|
||||
switch (aFormat) {
|
||||
case FORMAT_U8: return "unsigned 8 bit";
|
||||
case FORMAT_S16: return "signed 16 bit";
|
||||
case FORMAT_S24: return "signed 24 bit MSB";
|
||||
case FORMAT_S24LSB: return "signed 24 bit LSB";
|
||||
case FORMAT_S32: return "signed 32 bit";
|
||||
case FORMAT_FLT: return "32 bit floating point";
|
||||
case FORMAT_NONE: return "none";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
/* static */ uint32_t
|
||||
AudioConfig::SampleSize(AudioConfig::SampleFormat aFormat)
|
||||
{
|
||||
switch (aFormat) {
|
||||
case FORMAT_U8: return 1;
|
||||
case FORMAT_S16: return 2;
|
||||
case FORMAT_S24: MOZ_FALLTHROUGH;
|
||||
case FORMAT_S24LSB: MOZ_FALLTHROUGH;
|
||||
case FORMAT_S32: MOZ_FALLTHROUGH;
|
||||
case FORMAT_FLT: return 4;
|
||||
case FORMAT_NONE:
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ uint32_t
|
||||
AudioConfig::FormatToBits(AudioConfig::SampleFormat aFormat)
|
||||
{
|
||||
switch (aFormat) {
|
||||
case FORMAT_U8: return 8;
|
||||
case FORMAT_S16: return 16;
|
||||
case FORMAT_S24LSB: MOZ_FALLTHROUGH;
|
||||
case FORMAT_S24: return 24;
|
||||
case FORMAT_S32: MOZ_FALLTHROUGH;
|
||||
case FORMAT_FLT: return 32;
|
||||
case FORMAT_NONE: MOZ_FALLTHROUGH;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
AudioConfig::AudioConfig(const ChannelLayout& aChannelLayout, uint32_t aRate,
|
||||
AudioConfig::SampleFormat aFormat, bool aInterleaved)
|
||||
: mChannelLayout(aChannelLayout)
|
||||
, mChannels(aChannelLayout.Count())
|
||||
, mRate(aRate)
|
||||
, mFormat(aFormat)
|
||||
, mInterleaved(aInterleaved)
|
||||
{}
|
||||
|
||||
AudioConfig::AudioConfig(uint32_t aChannels, uint32_t aRate,
|
||||
AudioConfig::SampleFormat aFormat, bool aInterleaved)
|
||||
: mChannelLayout(aChannels)
|
||||
, mChannels(aChannels)
|
||||
, mRate(aRate)
|
||||
, mFormat(aFormat)
|
||||
, mInterleaved(aInterleaved)
|
||||
{}
|
||||
|
||||
} // namespace mozilla
|
|
@ -474,6 +474,153 @@ public:
|
|||
const nsCString& mMimeType;
|
||||
};
|
||||
|
||||
// Maximum channel number we can currently handle (7.1)
|
||||
#define MAX_AUDIO_CHANNELS 8
|
||||
|
||||
class AudioConfig {
|
||||
public:
|
||||
enum Channel {
|
||||
CHANNEL_INVALID = -1,
|
||||
CHANNEL_MONO = 0,
|
||||
CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_CENTER,
|
||||
CHANNEL_LS,
|
||||
CHANNEL_RS,
|
||||
CHANNEL_RLS,
|
||||
CHANNEL_RCENTER,
|
||||
CHANNEL_RRS,
|
||||
CHANNEL_LFE,
|
||||
};
|
||||
|
||||
class ChannelLayout {
|
||||
public:
|
||||
ChannelLayout()
|
||||
: mChannelMap(0)
|
||||
, mValid(false)
|
||||
{}
|
||||
explicit ChannelLayout(uint32_t aChannels)
|
||||
: ChannelLayout(aChannels, SMPTEDefault(aChannels))
|
||||
{}
|
||||
ChannelLayout(uint32_t aChannels, const Channel* aConfig)
|
||||
{
|
||||
mChannels.AppendElements(aConfig, aChannels);
|
||||
UpdateChannelMap();
|
||||
}
|
||||
bool operator==(const ChannelLayout& aOther) const
|
||||
{
|
||||
return mChannels == aOther.mChannels;
|
||||
}
|
||||
bool operator!=(const ChannelLayout& aOther) const
|
||||
{
|
||||
return mChannels != aOther.mChannels;
|
||||
}
|
||||
const Channel& operator[](uint32_t aIndex) const
|
||||
{
|
||||
return mChannels[aIndex];
|
||||
}
|
||||
uint32_t Count() const
|
||||
{
|
||||
return mChannels.Length();
|
||||
}
|
||||
uint32_t Map() const
|
||||
{
|
||||
return mChannelMap;
|
||||
}
|
||||
// Calculate the mapping table from the current layout to aOther such that
|
||||
// one can easily go from one layout to the other by doing:
|
||||
// out[channel] = in[map[channel]].
|
||||
// Returns true if the reordering is possible or false otherwise.
|
||||
// If true, then aMap, if set, will be updated to contain the mapping table
|
||||
// allowing conversion from the current layout to aOther.
|
||||
// If aMap is nullptr, then MappingTable can be used to simply determine if
|
||||
// the current layout can be easily reordered to aOther.
|
||||
// aMap must be an array of size MAX_AUDIO_CHANNELS.
|
||||
bool MappingTable(const ChannelLayout& aOther, uint8_t* aMap = nullptr) const;
|
||||
bool IsValid() const {
|
||||
return mValid;
|
||||
}
|
||||
bool HasChannel(Channel aChannel) const
|
||||
{
|
||||
return mChannelMap & (1 << aChannel);
|
||||
}
|
||||
private:
|
||||
void UpdateChannelMap();
|
||||
const Channel* SMPTEDefault(uint32_t aChannels) const;
|
||||
AutoTArray<Channel, MAX_AUDIO_CHANNELS> mChannels;
|
||||
uint32_t mChannelMap;
|
||||
bool mValid;
|
||||
};
|
||||
|
||||
enum SampleFormat {
|
||||
FORMAT_NONE = 0,
|
||||
FORMAT_U8,
|
||||
FORMAT_S16,
|
||||
FORMAT_S24LSB,
|
||||
FORMAT_S24,
|
||||
FORMAT_S32,
|
||||
FORMAT_FLT,
|
||||
#if defined(MOZ_SAMPLE_TYPE_FLOAT32)
|
||||
FORMAT_DEFAULT = FORMAT_FLT
|
||||
#elif defined(MOZ_SAMPLE_TYPE_S16)
|
||||
FORMAT_DEFAULT = FORMAT_S16
|
||||
#else
|
||||
#error "Not supported audio type"
|
||||
#endif
|
||||
};
|
||||
|
||||
AudioConfig(const ChannelLayout& aChannelLayout, uint32_t aRate,
|
||||
AudioConfig::SampleFormat aFormat = FORMAT_DEFAULT,
|
||||
bool aInterleaved = true);
|
||||
// Will create a channel configuration from default SMPTE ordering.
|
||||
AudioConfig(uint32_t aChannels, uint32_t aRate,
|
||||
AudioConfig::SampleFormat aFormat = FORMAT_DEFAULT,
|
||||
bool aInterleaved = true);
|
||||
|
||||
const ChannelLayout& Layout() const
|
||||
{
|
||||
return mChannelLayout;
|
||||
}
|
||||
uint32_t Channels() const
|
||||
{
|
||||
if (!mChannelLayout.IsValid()) {
|
||||
return mChannels;
|
||||
}
|
||||
return mChannelLayout.Count();
|
||||
}
|
||||
uint32_t Rate() const
|
||||
{
|
||||
return mRate;
|
||||
}
|
||||
SampleFormat Format() const
|
||||
{
|
||||
return mFormat;
|
||||
}
|
||||
bool Interleaved() const
|
||||
{
|
||||
return mInterleaved;
|
||||
}
|
||||
|
||||
static const char* FormatToString(SampleFormat aFormat);
|
||||
static uint32_t SampleSize(SampleFormat aFormat);
|
||||
static uint32_t FormatToBits(SampleFormat aFormat);
|
||||
|
||||
private:
|
||||
// Channels configuration.
|
||||
ChannelLayout mChannelLayout;
|
||||
|
||||
// Channel count.
|
||||
uint32_t mChannels;
|
||||
|
||||
// Sample rate.
|
||||
uint32_t mRate;
|
||||
|
||||
// Sample format.
|
||||
SampleFormat mFormat;
|
||||
|
||||
bool mInterleaved;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // MediaInfo_h
|
||||
|
|
|
@ -140,60 +140,6 @@ media::TimeIntervals GetEstimatedBufferedTimeRanges(mozilla::MediaResource* aStr
|
|||
return buffered;
|
||||
}
|
||||
|
||||
int DownmixAudioToStereo(mozilla::AudioDataValue* buffer,
|
||||
int channels, uint32_t frames)
|
||||
{
|
||||
int outChannels;
|
||||
outChannels = 2;
|
||||
#ifdef MOZ_SAMPLE_TYPE_FLOAT32
|
||||
// Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows 5-8.
|
||||
static const float dmatrix[6][8][2]= {
|
||||
/*3*/{{0.5858f,0},{0.4142f,0.4142f},{0, 0.5858f}},
|
||||
/*4*/{{0.4226f,0},{0, 0.4226f},{0.366f,0.2114f},{0.2114f,0.366f}},
|
||||
/*5*/{{0.6510f,0},{0.4600f,0.4600f},{0, 0.6510f},{0.5636f,0.3254f},{0.3254f,0.5636f}},
|
||||
/*6*/{{0.5290f,0},{0.3741f,0.3741f},{0, 0.5290f},{0.4582f,0.2645f},{0.2645f,0.4582f},{0.3741f,0.3741f}},
|
||||
/*7*/{{0.4553f,0},{0.3220f,0.3220f},{0, 0.4553f},{0.3943f,0.2277f},{0.2277f,0.3943f},{0.2788f,0.2788f},{0.3220f,0.3220f}},
|
||||
/*8*/{{0.3886f,0},{0.2748f,0.2748f},{0, 0.3886f},{0.3366f,0.1943f},{0.1943f,0.3366f},{0.3366f,0.1943f},{0.1943f,0.3366f},{0.2748f,0.2748f}},
|
||||
};
|
||||
// Re-write the buffer with downmixed data
|
||||
for (uint32_t i = 0; i < frames; i++) {
|
||||
float sampL = 0.0;
|
||||
float sampR = 0.0;
|
||||
for (int j = 0; j < channels; j++) {
|
||||
sampL+=buffer[i*channels+j]*dmatrix[channels-3][j][0];
|
||||
sampR+=buffer[i*channels+j]*dmatrix[channels-3][j][1];
|
||||
}
|
||||
buffer[i*outChannels]=sampL;
|
||||
buffer[i*outChannels+1]=sampR;
|
||||
}
|
||||
#else
|
||||
// Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows 5-8.
|
||||
// Coefficients in Q14.
|
||||
static const int16_t dmatrix[6][8][2]= {
|
||||
/*3*/{{9598, 0},{6786,6786},{0, 9598}},
|
||||
/*4*/{{6925, 0},{0, 6925},{5997,3462},{3462,5997}},
|
||||
/*5*/{{10663,0},{7540,7540},{0, 10663},{9234,5331},{5331,9234}},
|
||||
/*6*/{{8668, 0},{6129,6129},{0, 8668},{7507,4335},{4335,7507},{6129,6129}},
|
||||
/*7*/{{7459, 0},{5275,5275},{0, 7459},{6460,3731},{3731,6460},{4568,4568},{5275,5275}},
|
||||
/*8*/{{6368, 0},{4502,4502},{0, 6368},{5514,3184},{3184,5514},{5514,3184},{3184,5514},{4502,4502}}
|
||||
};
|
||||
// Re-write the buffer with downmixed data
|
||||
for (uint32_t i = 0; i < frames; i++) {
|
||||
int32_t sampL = 0;
|
||||
int32_t sampR = 0;
|
||||
for (int j = 0; j < channels; j++) {
|
||||
sampL+=buffer[i*channels+j]*dmatrix[channels-3][j][0];
|
||||
sampR+=buffer[i*channels+j]*dmatrix[channels-3][j][1];
|
||||
}
|
||||
sampL = (sampL + 8192)>>14;
|
||||
buffer[i*outChannels] = static_cast<mozilla::AudioDataValue>(MOZ_CLIP_TO_15(sampL));
|
||||
sampR = (sampR + 8192)>>14;
|
||||
buffer[i*outChannels+1] = static_cast<mozilla::AudioDataValue>(MOZ_CLIP_TO_15(sampR));
|
||||
}
|
||||
#endif
|
||||
return outChannels;
|
||||
}
|
||||
|
||||
void DownmixStereoToMono(mozilla::AudioDataValue* aBuffer,
|
||||
uint32_t aFrames)
|
||||
{
|
||||
|
|
|
@ -163,13 +163,6 @@ static const int32_t MAX_VIDEO_HEIGHT = 4608;
|
|||
// before being used!
|
||||
void ScaleDisplayByAspectRatio(nsIntSize& aDisplay, float aAspectRatio);
|
||||
|
||||
// Downmix multichannel Audio samples to Stereo.
|
||||
// Input are the buffer contains multichannel data,
|
||||
// the number of channels and the number of frames.
|
||||
int DownmixAudioToStereo(mozilla::AudioDataValue* buffer,
|
||||
int channels,
|
||||
uint32_t frames);
|
||||
|
||||
// Downmix Stereo audio samples to Mono.
|
||||
// Input are the buffer contains stereo data and the number of frames.
|
||||
void DownmixStereoToMono(mozilla::AudioDataValue* aBuffer,
|
||||
|
|
|
@ -83,6 +83,7 @@ ParseKeySystem(const nsAString& aExpectedKeySystem,
|
|||
static const char16_t* sKeySystems[] = {
|
||||
MOZ_UTF16("org.w3.clearkey"),
|
||||
MOZ_UTF16("com.adobe.primetime"),
|
||||
MOZ_UTF16("com.widevine.alpha"),
|
||||
};
|
||||
|
||||
bool
|
||||
|
@ -140,6 +141,9 @@ KeySystemToGMPName(const nsAString& aKeySystem)
|
|||
if (aKeySystem.EqualsLiteral("org.w3.clearkey")) {
|
||||
return NS_LITERAL_STRING("gmp-clearkey");
|
||||
}
|
||||
if (aKeySystem.EqualsLiteral("com.widevine.alpha")) {
|
||||
return NS_LITERAL_STRING("gmp-widevinecdm");
|
||||
}
|
||||
MOZ_ASSERT(false, "We should only call this for known GMPs");
|
||||
return EmptyString();
|
||||
}
|
||||
|
|
|
@ -33,9 +33,6 @@
|
|||
#include "nsXULAppAPI.h"
|
||||
#include "gmp-audio-decode.h"
|
||||
#include "gmp-video-decode.h"
|
||||
#ifdef XP_WIN
|
||||
#include "WMFDecoderModule.h"
|
||||
#endif
|
||||
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX)
|
||||
#define PRIMETIME_EME_SUPPORTED 1
|
||||
|
@ -306,6 +303,23 @@ MediaKeySystemAccess::GetKeySystemStatus(const nsAString& aKeySystem,
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDEVINE_EME
|
||||
if (aKeySystem.EqualsLiteral("com.widevine.alpha")) {
|
||||
#ifdef XP_WIN
|
||||
// Win Vista and later only.
|
||||
if (!IsVistaOrLater()) {
|
||||
aOutMessage = NS_LITERAL_CSTRING("Minimum Windows version not met for Widevine EME");
|
||||
return MediaKeySystemStatus::Cdm_not_supported;
|
||||
}
|
||||
#endif
|
||||
if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) {
|
||||
aOutMessage = NS_LITERAL_CSTRING("Widevine EME disabled");
|
||||
return MediaKeySystemStatus::Cdm_disabled;
|
||||
}
|
||||
return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion);
|
||||
}
|
||||
#endif
|
||||
|
||||
return MediaKeySystemStatus::Cdm_not_supported;
|
||||
}
|
||||
|
||||
|
@ -319,16 +333,7 @@ GMPDecryptsAndDecodesAAC(mozIGeckoMediaPluginService* aGMPS,
|
|||
return HaveGMPFor(aGMPS,
|
||||
NS_ConvertUTF16toUTF8(aKeySystem),
|
||||
NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
|
||||
NS_LITERAL_CSTRING("aac"))
|
||||
#ifdef XP_WIN
|
||||
// Clearkey on Windows advertises that it can decode in its GMP info
|
||||
// file, but uses Windows Media Foundation to decode. That's not present
|
||||
// on Windows XP, and on some Vista, Windows N, and KN variants without
|
||||
// certain services packs. So for ClearKey we must check that WMF will
|
||||
// work.
|
||||
&& (!aKeySystem.EqualsLiteral("org.w3.clearkey") || WMFDecoderModule::HasAAC())
|
||||
#endif
|
||||
;
|
||||
NS_LITERAL_CSTRING("aac"));
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -341,23 +346,12 @@ GMPDecryptsAndDecodesH264(mozIGeckoMediaPluginService* aGMPS,
|
|||
return HaveGMPFor(aGMPS,
|
||||
NS_ConvertUTF16toUTF8(aKeySystem),
|
||||
NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
|
||||
NS_LITERAL_CSTRING("h264"))
|
||||
#ifdef XP_WIN
|
||||
// Clearkey on Windows advertises that it can decode in its GMP info
|
||||
// file, but uses Windows Media Foundation to decode. That's not present
|
||||
// on Windows XP, and on some Vista, Windows N, and KN variants without
|
||||
// certain services packs. So for ClearKey we must check that WMF will
|
||||
// work.
|
||||
&& (!aKeySystem.EqualsLiteral("org.w3.clearkey") || WMFDecoderModule::HasH264())
|
||||
#endif
|
||||
;
|
||||
NS_LITERAL_CSTRING("h264"));
|
||||
}
|
||||
|
||||
// If this keysystem's CDM explicitly says it doesn't support decoding,
|
||||
// that means it's OK with passing the decrypted samples back to Gecko
|
||||
// for decoding. Note we special case Clearkey on Windows, where we need
|
||||
// to check for whether WMF is usable because the CDM uses that
|
||||
// to decode.
|
||||
// for decoding.
|
||||
static bool
|
||||
GMPDecryptsAndGeckoDecodesH264(mozIGeckoMediaPluginService* aGMPService,
|
||||
const nsAString& aKeySystem,
|
||||
|
@ -367,20 +361,11 @@ GMPDecryptsAndGeckoDecodesH264(mozIGeckoMediaPluginService* aGMPService,
|
|||
NS_ConvertUTF16toUTF8(aKeySystem),
|
||||
NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
|
||||
MOZ_ASSERT(IsH264ContentType(aContentType));
|
||||
return
|
||||
(!HaveGMPFor(aGMPService,
|
||||
NS_ConvertUTF16toUTF8(aKeySystem),
|
||||
NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
|
||||
NS_LITERAL_CSTRING("h264"))
|
||||
#ifdef XP_WIN
|
||||
// Clearkey on Windows advertises that it can decode in its GMP info
|
||||
// file, but uses Windows Media Foundation to decode. That's not present
|
||||
// on Windows XP, and on some Vista, Windows N, and KN variants without
|
||||
// certain services packs. So don't try to use gmp-clearkey for decoding
|
||||
// if we don't have a decoder here.
|
||||
|| (aKeySystem.EqualsLiteral("org.w3.clearkey") && !WMFDecoderModule::HasH264())
|
||||
#endif
|
||||
) && MP4Decoder::CanHandleMediaType(aContentType);
|
||||
return !HaveGMPFor(aGMPService,
|
||||
NS_ConvertUTF16toUTF8(aKeySystem),
|
||||
NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
|
||||
NS_LITERAL_CSTRING("h264")) &&
|
||||
MP4Decoder::CanHandleMediaType(aContentType);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -392,20 +377,20 @@ GMPDecryptsAndGeckoDecodesAAC(mozIGeckoMediaPluginService* aGMPService,
|
|||
NS_ConvertUTF16toUTF8(aKeySystem),
|
||||
NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
|
||||
MOZ_ASSERT(IsAACContentType(aContentType));
|
||||
return
|
||||
(!HaveGMPFor(aGMPService,
|
||||
NS_ConvertUTF16toUTF8(aKeySystem),
|
||||
NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
|
||||
NS_LITERAL_CSTRING("aac"))
|
||||
#ifdef XP_WIN
|
||||
// Clearkey on Windows advertises that it can decode in its GMP info
|
||||
// file, but uses Windows Media Foundation to decode. That's not present
|
||||
// on Windows XP, and on some Vista, Windows N, and KN variants without
|
||||
// certain services packs. So don't try to use gmp-clearkey for decoding
|
||||
// if we don't have a decoder here.
|
||||
|| (aKeySystem.EqualsLiteral("org.w3.clearkey") && !WMFDecoderModule::HasAAC())
|
||||
|
||||
return !HaveGMPFor(aGMPService,
|
||||
NS_ConvertUTF16toUTF8(aKeySystem),
|
||||
NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
|
||||
NS_LITERAL_CSTRING("aac")) &&
|
||||
#if defined(MOZ_WIDEVINE_EME) && defined(XP_WIN)
|
||||
// Widevine CDM doesn't include an AAC decoder. So if WMF can't
|
||||
// decode AAC, and a codec wasn't specified, be conservative
|
||||
// and reject the MediaKeys request, since our policy is to prevent
|
||||
// the Adobe GMP's unencrypted AAC decoding path being used to
|
||||
// decode content decrypted by the Widevine CDM.
|
||||
(!aKeySystem.EqualsLiteral("com.widevine.alpha") || WMFDecoderModule::HasAAC()) &&
|
||||
#endif
|
||||
) && MP4Decoder::CanHandleMediaType(aContentType);
|
||||
MP4Decoder::CanHandleMediaType(aContentType);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -457,6 +442,20 @@ IsSupported(mozIGeckoMediaPluginService* aGMPService,
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
IsSupportedInitDataType(const nsString& aCandidate, const nsAString& aKeySystem)
|
||||
{
|
||||
// All supported keySystems can handle "cenc" initDataType.
|
||||
// ClearKey also supports "keyids" and "webm" initDataTypes.
|
||||
return aCandidate.EqualsLiteral("cenc") ||
|
||||
((aKeySystem.EqualsLiteral("org.w3.clearkey")
|
||||
#ifdef MOZ_WIDEVINE_EME
|
||||
|| aKeySystem.EqualsLiteral("com.widevine.alpha")
|
||||
#endif
|
||||
) &&
|
||||
(aCandidate.EqualsLiteral("keyids") || aCandidate.EqualsLiteral("webm)")));
|
||||
}
|
||||
|
||||
static bool
|
||||
GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
|
||||
const nsAString& aKeySystem,
|
||||
|
@ -468,13 +467,7 @@ GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
|
|||
if (aCandidate.mInitDataTypes.WasPassed()) {
|
||||
nsTArray<nsString> initDataTypes;
|
||||
for (const nsString& candidate : aCandidate.mInitDataTypes.Value()) {
|
||||
// All supported keySystems can handle "cenc" initDataType.
|
||||
// ClearKey also supports "keyids" and "webm" initDataTypes.
|
||||
if (candidate.EqualsLiteral("cenc")) {
|
||||
initDataTypes.AppendElement(candidate);
|
||||
} else if ((candidate.EqualsLiteral("keyids") ||
|
||||
candidate.EqualsLiteral("webm)")) &&
|
||||
aKeySystem.EqualsLiteral("org.w3.clearkey")) {
|
||||
if (IsSupportedInitDataType(candidate, aKeySystem)) {
|
||||
initDataTypes.AppendElement(candidate);
|
||||
}
|
||||
}
|
||||
|
@ -511,6 +504,17 @@ GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
|
|||
config.mVideoCapabilities.Value().Assign(caps);
|
||||
}
|
||||
|
||||
#if defined(MOZ_WIDEVINE_EME) && defined(XP_WIN)
|
||||
// Widevine CDM doesn't include an AAC decoder. So if WMF can't decode AAC,
|
||||
// and a codec wasn't specified, be conservative and reject the MediaKeys request.
|
||||
if (aKeySystem.EqualsLiteral("com.widevine.alpha") &&
|
||||
(!aCandidate.mAudioCapabilities.WasPassed() ||
|
||||
!aCandidate.mVideoCapabilities.WasPassed()) &&
|
||||
!WMFDecoderModule::HasAAC()) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
aOutConfig = config;
|
||||
|
||||
return true;
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
# 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/.
|
||||
|
||||
for cdm in CONFIG['MOZ_EME_MODULES']:
|
||||
DEFINES['MOZ_%s_EME' % cdm.upper()] = True
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'MediaEncryptedEvent.h',
|
||||
'MediaKeyError.h',
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
#include "mozilla/dom/CrashReporterChild.h"
|
||||
#include "GMPUtils.h"
|
||||
#include "prio.h"
|
||||
#ifdef MOZ_WIDEVINE_EME
|
||||
#include "widevine-adapter/WidevineAdapter.h"
|
||||
#endif
|
||||
|
||||
using mozilla::dom::CrashReporterChild;
|
||||
|
||||
|
@ -345,7 +348,7 @@ GMPChild::GetUTF8LibPath(nsACString& aOutLibPath)
|
|||
}
|
||||
|
||||
bool
|
||||
GMPChild::AnswerStartPlugin()
|
||||
GMPChild::AnswerStartPlugin(const nsString& aAdapter)
|
||||
{
|
||||
LOGD("%s", __FUNCTION__);
|
||||
|
||||
|
@ -378,11 +381,18 @@ GMPChild::AnswerStartPlugin()
|
|||
}
|
||||
#endif
|
||||
|
||||
GMPAdapter* adapter = nullptr;
|
||||
#ifdef MOZ_WIDEVINE_EME
|
||||
if (aAdapter.EqualsLiteral("widevine")) {
|
||||
adapter = new WidevineAdapter();
|
||||
}
|
||||
#endif
|
||||
if (!mGMPLoader->Load(libPath.get(),
|
||||
libPath.Length(),
|
||||
mNodeId.BeginWriting(),
|
||||
mNodeId.Length(),
|
||||
platformAPI)) {
|
||||
platformAPI,
|
||||
adapter)) {
|
||||
NS_WARNING("Failed to load GMP");
|
||||
delete platformAPI;
|
||||
return false;
|
||||
|
|
|
@ -53,7 +53,7 @@ private:
|
|||
bool GetUTF8LibPath(nsACString& aOutLibPath);
|
||||
|
||||
bool RecvSetNodeId(const nsCString& aNodeId) override;
|
||||
bool AnswerStartPlugin() override;
|
||||
bool AnswerStartPlugin(const nsString& aAdapter) override;
|
||||
bool RecvPreloadLibs(const nsCString& aLibs) override;
|
||||
|
||||
PCrashReporterChild* AllocPCrashReporterChild(const NativeThreadId& aThread) override;
|
||||
|
|
|
@ -169,15 +169,20 @@ GMPDecryptorParent::Decrypt(uint32_t aId,
|
|||
}
|
||||
|
||||
// Caller should ensure parameters passed in are valid.
|
||||
MOZ_ASSERT(!aBuffer.IsEmpty() && aCrypto.mValid);
|
||||
MOZ_ASSERT(!aBuffer.IsEmpty());
|
||||
|
||||
GMPDecryptionData data(aCrypto.mKeyId,
|
||||
aCrypto.mIV,
|
||||
aCrypto.mPlainSizes,
|
||||
aCrypto.mEncryptedSizes,
|
||||
aCrypto.mSessionIds);
|
||||
if (aCrypto.mValid) {
|
||||
GMPDecryptionData data(aCrypto.mKeyId,
|
||||
aCrypto.mIV,
|
||||
aCrypto.mPlainSizes,
|
||||
aCrypto.mEncryptedSizes,
|
||||
aCrypto.mSessionIds);
|
||||
|
||||
Unused << SendDecrypt(aId, aBuffer, data);
|
||||
Unused << SendDecrypt(aId, aBuffer, data);
|
||||
} else {
|
||||
GMPDecryptionData data;
|
||||
Unused << SendDecrypt(aId, aBuffer, data);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -53,6 +53,7 @@ class GMPLoaderImpl : public GMPLoader {
|
|||
public:
|
||||
explicit GMPLoaderImpl(SandboxStarter* aStarter)
|
||||
: mSandboxStarter(aStarter)
|
||||
, mAdapter(nullptr)
|
||||
{}
|
||||
virtual ~GMPLoaderImpl() {}
|
||||
|
||||
|
@ -60,7 +61,8 @@ public:
|
|||
uint32_t aUTF8LibPathLen,
|
||||
char* aOriginSalt,
|
||||
uint32_t aOriginSaltLen,
|
||||
const GMPPlatformAPI* aPlatformAPI) override;
|
||||
const GMPPlatformAPI* aPlatformAPI,
|
||||
GMPAdapter* aAdapter) override;
|
||||
|
||||
GMPErr GetAPI(const char* aAPIName,
|
||||
void* aHostAPI,
|
||||
|
@ -73,15 +75,77 @@ public:
|
|||
#endif
|
||||
|
||||
private:
|
||||
PRLibrary* mLib;
|
||||
GMPGetAPIFunc mGetAPIFunc;
|
||||
SandboxStarter* mSandboxStarter;
|
||||
UniquePtr<GMPAdapter> mAdapter;
|
||||
};
|
||||
|
||||
GMPLoader* CreateGMPLoader(SandboxStarter* aStarter) {
|
||||
return static_cast<GMPLoader*>(new GMPLoaderImpl(aStarter));
|
||||
}
|
||||
|
||||
class PassThroughGMPAdapter : public GMPAdapter {
|
||||
public:
|
||||
~PassThroughGMPAdapter() {
|
||||
// Ensure we're always shutdown, even if caller forgets to call GMPShutdown().
|
||||
GMPShutdown();
|
||||
}
|
||||
|
||||
void SetAdaptee(PRLibrary* aLib) override
|
||||
{
|
||||
mLib = aLib;
|
||||
}
|
||||
|
||||
GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) override
|
||||
{
|
||||
if (!mLib) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(mLib, "GMPInit"));
|
||||
if (!initFunc) {
|
||||
return GMPNotImplementedErr;
|
||||
}
|
||||
return initFunc(aPlatformAPI);
|
||||
}
|
||||
|
||||
GMPErr GMPGetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI) override
|
||||
{
|
||||
if (!mLib) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
GMPGetAPIFunc getapiFunc = reinterpret_cast<GMPGetAPIFunc>(PR_FindFunctionSymbol(mLib, "GMPGetAPI"));
|
||||
if (!getapiFunc) {
|
||||
return GMPNotImplementedErr;
|
||||
}
|
||||
return getapiFunc(aAPIName, aHostAPI, aPluginAPI);
|
||||
}
|
||||
|
||||
void GMPShutdown() override
|
||||
{
|
||||
if (mLib) {
|
||||
GMPShutdownFunc shutdownFunc = reinterpret_cast<GMPShutdownFunc>(PR_FindFunctionSymbol(mLib, "GMPShutdown"));
|
||||
if (shutdownFunc) {
|
||||
shutdownFunc();
|
||||
}
|
||||
PR_UnloadLibrary(mLib);
|
||||
mLib = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void GMPSetNodeId(const char* aNodeId, uint32_t aLength) override
|
||||
{
|
||||
if (!mLib) {
|
||||
return;
|
||||
}
|
||||
GMPSetNodeIdFunc setNodeIdFunc = reinterpret_cast<GMPSetNodeIdFunc>(PR_FindFunctionSymbol(mLib, "GMPSetNodeId"));
|
||||
if (setNodeIdFunc) {
|
||||
setNodeIdFunc(aNodeId, aLength);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
PRLibrary* mLib = nullptr;
|
||||
};
|
||||
|
||||
#if defined(XP_WIN) && defined(HASH_NODE_ID_WITH_DEVICE_ID)
|
||||
MOZ_NEVER_INLINE
|
||||
static bool
|
||||
|
@ -175,7 +239,8 @@ GMPLoaderImpl::Load(const char* aUTF8LibPath,
|
|||
uint32_t aUTF8LibPathLen,
|
||||
char* aOriginSalt,
|
||||
uint32_t aOriginSaltLen,
|
||||
const GMPPlatformAPI* aPlatformAPI)
|
||||
const GMPPlatformAPI* aPlatformAPI,
|
||||
GMPAdapter* aAdapter)
|
||||
{
|
||||
std::string nodeId;
|
||||
#ifdef HASH_NODE_ID_WITH_DEVICE_ID
|
||||
|
@ -259,29 +324,27 @@ GMPLoaderImpl::Load(const char* aUTF8LibPath,
|
|||
libSpec.value.pathname = aUTF8LibPath;
|
||||
libSpec.type = PR_LibSpec_Pathname;
|
||||
#endif
|
||||
mLib = PR_LoadLibraryWithFlags(libSpec, 0);
|
||||
if (!mLib) {
|
||||
PRLibrary* lib = PR_LoadLibraryWithFlags(libSpec, 0);
|
||||
if (!lib) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(mLib, "GMPInit"));
|
||||
if (!initFunc) {
|
||||
GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(lib, "GMPInit"));
|
||||
if ((initFunc && aAdapter) ||
|
||||
(!initFunc && !aAdapter)) {
|
||||
// Ensure that if we're dealing with a GMP we do *not* use an adapter
|
||||
// provided from the outside world. This is important as it means we
|
||||
// don't call code not covered by Adobe's plugin-container voucher
|
||||
// before we pass the node Id to Adobe's GMP.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (initFunc(aPlatformAPI) != GMPNoErr) {
|
||||
return false;
|
||||
}
|
||||
// Note: PassThroughGMPAdapter's code must remain in this file so that it's
|
||||
// covered by Adobe's plugin-container voucher.
|
||||
mAdapter.reset((!aAdapter) ? new PassThroughGMPAdapter() : aAdapter);
|
||||
mAdapter->SetAdaptee(lib);
|
||||
|
||||
GMPSetNodeIdFunc setNodeIdFunc = reinterpret_cast<GMPSetNodeIdFunc>(PR_FindFunctionSymbol(mLib, "GMPSetNodeId"));
|
||||
if (setNodeIdFunc) {
|
||||
setNodeIdFunc(nodeId.c_str(), nodeId.size());
|
||||
}
|
||||
|
||||
mGetAPIFunc = reinterpret_cast<GMPGetAPIFunc>(PR_FindFunctionSymbol(mLib, "GMPGetAPI"));
|
||||
if (!mGetAPIFunc) {
|
||||
return false;
|
||||
}
|
||||
mAdapter->GMPInit(aPlatformAPI);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -291,20 +354,14 @@ GMPLoaderImpl::GetAPI(const char* aAPIName,
|
|||
void* aHostAPI,
|
||||
void** aPluginAPI)
|
||||
{
|
||||
return mGetAPIFunc ? mGetAPIFunc(aAPIName, aHostAPI, aPluginAPI)
|
||||
: GMPGenericErr;
|
||||
return mAdapter->GMPGetAPI(aAPIName, aHostAPI, aPluginAPI);
|
||||
}
|
||||
|
||||
void
|
||||
GMPLoaderImpl::Shutdown()
|
||||
{
|
||||
if (mLib) {
|
||||
GMPShutdownFunc shutdownFunc = reinterpret_cast<GMPShutdownFunc>(PR_FindFunctionSymbol(mLib, "GMPShutdown"));
|
||||
if (shutdownFunc) {
|
||||
shutdownFunc();
|
||||
}
|
||||
PR_UnloadLibrary(mLib);
|
||||
mLib = nullptr;
|
||||
if (mAdapter) {
|
||||
mAdapter->GMPShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define GMP_LOADER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "prlink.h"
|
||||
#include "gmp-entrypoints.h"
|
||||
|
||||
#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
|
||||
|
@ -29,6 +30,22 @@ public:
|
|||
#endif
|
||||
};
|
||||
|
||||
// Interface that adapts a plugin to the GMP API.
|
||||
class GMPAdapter {
|
||||
public:
|
||||
virtual ~GMPAdapter() {}
|
||||
// Sets the adapted to plugin library module.
|
||||
// Note: the GMPAdapter is responsible for calling PR_UnloadLibrary on aLib
|
||||
// when it's finished with it.
|
||||
virtual void SetAdaptee(PRLibrary* aLib) = 0;
|
||||
|
||||
// These are called in place of the corresponding GMP API functions.
|
||||
virtual GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) = 0;
|
||||
virtual GMPErr GMPGetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI) = 0;
|
||||
virtual void GMPShutdown() = 0;
|
||||
virtual void GMPSetNodeId(const char* aNodeId, uint32_t aLength) = 0;
|
||||
};
|
||||
|
||||
// Encapsulates generating the device-bound node id, activating the sandbox,
|
||||
// loading the GMP, and passing the node id to the GMP (in that order).
|
||||
//
|
||||
|
@ -45,18 +62,24 @@ public:
|
|||
//
|
||||
// There is exactly one GMPLoader per GMP child process, and only one GMP
|
||||
// per child process (so the GMPLoader only loads one GMP).
|
||||
//
|
||||
// Load() takes an optional GMPAdapter which can be used to adapt non-GMPs
|
||||
// to adhere to the GMP API.
|
||||
class GMPLoader {
|
||||
public:
|
||||
virtual ~GMPLoader() {}
|
||||
|
||||
// Calculates the device-bound node id, then activates the sandbox,
|
||||
// then loads the GMP library and (if applicable) passes the bound node id
|
||||
// to the GMP.
|
||||
// to the GMP. If aAdapter is non-null, the lib path is assumed to be
|
||||
// a non-GMP, and the adapter is initialized with the lib and the adapter
|
||||
// is used to interact with the plugin.
|
||||
virtual bool Load(const char* aUTF8LibPath,
|
||||
uint32_t aLibPathLen,
|
||||
char* aOriginSalt,
|
||||
uint32_t aOriginSaltLen,
|
||||
const GMPPlatformAPI* aPlatformAPI) = 0;
|
||||
const GMPPlatformAPI* aPlatformAPI,
|
||||
GMPAdapter* aAdapter = nullptr) = 0;
|
||||
|
||||
// Retrieves an interface pointer from the GMP.
|
||||
virtual GMPErr GetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI) = 0;
|
||||
|
|
|
@ -35,6 +35,15 @@ using CrashReporter::GetIDFromMinidump;
|
|||
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include "WMFDecoderModule.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDEVINE_EME
|
||||
#include "mozilla/dom/WidevineCDMManifestBinding.h"
|
||||
#include "widevine-adapter/WidevineAdapter.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#undef LOG
|
||||
|
@ -80,10 +89,24 @@ GMPParent::CloneFrom(const GMPParent* aOther)
|
|||
{
|
||||
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
|
||||
MOZ_ASSERT(aOther->mDirectory && aOther->mService, "null plugin directory");
|
||||
return Init(aOther->mService, aOther->mDirectory);
|
||||
|
||||
mService = aOther->mService;
|
||||
mDirectory = aOther->mDirectory;
|
||||
mName = aOther->mName;
|
||||
mVersion = aOther->mVersion;
|
||||
mDescription = aOther->mDescription;
|
||||
mDisplayName = aOther->mDisplayName;
|
||||
#ifdef XP_WIN
|
||||
mLibs = aOther->mLibs;
|
||||
#endif
|
||||
for (const GMPCapability& cap : aOther->mCapabilities) {
|
||||
mCapabilities.AppendElement(cap);
|
||||
}
|
||||
mAdapter = aOther->mAdapter;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
RefPtr<GenericPromise>
|
||||
GMPParent::Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir)
|
||||
{
|
||||
MOZ_ASSERT(aPluginDir);
|
||||
|
@ -98,12 +121,12 @@ GMPParent::Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir)
|
|||
nsCOMPtr<nsIFile> parent;
|
||||
nsresult rv = aPluginDir->GetParent(getter_AddRefs(parent));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
return GenericPromise::CreateAndReject(rv, __func__);
|
||||
}
|
||||
nsAutoString parentLeafName;
|
||||
rv = parent->GetLeafName(parentLeafName);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
return GenericPromise::CreateAndReject(rv, __func__);
|
||||
}
|
||||
LOGD("%s: for %s", __FUNCTION__, NS_LossyConvertUTF16toASCII(parentLeafName).get());
|
||||
|
||||
|
@ -175,7 +198,7 @@ GMPParent::LoadProcess()
|
|||
#endif
|
||||
|
||||
// Intr call to block initialization on plugin load.
|
||||
ok = CallStartPlugin();
|
||||
ok = CallStartPlugin(mAdapter);
|
||||
if (!ok) {
|
||||
LOGD("%s: Failed to send start to child process", __FUNCTION__);
|
||||
return NS_ERROR_FAILURE;
|
||||
|
@ -546,10 +569,10 @@ bool
|
|||
GMPParent::SupportsAPI(const nsCString& aAPI, const nsCString& aTag)
|
||||
{
|
||||
for (uint32_t i = 0; i < mCapabilities.Length(); i++) {
|
||||
if (!mCapabilities[i]->mAPIName.Equals(aAPI)) {
|
||||
if (!mCapabilities[i].mAPIName.Equals(aAPI)) {
|
||||
continue;
|
||||
}
|
||||
nsTArray<nsCString>& tags = mCapabilities[i]->mAPITags;
|
||||
nsTArray<nsCString>& tags = mCapabilities[i].mAPITags;
|
||||
for (uint32_t j = 0; j < tags.Length(); j++) {
|
||||
if (tags[j].Equals(aTag)) {
|
||||
return true;
|
||||
|
@ -752,7 +775,7 @@ ReadInfoField(GMPInfoFileParser& aParser, const nsCString& aKey, nsACString& aOu
|
|||
return true;
|
||||
}
|
||||
|
||||
nsresult
|
||||
RefPtr<GenericPromise>
|
||||
GMPParent::ReadGMPMetaData()
|
||||
{
|
||||
MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
|
||||
|
@ -761,13 +784,34 @@ GMPParent::ReadGMPMetaData()
|
|||
nsCOMPtr<nsIFile> infoFile;
|
||||
nsresult rv = mDirectory->Clone(getter_AddRefs(infoFile));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
return GenericPromise::CreateAndReject(rv, __func__);
|
||||
}
|
||||
infoFile->AppendRelativePath(mName + NS_LITERAL_STRING(".info"));
|
||||
|
||||
if (FileExists(infoFile)) {
|
||||
return ReadGMPInfoFile(infoFile);
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDEVINE_EME
|
||||
// Maybe this is the Widevine adapted plugin?
|
||||
nsCOMPtr<nsIFile> manifestFile;
|
||||
rv = mDirectory->Clone(getter_AddRefs(manifestFile));
|
||||
if (NS_FAILED(rv)) {
|
||||
return GenericPromise::CreateAndReject(rv, __func__);
|
||||
}
|
||||
manifestFile->AppendRelativePath(NS_LITERAL_STRING("manifest.json"));
|
||||
return ReadChromiumManifestFile(manifestFile);
|
||||
#else
|
||||
return GenericPromise::CreateAndReject(rv, __func__);
|
||||
#endif
|
||||
}
|
||||
|
||||
RefPtr<GenericPromise>
|
||||
GMPParent::ReadGMPInfoFile(nsIFile* aFile)
|
||||
{
|
||||
GMPInfoFileParser parser;
|
||||
if (!parser.Init(infoFile)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
if (!parser.Init(aFile)) {
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
nsAutoCString apis;
|
||||
|
@ -775,7 +819,7 @@ GMPParent::ReadGMPMetaData()
|
|||
!ReadInfoField(parser, NS_LITERAL_CSTRING("description"), mDescription) ||
|
||||
!ReadInfoField(parser, NS_LITERAL_CSTRING("version"), mVersion) ||
|
||||
!ReadInfoField(parser, NS_LITERAL_CSTRING("apis"), apis)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
|
@ -793,37 +837,36 @@ GMPParent::ReadGMPMetaData()
|
|||
continue;
|
||||
}
|
||||
|
||||
auto cap = new GMPCapability();
|
||||
GMPCapability cap;
|
||||
if (tagsStart == -1) {
|
||||
// No tags.
|
||||
cap->mAPIName.Assign(api);
|
||||
cap.mAPIName.Assign(api);
|
||||
} else {
|
||||
auto tagsEnd = api.FindChar(']');
|
||||
if (tagsEnd == -1 || tagsEnd < tagsStart) {
|
||||
// Invalid syntax, skip whole capability.
|
||||
delete cap;
|
||||
continue;
|
||||
}
|
||||
|
||||
cap->mAPIName.Assign(Substring(api, 0, tagsStart));
|
||||
cap.mAPIName.Assign(Substring(api, 0, tagsStart));
|
||||
|
||||
if ((tagsEnd - tagsStart) > 1) {
|
||||
const nsDependentCSubstring ts(Substring(api, tagsStart + 1, tagsEnd - tagsStart - 1));
|
||||
nsTArray<nsCString> tagTokens;
|
||||
SplitAt(":", ts, tagTokens);
|
||||
for (nsCString tag : tagTokens) {
|
||||
cap->mAPITags.AppendElement(tag);
|
||||
cap.mAPITags.AppendElement(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We support the current GMPDecryptor version, and the previous.
|
||||
// We Adapt the previous to the current in the GMPContentChild.
|
||||
if (cap->mAPIName.EqualsLiteral(GMP_API_DECRYPTOR_BACKWARDS_COMPAT)) {
|
||||
cap->mAPIName.AssignLiteral(GMP_API_DECRYPTOR);
|
||||
if (cap.mAPIName.EqualsLiteral(GMP_API_DECRYPTOR_BACKWARDS_COMPAT)) {
|
||||
cap.mAPIName.AssignLiteral(GMP_API_DECRYPTOR);
|
||||
}
|
||||
|
||||
if (cap->mAPIName.EqualsLiteral(GMP_API_DECRYPTOR)) {
|
||||
if (cap.mAPIName.EqualsLiteral(GMP_API_DECRYPTOR)) {
|
||||
mCanDecrypt = true;
|
||||
|
||||
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
|
||||
|
@ -831,36 +874,104 @@ GMPParent::ReadGMPMetaData()
|
|||
printf_stderr("GMPParent::ReadGMPMetaData: Plugin \"%s\" is an EME CDM"
|
||||
" but this system can't sandbox it; not loading.\n",
|
||||
mDisplayName.get());
|
||||
delete cap;
|
||||
return NS_ERROR_FAILURE;
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
#endif
|
||||
#ifdef XP_WIN
|
||||
// Adobe GMP doesn't work without SSE2. Check the tags to see if
|
||||
// the decryptor is for the Adobe GMP, and refuse to load it if
|
||||
// SSE2 isn't supported.
|
||||
for (const nsCString& tag : cap->mAPITags) {
|
||||
if (!tag.EqualsLiteral("com.adobe.primetime")) {
|
||||
continue;
|
||||
}
|
||||
if (!mozilla::supports_sse2()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
break;
|
||||
if (cap.mAPITags.Contains(NS_LITERAL_CSTRING("com.adobe.primetime")) &&
|
||||
!mozilla::supports_sse2()) {
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
#endif // XP_WIN
|
||||
}
|
||||
|
||||
mCapabilities.AppendElement(cap);
|
||||
#ifdef XP_WIN
|
||||
// Clearkey on Windows advertises that it can decode in its GMP info
|
||||
// file, but uses Windows Media Foundation to decode. That's not present
|
||||
// on Windows XP, and on some Vista, Windows N, and KN variants without
|
||||
// certain services packs. So don't add the decoding capability to
|
||||
// gmp-clearkey's GMPParent if it's not going to be able to use WMF to
|
||||
// decode.
|
||||
if (cap.mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER) &&
|
||||
cap.mAPITags.Contains(NS_LITERAL_CSTRING("org.w3.clearkey")) &&
|
||||
!WMFDecoderModule::HasH264()) {
|
||||
continue;
|
||||
}
|
||||
if (cap.mAPIName.EqualsLiteral(GMP_API_AUDIO_DECODER) &&
|
||||
cap.mAPITags.Contains(NS_LITERAL_CSTRING("org.w3.clearkey")) &&
|
||||
!WMFDecoderModule::HasAAC()) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
mCapabilities.AppendElement(Move(cap));
|
||||
}
|
||||
|
||||
if (mCapabilities.IsEmpty()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
return GenericPromise::CreateAndResolve(true, __func__);
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDEVINE_EME
|
||||
RefPtr<GenericPromise>
|
||||
GMPParent::ReadChromiumManifestFile(nsIFile* aFile)
|
||||
{
|
||||
nsAutoCString json;
|
||||
if (!ReadIntoString(aFile, json, 5 * 1024)) {
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
// DOM JSON parsing needs to run on the main thread.
|
||||
return InvokeAsync(AbstractThread::MainThread(), this, __func__,
|
||||
&GMPParent::ParseChromiumManifest, NS_ConvertUTF8toUTF16(json));
|
||||
}
|
||||
|
||||
RefPtr<GenericPromise>
|
||||
GMPParent::ParseChromiumManifest(nsString aJSON)
|
||||
{
|
||||
LOGD("%s: for '%s'", __FUNCTION__, NS_LossyConvertUTF16toASCII(aJSON).get());
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mozilla::dom::WidevineCDMManifest m;
|
||||
if (!m.Init(aJSON)) {
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
nsresult ignored; // Note: ToInteger returns 0 on failure.
|
||||
if (!WidevineAdapter::Supports(m.mX_cdm_module_versions.ToInteger(&ignored),
|
||||
m.mX_cdm_interface_versions.ToInteger(&ignored),
|
||||
m.mX_cdm_host_versions.ToInteger(&ignored))) {
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
mDisplayName = NS_ConvertUTF16toUTF8(m.mName);
|
||||
mDescription = NS_ConvertUTF16toUTF8(m.mDescription);
|
||||
mVersion = NS_ConvertUTF16toUTF8(m.mVersion);
|
||||
|
||||
GMPCapability video(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER));
|
||||
video.mAPITags.AppendElement(NS_LITERAL_CSTRING("h264"));
|
||||
video.mAPITags.AppendElement(NS_LITERAL_CSTRING("com.widevine.alpha"));
|
||||
mCapabilities.AppendElement(Move(video));
|
||||
|
||||
GMPCapability decrypt(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
|
||||
decrypt.mAPITags.AppendElement(NS_LITERAL_CSTRING("com.widevine.alpha"));
|
||||
mCapabilities.AppendElement(Move(decrypt));
|
||||
|
||||
MOZ_ASSERT(mName.EqualsLiteral("widevinecdm"));
|
||||
mAdapter = NS_LITERAL_STRING("widevine");
|
||||
#ifdef XP_WIN
|
||||
mLibs = NS_LITERAL_CSTRING("dxva2.dll");
|
||||
#endif
|
||||
|
||||
return GenericPromise::CreateAndResolve(true, __func__);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool
|
||||
GMPParent::CanBeSharedCrossNodeIds() const
|
||||
{
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "nsString.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsIFile.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
|
||||
class nsIThread;
|
||||
|
||||
|
@ -41,6 +42,16 @@ namespace gmp {
|
|||
class GMPCapability
|
||||
{
|
||||
public:
|
||||
explicit GMPCapability() {}
|
||||
GMPCapability(GMPCapability&& aOther)
|
||||
: mAPIName(Move(aOther.mAPIName))
|
||||
, mAPITags(Move(aOther.mAPITags))
|
||||
{
|
||||
}
|
||||
explicit GMPCapability(const nsCString& aAPIName)
|
||||
: mAPIName(aAPIName)
|
||||
{}
|
||||
explicit GMPCapability(const GMPCapability& aOther) = default;
|
||||
nsCString mAPIName;
|
||||
nsTArray<nsCString> mAPITags;
|
||||
};
|
||||
|
@ -75,7 +86,7 @@ public:
|
|||
|
||||
GMPParent();
|
||||
|
||||
nsresult Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir);
|
||||
RefPtr<GenericPromise> Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir);
|
||||
nsresult CloneFrom(const GMPParent* aOther);
|
||||
|
||||
void Crash();
|
||||
|
@ -151,9 +162,15 @@ public:
|
|||
|
||||
private:
|
||||
~GMPParent();
|
||||
|
||||
RefPtr<GeckoMediaPluginServiceParent> mService;
|
||||
bool EnsureProcessLoaded();
|
||||
nsresult ReadGMPMetaData();
|
||||
RefPtr<GenericPromise> ReadGMPMetaData();
|
||||
RefPtr<GenericPromise> ReadGMPInfoFile(nsIFile* aFile);
|
||||
#ifdef MOZ_WIDEVINE_EME
|
||||
RefPtr<GenericPromise> ParseChromiumManifest(nsString aJSON); // Main thread.
|
||||
RefPtr<GenericPromise> ReadChromiumManifestFile(nsIFile* aFile); // GMP thread.
|
||||
#endif
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
void WriteExtraDataForMinidump(CrashReporter::AnnotationTable& notes);
|
||||
void GetCrashID(nsString& aResult);
|
||||
|
@ -196,8 +213,9 @@ private:
|
|||
#ifdef XP_WIN
|
||||
nsCString mLibs;
|
||||
#endif
|
||||
nsString mAdapter;
|
||||
uint32_t mPluginId;
|
||||
nsTArray<nsAutoPtr<GMPCapability>> mCapabilities;
|
||||
nsTArray<GMPCapability> mCapabilities;
|
||||
GMPProcessParent* mProcess;
|
||||
bool mDeleteProcessOnlyOnUnload;
|
||||
bool mAbnormalShutdownInProgress;
|
||||
|
|
|
@ -357,6 +357,8 @@ GeckoMediaPluginService::GetThread(nsIThread** aThread)
|
|||
return rv;
|
||||
}
|
||||
|
||||
mAbstractGMPThread = AbstractThread::CreateXPCOMThreadWrapper(mGMPThread, false);
|
||||
|
||||
// Tell the thread to initialize plugins
|
||||
InitializePlugins();
|
||||
}
|
||||
|
@ -367,6 +369,12 @@ GeckoMediaPluginService::GetThread(nsIThread** aThread)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
RefPtr<AbstractThread>
|
||||
GeckoMediaPluginService::GetAbstractGMPThread()
|
||||
{
|
||||
return mAbstractGMPThread;
|
||||
}
|
||||
|
||||
class GetGMPContentParentForAudioDecoderDone : public GetGMPContentParentCallback
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "nsPIDOMWindow.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIWeakReference.h"
|
||||
#include "mozilla/AbstractThread.h"
|
||||
|
||||
template <class> struct already_AddRefed;
|
||||
|
||||
|
@ -74,6 +75,8 @@ public:
|
|||
void AddPluginCrashedEventTarget(const uint32_t aPluginId,
|
||||
nsPIDOMWindowInner* aParentWindow);
|
||||
|
||||
RefPtr<AbstractThread> GetAbstractGMPThread();
|
||||
|
||||
protected:
|
||||
GeckoMediaPluginService();
|
||||
virtual ~GeckoMediaPluginService();
|
||||
|
@ -92,6 +95,7 @@ protected:
|
|||
Mutex mMutex; // Protects mGMPThread and mGMPThreadShutdown and some members
|
||||
// in derived classes.
|
||||
nsCOMPtr<nsIThread> mGMPThread;
|
||||
RefPtr<AbstractThread> mAbstractGMPThread;
|
||||
bool mGMPThreadShutdown;
|
||||
bool mShuttingDownOnGMPThread;
|
||||
|
||||
|
|
|
@ -91,6 +91,8 @@ GeckoMediaPluginServiceParent::GeckoMediaPluginServiceParent()
|
|||
#endif
|
||||
, mScannedPluginOnDisk(false)
|
||||
, mWaitingForPluginsSyncShutdown(false)
|
||||
, mInitPromiseMonitor("GeckoMediaPluginServiceParent::mInitPromiseMonitor")
|
||||
, mLoadPluginsFromDiskComplete(false)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!sHaveSetGMPServiceParentPrefCaches) {
|
||||
|
@ -101,6 +103,7 @@ GeckoMediaPluginServiceParent::GeckoMediaPluginServiceParent()
|
|||
Preferences::AddBoolVarCache(&sAllowInsecureGMP,
|
||||
"media.gmp.insecure.allow", false);
|
||||
}
|
||||
mInitPromise.SetMonitor(&mInitPromiseMonitor);
|
||||
}
|
||||
|
||||
GeckoMediaPluginServiceParent::~GeckoMediaPluginServiceParent()
|
||||
|
@ -508,30 +511,72 @@ GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
RefPtr<GenericPromise>
|
||||
GeckoMediaPluginServiceParent::EnsureInitialized() {
|
||||
MonitorAutoLock lock(mInitPromiseMonitor);
|
||||
if (mLoadPluginsFromDiskComplete) {
|
||||
return GenericPromise::CreateAndResolve(true, __func__);
|
||||
}
|
||||
// We should have an init promise in flight.
|
||||
MOZ_ASSERT(!mInitPromise.IsEmpty());
|
||||
return mInitPromise.Ensure(__func__);
|
||||
}
|
||||
|
||||
bool
|
||||
GeckoMediaPluginServiceParent::GetContentParentFrom(const nsACString& aNodeId,
|
||||
const nsCString& aAPI,
|
||||
const nsTArray<nsCString>& aTags,
|
||||
UniquePtr<GetGMPContentParentCallback>&& aCallback)
|
||||
{
|
||||
RefPtr<GMPParent> gmp = SelectPluginForAPI(aNodeId, aAPI, aTags);
|
||||
|
||||
nsCString api = aTags[0];
|
||||
LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)this, (void *)gmp, api.get()));
|
||||
|
||||
if (!gmp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return gmp->GetGMPContentParent(Move(aCallback));
|
||||
RefPtr<GeckoMediaPluginServiceParent> self(this);
|
||||
RefPtr<AbstractThread> thread(GetAbstractGMPThread());
|
||||
nsCString nodeId(aNodeId);
|
||||
nsTArray<nsCString> tags(aTags);
|
||||
nsCString api(aAPI);
|
||||
GetGMPContentParentCallback* rawCallback = aCallback.release();
|
||||
EnsureInitialized()->Then(thread, __func__,
|
||||
[self, tags, api, nodeId, rawCallback]() -> void {
|
||||
UniquePtr<GetGMPContentParentCallback> callback(rawCallback);
|
||||
RefPtr<GMPParent> gmp = self->SelectPluginForAPI(nodeId, api, tags);
|
||||
LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)self, (void *)gmp, api.get()));
|
||||
if (!gmp) {
|
||||
NS_WARNING("GeckoMediaPluginServiceParent::GetContentParentFrom failed");
|
||||
callback->Done(nullptr);
|
||||
return;
|
||||
}
|
||||
gmp->GetGMPContentParent(Move(callback));
|
||||
},
|
||||
[rawCallback]() -> void {
|
||||
UniquePtr<GetGMPContentParentCallback> callback(rawCallback);
|
||||
NS_WARNING("GMPService::EnsureInitialized failed.");
|
||||
callback->Done(nullptr);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
GeckoMediaPluginServiceParent::InitializePlugins()
|
||||
{
|
||||
mGMPThread->Dispatch(
|
||||
NS_NewRunnableMethod(this, &GeckoMediaPluginServiceParent::LoadFromEnvironment),
|
||||
NS_DISPATCH_NORMAL);
|
||||
MonitorAutoLock lock(mInitPromiseMonitor);
|
||||
if (mLoadPluginsFromDiskComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<GeckoMediaPluginServiceParent> self(this);
|
||||
RefPtr<GenericPromise> p = mInitPromise.Ensure(__func__);
|
||||
RefPtr<AbstractThread> thread(GetAbstractGMPThread());
|
||||
InvokeAsync(thread, this, __func__, &GeckoMediaPluginServiceParent::LoadFromEnvironment)
|
||||
->Then(thread, __func__,
|
||||
[self]() -> void {
|
||||
MonitorAutoLock lock(self->mInitPromiseMonitor);
|
||||
self->mLoadPluginsFromDiskComplete = true;
|
||||
self->mInitPromise.Resolve(true, __func__);
|
||||
},
|
||||
[self]() -> void {
|
||||
MonitorAutoLock lock(self->mInitPromiseMonitor);
|
||||
self->mLoadPluginsFromDiskComplete = true;
|
||||
self->mInitPromise.Reject(NS_ERROR_FAILURE, __func__);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -702,36 +747,39 @@ GeckoMediaPluginServiceParent::CrashPlugins()
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
RefPtr<GenericPromise::AllPromiseType>
|
||||
GeckoMediaPluginServiceParent::LoadFromEnvironment()
|
||||
{
|
||||
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
|
||||
|
||||
const char* env = PR_GetEnv("MOZ_GMP_PATH");
|
||||
if (!env || !*env) {
|
||||
return;
|
||||
return GenericPromise::AllPromiseType::CreateAndResolve(true, __func__);
|
||||
}
|
||||
|
||||
nsString allpaths;
|
||||
if (NS_WARN_IF(NS_FAILED(NS_CopyNativeToUnicode(nsDependentCString(env), allpaths)))) {
|
||||
return;
|
||||
return GenericPromise::AllPromiseType::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
nsTArray<RefPtr<GenericPromise>> promises;
|
||||
uint32_t pos = 0;
|
||||
while (pos < allpaths.Length()) {
|
||||
// Loop over multiple path entries separated by colons (*nix) or
|
||||
// semicolons (Windows)
|
||||
int32_t next = allpaths.FindChar(XPCOM_ENV_PATH_SEPARATOR[0], pos);
|
||||
if (next == -1) {
|
||||
AddOnGMPThread(nsDependentSubstring(allpaths, pos));
|
||||
promises.AppendElement(AddOnGMPThread(nsString(Substring(allpaths, pos))));
|
||||
break;
|
||||
} else {
|
||||
AddOnGMPThread(nsDependentSubstring(allpaths, pos, next - pos));
|
||||
promises.AppendElement(AddOnGMPThread(nsString(Substring(allpaths, pos, next - pos))));
|
||||
pos = next + 1;
|
||||
}
|
||||
}
|
||||
|
||||
mScannedPluginOnDisk = true;
|
||||
RefPtr<AbstractThread> thread(GetAbstractGMPThread());
|
||||
return GenericPromise::All(thread, promises);
|
||||
}
|
||||
|
||||
class NotifyObserversTask final : public nsRunnable {
|
||||
|
@ -758,13 +806,9 @@ private:
|
|||
NS_IMETHODIMP
|
||||
GeckoMediaPluginServiceParent::PathRunnable::Run()
|
||||
{
|
||||
if (mOperation == ADD) {
|
||||
mService->AddOnGMPThread(mPath);
|
||||
} else {
|
||||
mService->RemoveOnGMPThread(mPath,
|
||||
mOperation == REMOVE_AND_DELETE_FROM_DISK,
|
||||
mDefer);
|
||||
}
|
||||
mService->RemoveOnGMPThread(mPath,
|
||||
mOperation == REMOVE_AND_DELETE_FROM_DISK,
|
||||
mDefer);
|
||||
#ifndef MOZ_WIDGET_GONK // Bug 1214967: disabled on B2G due to inscrutable test failures.
|
||||
// For e10s, we must fire a notification so that all ContentParents notify
|
||||
// their children to update the codecs that the GMPDecoderModule can use.
|
||||
|
@ -778,12 +822,41 @@ GeckoMediaPluginServiceParent::PathRunnable::Run()
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
RefPtr<GenericPromise>
|
||||
GeckoMediaPluginServiceParent::AsyncAddPluginDirectory(const nsAString& aDirectory)
|
||||
{
|
||||
RefPtr<AbstractThread> thread(GetAbstractGMPThread());
|
||||
nsString dir(aDirectory);
|
||||
return InvokeAsync(thread, this, __func__, &GeckoMediaPluginServiceParent::AddOnGMPThread, dir)
|
||||
->Then(AbstractThread::MainThread(), __func__,
|
||||
[dir]() -> void {
|
||||
LOGD(("GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s succeeded",
|
||||
NS_ConvertUTF16toUTF8(dir).get()));
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// For e10s, we must fire a notification so that all ContentParents notify
|
||||
// their children to update the codecs that the GMPDecoderModule can use.
|
||||
nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
|
||||
MOZ_ASSERT(obsService);
|
||||
if (obsService) {
|
||||
obsService->NotifyObservers(nullptr, "gmp-changed", nullptr);
|
||||
}
|
||||
// For non-e10s, and for decoding in the chrome process, must update GMP
|
||||
// PDM's codecs list directly.
|
||||
GMPDecoderModule::UpdateUsableCodecs();
|
||||
},
|
||||
[dir]() -> void {
|
||||
LOGD(("GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s failed",
|
||||
NS_ConvertUTF16toUTF8(dir).get()));
|
||||
})
|
||||
->CompletionPromise();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
GeckoMediaPluginServiceParent::AddPluginDirectory(const nsAString& aDirectory)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return GMPDispatch(new PathRunnable(this, aDirectory,
|
||||
PathRunnable::EOperation::ADD));
|
||||
RefPtr<GenericPromise> p = AsyncAddPluginDirectory(aDirectory);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -983,8 +1056,8 @@ GeckoMediaPluginServiceParent::ClonePlugin(const GMPParent* aOriginal)
|
|||
return gmp.get();
|
||||
}
|
||||
|
||||
void
|
||||
GeckoMediaPluginServiceParent::AddOnGMPThread(const nsAString& aDirectory)
|
||||
RefPtr<GenericPromise>
|
||||
GeckoMediaPluginServiceParent::AddOnGMPThread(nsString aDirectory)
|
||||
{
|
||||
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
|
||||
LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, NS_LossyConvertUTF16toASCII(aDirectory).get()));
|
||||
|
@ -992,22 +1065,31 @@ GeckoMediaPluginServiceParent::AddOnGMPThread(const nsAString& aDirectory)
|
|||
nsCOMPtr<nsIFile> directory;
|
||||
nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
RefPtr<GMPParent> gmp = CreateGMPParent();
|
||||
rv = gmp ? gmp->Init(this, directory) : NS_ERROR_NOT_AVAILABLE;
|
||||
if (NS_FAILED(rv)) {
|
||||
if (!gmp) {
|
||||
NS_WARNING("Can't Create GMPParent");
|
||||
return;
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mPlugins.AppendElement(gmp);
|
||||
}
|
||||
|
||||
NS_DispatchToMainThread(new NotifyObserversTask("gmp-path-added"), NS_DISPATCH_NORMAL);
|
||||
RefPtr<GeckoMediaPluginServiceParent> self(this);
|
||||
RefPtr<AbstractThread> thread(GetAbstractGMPThread());
|
||||
nsCString dir = NS_ConvertUTF16toUTF8(aDirectory);
|
||||
return gmp->Init(this, directory)->Then(thread, __func__,
|
||||
[gmp, self, dir]() -> void {
|
||||
LOGD(("%s::%s: %s Succeeded", __CLASS__, __FUNCTION__, dir.get()));
|
||||
{
|
||||
MutexAutoLock lock(self->mMutex);
|
||||
self->mPlugins.AppendElement(gmp);
|
||||
}
|
||||
NS_DispatchToMainThread(new NotifyObserversTask("gmp-path-added"), NS_DISPATCH_NORMAL);
|
||||
},
|
||||
[dir]() -> void {
|
||||
LOGD(("%s::%s: %s Failed", __CLASS__, __FUNCTION__, dir.get()));
|
||||
})
|
||||
->CompletionPromise();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1245,13 +1327,18 @@ GeckoMediaPluginServiceParent::GetNodeId(const nsAString& aOrigin,
|
|||
|
||||
nsresult rv;
|
||||
|
||||
if (aOrigin.EqualsLiteral("null") ||
|
||||
if (aGMPName.EqualsLiteral("gmp-widevinecdm") ||
|
||||
aOrigin.EqualsLiteral("null") ||
|
||||
aOrigin.IsEmpty() ||
|
||||
aTopLevelOrigin.EqualsLiteral("null") ||
|
||||
aTopLevelOrigin.IsEmpty()) {
|
||||
// At least one of the (origin, topLevelOrigin) is null or empty;
|
||||
// probably a local file. Generate a random node id, and don't store
|
||||
// it so that the GMP's storage is temporary and not shared.
|
||||
// This is for the Google Widevine CDM, which doesn't have persistent
|
||||
// storage and which can't handle being used by more than one origin at
|
||||
// once in the same plugin instance, or at least one of the
|
||||
// (origin, topLevelOrigin) is null or empty; probably a local file.
|
||||
// Generate a random node id, and don't store it so that the GMP's storage
|
||||
// is temporary and the process for this GMP is not shared with GMP
|
||||
// instances that have the same nodeId.
|
||||
nsAutoCString salt;
|
||||
rv = GenerateRandomPathName(salt, NodeIdSaltLength);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "nsDataHashtable.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
|
||||
template <class> struct already_AddRefed;
|
||||
|
||||
|
@ -55,6 +56,8 @@ public:
|
|||
#ifdef MOZ_CRASHREPORTER
|
||||
void SetAsyncShutdownPluginState(GMPParent* aGMPParent, char aId, const nsCString& aState);
|
||||
#endif // MOZ_CRASHREPORTER
|
||||
RefPtr<GenericPromise> EnsureInitialized();
|
||||
RefPtr<GenericPromise> AsyncAddPluginDirectory(const nsAString& aDirectory);
|
||||
|
||||
private:
|
||||
friend class GMPServiceParent;
|
||||
|
@ -80,10 +83,8 @@ private:
|
|||
void NotifySyncShutdownComplete();
|
||||
void NotifyAsyncShutdownComplete();
|
||||
|
||||
void LoadFromEnvironment();
|
||||
void ProcessPossiblePlugin(nsIFile* aDir);
|
||||
|
||||
void AddOnGMPThread(const nsAString& aDirectory);
|
||||
void RemoveOnGMPThread(const nsAString& aDirectory,
|
||||
const bool aDeleteFromDisk,
|
||||
const bool aCanDefer);
|
||||
|
@ -105,6 +106,8 @@ protected:
|
|||
void ReAddOnGMPThread(const RefPtr<GMPParent>& aOld);
|
||||
void PluginTerminated(const RefPtr<GMPParent>& aOld);
|
||||
void InitializePlugins() override;
|
||||
RefPtr<GenericPromise::AllPromiseType> LoadFromEnvironment();
|
||||
RefPtr<GenericPromise> AddOnGMPThread(nsString aDirectory);
|
||||
bool GetContentParentFrom(const nsACString& aNodeId,
|
||||
const nsCString& aAPI,
|
||||
const nsTArray<nsCString>& aTags,
|
||||
|
@ -119,7 +122,6 @@ private:
|
|||
{
|
||||
public:
|
||||
enum EOperation {
|
||||
ADD,
|
||||
REMOVE,
|
||||
REMOVE_AND_DELETE_FROM_DISK,
|
||||
};
|
||||
|
@ -193,6 +195,12 @@ private:
|
|||
// Hashes node id to whether that node id is allowed to store data
|
||||
// persistently on disk.
|
||||
nsDataHashtable<nsCStringHashKey, bool> mPersistentStorageAllowed;
|
||||
|
||||
// Synchronization for barrier that ensures we've loaded GMPs from
|
||||
// MOZ_GMP_PATH before allowing GetContentParentFrom() to proceed.
|
||||
Monitor mInitPromiseMonitor;
|
||||
MozPromiseHolder<GenericPromise> mInitPromise;
|
||||
bool mLoadPluginsFromDiskComplete;
|
||||
};
|
||||
|
||||
nsresult ReadSalt(nsIFile* aPath, nsACString& aOutData);
|
||||
|
|
|
@ -138,7 +138,7 @@ ReadIntoArray(nsIFile* aFile,
|
|||
return (bytesRead == length);
|
||||
}
|
||||
|
||||
static bool
|
||||
bool
|
||||
ReadIntoString(nsIFile* aFile,
|
||||
nsCString& aOutDst,
|
||||
size_t aMaxLength)
|
||||
|
|
|
@ -75,6 +75,11 @@ ReadIntoArray(nsIFile* aFile,
|
|||
nsTArray<uint8_t>& aOutDst,
|
||||
size_t aMaxLength);
|
||||
|
||||
bool
|
||||
ReadIntoString(nsIFile* aFile,
|
||||
nsCString& aOutDst,
|
||||
size_t aMaxLength);
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
|
@ -34,7 +34,7 @@ parent:
|
|||
child:
|
||||
async BeginAsyncShutdown();
|
||||
async CrashPluginNow();
|
||||
intr StartPlugin();
|
||||
intr StartPlugin(nsString adapter);
|
||||
async SetNodeId(nsCString nodeId);
|
||||
async PreloadLibs(nsCString libs);
|
||||
async CloseActive();
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
# 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/.
|
||||
|
||||
for cdm in CONFIG['MOZ_EME_MODULES']:
|
||||
DEFINES['MOZ_%s_EME' % cdm.upper()] = True
|
||||
|
||||
XPIDL_MODULE = 'content_geckomediaplugins'
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
|
@ -114,6 +117,11 @@ if CONFIG['OS_TARGET'] in ('WINNT', 'Darwin'):
|
|||
'rlz',
|
||||
]
|
||||
|
||||
if 'widevine' in CONFIG['MOZ_EME_MODULES']:
|
||||
DIRS += [
|
||||
'widevine-adapter',
|
||||
]
|
||||
|
||||
IPDL_SOURCES += [
|
||||
'GMPTypes.ipdlh',
|
||||
'PGMP.ipdl',
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "WidevineAdapter.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "WidevineDecryptor.h"
|
||||
#include "WidevineUtils.h"
|
||||
#include "WidevineVideoDecoder.h"
|
||||
#include "gmp-api/gmp-entrypoints.h"
|
||||
#include "gmp-api/gmp-decryption.h"
|
||||
#include "gmp-api/gmp-video-codec.h"
|
||||
#include "gmp-api/gmp-platform.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
|
||||
static const GMPPlatformAPI* sPlatform = nullptr;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
const char* WidevineKeySystem = "com.widevine.alpha";
|
||||
StaticRefPtr<CDMWrapper> sCDMWrapper;
|
||||
|
||||
GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime) {
|
||||
return sPlatform->getcurrenttime(aOutTime);
|
||||
}
|
||||
|
||||
// Call on main thread only.
|
||||
GMPErr GMPSetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS) {
|
||||
return sPlatform->settimer(aTask, aTimeoutMS);
|
||||
}
|
||||
|
||||
GMPErr GMPCreateRecord(const char* aRecordName,
|
||||
uint32_t aRecordNameSize,
|
||||
GMPRecord** aOutRecord,
|
||||
GMPRecordClient* aClient)
|
||||
{
|
||||
return sPlatform->createrecord(aRecordName, aRecordNameSize, aOutRecord, aClient);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineAdapter::SetAdaptee(PRLibrary* aLib)
|
||||
{
|
||||
mLib = aLib;
|
||||
}
|
||||
|
||||
void* GetCdmHost(int aHostInterfaceVersion, void* aUserData)
|
||||
{
|
||||
Log("GetCdmHostFunc(%d, %p)", aHostInterfaceVersion, aUserData);
|
||||
WidevineDecryptor* decryptor = reinterpret_cast<WidevineDecryptor*>(aUserData);
|
||||
MOZ_ASSERT(decryptor);
|
||||
return static_cast<cdm::Host_8*>(decryptor);
|
||||
}
|
||||
|
||||
#define STRINGIFY(s) _STRINGIFY(s)
|
||||
#define _STRINGIFY(s) #s
|
||||
|
||||
GMPErr
|
||||
WidevineAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI)
|
||||
{
|
||||
#ifdef ENABLE_WIDEVINE_LOG
|
||||
if (getenv("GMP_LOG_FILE")) {
|
||||
// Clear log file.
|
||||
FILE* f = fopen(getenv("GMP_LOG_FILE"), "w");
|
||||
if (f) {
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
sPlatform = aPlatformAPI;
|
||||
if (!mLib) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
|
||||
auto init = reinterpret_cast<decltype(::INITIALIZE_CDM_MODULE)*>(
|
||||
PR_FindFunctionSymbol(mLib, STRINGIFY(INITIALIZE_CDM_MODULE)));
|
||||
if (!init) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
|
||||
Log(STRINGIFY(INITIALIZE_CDM_MODULE)"()");
|
||||
init();
|
||||
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
GMPErr
|
||||
WidevineAdapter::GMPGetAPI(const char* aAPIName,
|
||||
void* aHostAPI,
|
||||
void** aPluginAPI)
|
||||
{
|
||||
Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p",
|
||||
aAPIName, aHostAPI, aPluginAPI, this);
|
||||
if (!strcmp(aAPIName, GMP_API_DECRYPTOR)) {
|
||||
if (sCDMWrapper) {
|
||||
// We only support one CDM instance per GMP process. Fail!
|
||||
Log("WidevineAdapter::GMPGetAPI() Tried to create more than once CDM per process! FAIL!");
|
||||
return GMPQuotaExceededErr;
|
||||
}
|
||||
auto create = reinterpret_cast<decltype(::CreateCdmInstance)*>(
|
||||
PR_FindFunctionSymbol(mLib, "CreateCdmInstance"));
|
||||
if (!create) {
|
||||
Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p FAILED to create cdm",
|
||||
aAPIName, aHostAPI, aPluginAPI, this);
|
||||
return GMPGenericErr;
|
||||
}
|
||||
|
||||
WidevineDecryptor* decryptor = new WidevineDecryptor();
|
||||
|
||||
auto cdm = reinterpret_cast<cdm::ContentDecryptionModule*>(
|
||||
create(cdm::ContentDecryptionModule::kVersion,
|
||||
WidevineKeySystem,
|
||||
strlen(WidevineKeySystem),
|
||||
&GetCdmHost,
|
||||
decryptor));
|
||||
Log("cdm: 0x%x", cdm);
|
||||
sCDMWrapper = new CDMWrapper(cdm);
|
||||
decryptor->SetCDM(RefPtr<CDMWrapper>(sCDMWrapper));
|
||||
|
||||
cdm->Initialize(false, /* allow_distinctive_identifier */
|
||||
false /* allow_persistent_state */);
|
||||
|
||||
*aPluginAPI = decryptor;
|
||||
|
||||
} else if (!strcmp(aAPIName, GMP_API_VIDEO_DECODER)) {
|
||||
*aPluginAPI = new WidevineVideoDecoder(static_cast<GMPVideoHost*>(aHostAPI), RefPtr<CDMWrapper>(sCDMWrapper));
|
||||
|
||||
}
|
||||
return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
|
||||
}
|
||||
|
||||
void
|
||||
WidevineAdapter::GMPShutdown()
|
||||
{
|
||||
Log("WidevineAdapter::GMPShutdown()");
|
||||
|
||||
decltype(::DeinitializeCdmModule)* deinit;
|
||||
deinit = (decltype(deinit))(PR_FindFunctionSymbol(mLib, "DeinitializeCdmModule"));
|
||||
if (deinit) {
|
||||
Log("DeinitializeCdmModule()");
|
||||
deinit();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WidevineAdapter::GMPSetNodeId(const char* aNodeId, uint32_t aLength)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool
|
||||
WidevineAdapter::Supports(int32_t aModuleVersion,
|
||||
int32_t aInterfaceVersion,
|
||||
int32_t aHostVersion)
|
||||
{
|
||||
return aModuleVersion == CDM_MODULE_VERSION &&
|
||||
aInterfaceVersion == cdm::ContentDecryptionModule::kVersion &&
|
||||
aHostVersion == cdm::Host_8::kVersion;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,56 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef WidevineAdapter_h_
|
||||
#define WidevineAdapter_h_
|
||||
|
||||
#include "GMPLoader.h"
|
||||
#include "prlink.h"
|
||||
#include "GMPUtils.h"
|
||||
|
||||
struct GMPPlatformAPI;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class WidevineAdapter : public gmp::GMPAdapter {
|
||||
public:
|
||||
|
||||
void SetAdaptee(PRLibrary* aLib) override;
|
||||
|
||||
// These are called in place of the corresponding GMP API functions.
|
||||
GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) override;
|
||||
GMPErr GMPGetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI) override;
|
||||
void GMPShutdown() override;
|
||||
void GMPSetNodeId(const char* aNodeId, uint32_t aLength) override;
|
||||
|
||||
static bool Supports(int32_t aModuleVersion,
|
||||
int32_t aInterfaceVersion,
|
||||
int32_t aHostVersion);
|
||||
|
||||
private:
|
||||
PRLibrary* mLib = nullptr;
|
||||
};
|
||||
|
||||
GMPErr GMPCreateThread(GMPThread** aThread);
|
||||
GMPErr GMPRunOnMainThread(GMPTask* aTask);
|
||||
GMPErr GMPCreateMutex(GMPMutex** aMutex);
|
||||
|
||||
// Call on main thread only.
|
||||
GMPErr GMPCreateRecord(const char* aRecordName,
|
||||
uint32_t aRecordNameSize,
|
||||
GMPRecord** aOutRecord,
|
||||
GMPRecordClient* aClient);
|
||||
|
||||
// Call on main thread only.
|
||||
GMPErr GMPSetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS);
|
||||
|
||||
GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime);
|
||||
|
||||
GMPErr GMPCreateRecordIterator(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
|
||||
void* aUserArg);
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // WidevineAdapter_h_
|
|
@ -0,0 +1,453 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "WidevineDecryptor.h"
|
||||
|
||||
#include "WidevineAdapter.h"
|
||||
#include "WidevineUtils.h"
|
||||
#include <mozilla/SizePrintfMacros.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
using namespace cdm;
|
||||
using namespace std;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
||||
WidevineDecryptor::WidevineDecryptor()
|
||||
: mCallback(nullptr)
|
||||
{
|
||||
Log("WidevineDecryptor created this=%p", this);
|
||||
AddRef(); // Released in DecryptingComplete().
|
||||
}
|
||||
|
||||
WidevineDecryptor::~WidevineDecryptor()
|
||||
{
|
||||
Log("WidevineDecryptor destroyed this=%p", this);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::SetCDM(RefPtr<CDMWrapper> aCDM)
|
||||
{
|
||||
mCDM = aCDM;
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::Init(GMPDecryptorCallback* aCallback)
|
||||
{
|
||||
mCallback = aCallback;
|
||||
mCallback->SetCapabilities(GMP_EME_CAP_DECRYPT_AND_DECODE_VIDEO |
|
||||
GMP_EME_CAP_DECRYPT_AUDIO);
|
||||
}
|
||||
|
||||
static SessionType
|
||||
ToCDMSessionType(GMPSessionType aSessionType)
|
||||
{
|
||||
switch (aSessionType) {
|
||||
case kGMPTemporySession: return kTemporary;
|
||||
case kGMPPersistentSession: return kPersistentLicense;
|
||||
case kGMPSessionInvalid: return kTemporary;
|
||||
// TODO: kPersistentKeyRelease
|
||||
}
|
||||
MOZ_ASSERT(false); // Not supposed to get here.
|
||||
return kTemporary;
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::CreateSession(uint32_t aCreateSessionToken,
|
||||
uint32_t aPromiseId,
|
||||
const char* aInitDataType,
|
||||
uint32_t aInitDataTypeSize,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize,
|
||||
GMPSessionType aSessionType)
|
||||
{
|
||||
Log("Decryptor::CreateSession(token=%d, pid=%d)", aCreateSessionToken, aPromiseId);
|
||||
MOZ_ASSERT(!strcmp(aInitDataType, "cenc"));
|
||||
mPromiseIdToNewSessionTokens[aPromiseId] = aCreateSessionToken;
|
||||
CDM()->CreateSessionAndGenerateRequest(aPromiseId,
|
||||
ToCDMSessionType(aSessionType),
|
||||
kCenc,
|
||||
aInitData, aInitDataSize);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::LoadSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength)
|
||||
{
|
||||
Log("Decryptor::LoadSession(pid=%d, %s)", aPromiseId, aSessionId);
|
||||
// TODO: session type??
|
||||
CDM()->LoadSession(aPromiseId, kPersistentLicense, aSessionId, aSessionIdLength);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::UpdateSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength,
|
||||
const uint8_t* aResponse,
|
||||
uint32_t aResponseSize)
|
||||
{
|
||||
Log("Decryptor::UpdateSession(pid=%d, session=%s)", aPromiseId, aSessionId);
|
||||
CDM()->UpdateSession(aPromiseId, aSessionId, aSessionIdLength, aResponse, aResponseSize);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::CloseSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength)
|
||||
{
|
||||
Log("Decryptor::CloseSession(pid=%d, session=%s)", aPromiseId, aSessionId);
|
||||
CDM()->CloseSession(aPromiseId, aSessionId, aSessionIdLength);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::RemoveSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength)
|
||||
{
|
||||
Log("Decryptor::RemoveSession(%s)", aSessionId);
|
||||
CDM()->RemoveSession(aPromiseId, aSessionId, aSessionIdLength);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::SetServerCertificate(uint32_t aPromiseId,
|
||||
const uint8_t* aServerCert,
|
||||
uint32_t aServerCertSize)
|
||||
{
|
||||
Log("Decryptor::SetServerCertificate()");
|
||||
CDM()->SetServerCertificate(aPromiseId, aServerCert, aServerCertSize);
|
||||
}
|
||||
|
||||
class WidevineDecryptedBlock : public cdm::DecryptedBlock {
|
||||
public:
|
||||
|
||||
WidevineDecryptedBlock()
|
||||
: mBuffer(nullptr)
|
||||
, mTimestamp(0)
|
||||
{
|
||||
}
|
||||
|
||||
~WidevineDecryptedBlock() {
|
||||
if (mBuffer) {
|
||||
mBuffer->Destroy();
|
||||
mBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDecryptedBuffer(cdm::Buffer* aBuffer) override {
|
||||
mBuffer = aBuffer;
|
||||
}
|
||||
|
||||
cdm::Buffer* DecryptedBuffer() override {
|
||||
return mBuffer;
|
||||
}
|
||||
|
||||
void SetTimestamp(int64_t aTimestamp) override {
|
||||
mTimestamp = aTimestamp;
|
||||
}
|
||||
|
||||
int64_t Timestamp() const override {
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
private:
|
||||
cdm::Buffer* mBuffer;
|
||||
int64_t mTimestamp;
|
||||
};
|
||||
|
||||
void
|
||||
WidevineDecryptor::Decrypt(GMPBuffer* aBuffer,
|
||||
GMPEncryptedBufferMetadata* aMetadata)
|
||||
{
|
||||
const GMPEncryptedBufferMetadata* crypto = aMetadata;
|
||||
InputBuffer sample;
|
||||
nsTArray<SubsampleEntry> subsamples;
|
||||
InitInputBuffer(crypto, aBuffer->Id(), aBuffer->Data(), aBuffer->Size(), sample, subsamples);
|
||||
WidevineDecryptedBlock decrypted;
|
||||
Status rv = CDM()->Decrypt(sample, &decrypted);
|
||||
Log("Decryptor::Decrypt(timestamp=%lld) rv=%d sz=%d",
|
||||
sample.timestamp, rv, decrypted.DecryptedBuffer()->Size());
|
||||
if (rv == kSuccess) {
|
||||
aBuffer->Resize(decrypted.DecryptedBuffer()->Size());
|
||||
memcpy(aBuffer->Data(),
|
||||
decrypted.DecryptedBuffer()->Data(),
|
||||
decrypted.DecryptedBuffer()->Size());
|
||||
}
|
||||
mCallback->Decrypted(aBuffer, ToGMPErr(rv));
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::DecryptingComplete()
|
||||
{
|
||||
Log("WidevineDecryptor::DecryptingComplete() this=%p", this);
|
||||
mCDM = nullptr;
|
||||
Release();
|
||||
}
|
||||
|
||||
class WidevineBuffer : public cdm::Buffer {
|
||||
public:
|
||||
WidevineBuffer(size_t aSize) {
|
||||
Log("WidevineBuffer(size=" PRIuSIZE ") created", aSize);
|
||||
mBuffer.SetLength(aSize);
|
||||
}
|
||||
~WidevineBuffer() {
|
||||
Log("WidevineBuffer(size=" PRIuSIZE ") destroyed", Size());
|
||||
}
|
||||
void Destroy() override { delete this; }
|
||||
uint32_t Capacity() const override { return mBuffer.Length(); };
|
||||
uint8_t* Data() override { return mBuffer.Elements(); }
|
||||
void SetSize(uint32_t aSize) override { mBuffer.SetLength(aSize); }
|
||||
uint32_t Size() const override { return mBuffer.Length(); }
|
||||
|
||||
private:
|
||||
WidevineBuffer(const WidevineBuffer&);
|
||||
void operator=(const WidevineBuffer&);
|
||||
|
||||
nsTArray<uint8_t> mBuffer;
|
||||
};
|
||||
|
||||
Buffer*
|
||||
WidevineDecryptor::Allocate(uint32_t aCapacity)
|
||||
{
|
||||
Log("Decryptor::Allocate(capacity=%u)", aCapacity);
|
||||
return new WidevineBuffer(aCapacity);
|
||||
}
|
||||
|
||||
class TimerTask : public GMPTask {
|
||||
public:
|
||||
TimerTask(WidevineDecryptor* aDecryptor,
|
||||
RefPtr<CDMWrapper> aCDM,
|
||||
void* aContext)
|
||||
: mDecryptor(aDecryptor)
|
||||
, mCDM(aCDM)
|
||||
, mContext(aContext)
|
||||
{
|
||||
}
|
||||
~TimerTask() override {}
|
||||
void Run() override {
|
||||
mCDM->GetCDM()->TimerExpired(mContext);
|
||||
}
|
||||
void Destroy() override { delete this; }
|
||||
private:
|
||||
RefPtr<WidevineDecryptor> mDecryptor;
|
||||
RefPtr<CDMWrapper> mCDM;
|
||||
void* mContext;
|
||||
};
|
||||
|
||||
void
|
||||
WidevineDecryptor::SetTimer(int64_t aDelayMs, void* aContext)
|
||||
{
|
||||
Log("Decryptor::SetTimer(delay_ms=%lld, context=0x%x)", aDelayMs, aContext);
|
||||
if (mCDM) {
|
||||
GMPSetTimerOnMainThread(new TimerTask(this, mCDM, aContext), aDelayMs);
|
||||
}
|
||||
}
|
||||
|
||||
Time
|
||||
WidevineDecryptor::GetCurrentWallTime()
|
||||
{
|
||||
GMPTimestamp gmpTime = 0;
|
||||
GMPGetCurrentTime(&gmpTime);
|
||||
double t = (double)gmpTime / 1e3;
|
||||
Log("Decryptor::GetCurrentWallTime()= %lf", t);
|
||||
return t;
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::OnResolveNewSessionPromise(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdSize)
|
||||
{
|
||||
Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d)", aPromiseId);
|
||||
auto iter = mPromiseIdToNewSessionTokens.find(aPromiseId);
|
||||
if (iter == mPromiseIdToNewSessionTokens.end()) {
|
||||
Log("FAIL: Decryptor::OnResolveNewSessionPromise(aPromiseId=%d) unknown aPromiseId", aPromiseId);
|
||||
return;
|
||||
}
|
||||
mCallback->SetSessionId(iter->second, aSessionId, aSessionIdSize);
|
||||
mCallback->ResolvePromise(aPromiseId);
|
||||
mPromiseIdToNewSessionTokens.erase(iter);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::OnResolvePromise(uint32_t aPromiseId)
|
||||
{
|
||||
Log("Decryptor::OnResolvePromise(aPromiseId=%d)", aPromiseId);
|
||||
mCallback->ResolvePromise(aPromiseId);
|
||||
}
|
||||
|
||||
static GMPDOMException
|
||||
ToGMPDOMException(cdm::Error aError)
|
||||
{
|
||||
switch (aError) {
|
||||
case kNotSupportedError: return kGMPNotSupportedError;
|
||||
case kInvalidStateError: return kGMPInvalidStateError;
|
||||
case kInvalidAccessError: return kGMPInvalidAccessError;
|
||||
case kQuotaExceededError: return kGMPQuotaExceededError;
|
||||
case kUnknownError: return kGMPInvalidModificationError; // Note: Unique placeholder.
|
||||
case kClientError: return kGMPAbortError; // Note: Unique placeholder.
|
||||
case kOutputError: return kGMPSecurityError; // Note: Unique placeholder.
|
||||
};
|
||||
return kGMPTimeoutError; // Note: Unique placeholder.
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::OnRejectPromise(uint32_t aPromiseId,
|
||||
Error aError,
|
||||
uint32_t aSystemCode,
|
||||
const char* aErrorMessage,
|
||||
uint32_t aErrorMessageSize)
|
||||
{
|
||||
Log("Decryptor::OnRejectPromise(aPromiseId=%d, err=%d, sysCode=%d, msg=%s)",
|
||||
aPromiseId, (int)aError, aSystemCode, aErrorMessage);
|
||||
mCallback->RejectPromise(aPromiseId,
|
||||
ToGMPDOMException(aError),
|
||||
!aErrorMessageSize ? "" : aErrorMessage,
|
||||
aErrorMessageSize);
|
||||
}
|
||||
|
||||
static GMPSessionMessageType
|
||||
ToGMPMessageType(MessageType message_type)
|
||||
{
|
||||
switch (message_type) {
|
||||
case kLicenseRequest: return kGMPLicenseRequest;
|
||||
case kLicenseRenewal: return kGMPLicenseRenewal;
|
||||
case kLicenseRelease: return kGMPLicenseRelease;
|
||||
}
|
||||
return kGMPMessageInvalid;
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::OnSessionMessage(const char* aSessionId,
|
||||
uint32_t aSessionIdSize,
|
||||
MessageType aMessageType,
|
||||
const char* aMessage,
|
||||
uint32_t aMessageSize,
|
||||
const char* aLegacyDestinationUrl,
|
||||
uint32_t aLegacyDestinationUrlLength)
|
||||
{
|
||||
Log("Decryptor::OnSessionMessage()");
|
||||
mCallback->SessionMessage(aSessionId,
|
||||
aSessionIdSize,
|
||||
ToGMPMessageType(aMessageType),
|
||||
reinterpret_cast<const uint8_t*>(aMessage),
|
||||
aMessageSize);
|
||||
}
|
||||
|
||||
static GMPMediaKeyStatus
|
||||
ToGMPKeyStatus(KeyStatus aStatus)
|
||||
{
|
||||
switch (aStatus) {
|
||||
case kUsable: return kGMPUsable;
|
||||
case kInternalError: return kGMPInternalError;
|
||||
case kExpired: return kGMPExpired;
|
||||
case kOutputRestricted: return kGMPOutputRestricted;
|
||||
case kOutputDownscaled: return kGMPOutputDownscaled;
|
||||
case kStatusPending: return kGMPStatusPending;
|
||||
case kReleased: return kGMPReleased;
|
||||
}
|
||||
return kGMPUnknown;
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::OnSessionKeysChange(const char* aSessionId,
|
||||
uint32_t aSessionIdSize,
|
||||
bool aHasAdditionalUsableKey,
|
||||
const KeyInformation* aKeysInfo,
|
||||
uint32_t aKeysInfoCount)
|
||||
{
|
||||
Log("Decryptor::OnSessionKeysChange()");
|
||||
for (uint32_t i = 0; i < aKeysInfoCount; i++) {
|
||||
mCallback->KeyStatusChanged(aSessionId,
|
||||
aSessionIdSize,
|
||||
aKeysInfo[i].key_id,
|
||||
aKeysInfo[i].key_id_size,
|
||||
ToGMPKeyStatus(aKeysInfo[i].status));
|
||||
}
|
||||
}
|
||||
|
||||
static GMPTimestamp
|
||||
ToGMPTime(Time aCDMTime)
|
||||
{
|
||||
return static_cast<GMPTimestamp>(aCDMTime * 1000);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::OnExpirationChange(const char* aSessionId,
|
||||
uint32_t aSessionIdSize,
|
||||
Time aNewExpiryTime)
|
||||
{
|
||||
Log("Decryptor::OnExpirationChange(sid=%s) t=%lf", aSessionId, aNewExpiryTime);
|
||||
GMPTimestamp expiry = ToGMPTime(aNewExpiryTime);
|
||||
if (aNewExpiryTime == 0) {
|
||||
return;
|
||||
}
|
||||
mCallback->ExpirationChange(aSessionId, aSessionIdSize, expiry);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::OnSessionClosed(const char* aSessionId,
|
||||
uint32_t aSessionIdSize)
|
||||
{
|
||||
Log("Decryptor::OnSessionClosed(sid=%s)", aSessionId);
|
||||
mCallback->SessionClosed(aSessionId, aSessionIdSize);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::OnLegacySessionError(const char* aSessionId,
|
||||
uint32_t aSessionIdLength,
|
||||
Error aError,
|
||||
uint32_t aSystemCode,
|
||||
const char* aErrorMessage,
|
||||
uint32_t aErrorMessageLength)
|
||||
{
|
||||
Log("Decryptor::OnSessionClosed(sid=%s, error=%d)", aSessionId, (int)aError);
|
||||
mCallback->SessionError(aSessionId,
|
||||
aSessionIdLength,
|
||||
ToGMPDOMException(aError),
|
||||
aSystemCode,
|
||||
aErrorMessage,
|
||||
aErrorMessageLength);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::SendPlatformChallenge(const char* aServiceId,
|
||||
uint32_t aServiceIdSize,
|
||||
const char* aChallenge,
|
||||
uint32_t aChallengeSize)
|
||||
{
|
||||
Log("Decryptor::SendPlatformChallenge(service_id=%s)", aServiceId);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::EnableOutputProtection(uint32_t aDesiredProtectionMask)
|
||||
{
|
||||
Log("Decryptor::EnableOutputProtection(mask=0x%x)", aDesiredProtectionMask);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::QueryOutputProtectionStatus()
|
||||
{
|
||||
Log("Decryptor::QueryOutputProtectionStatus()");
|
||||
}
|
||||
|
||||
void
|
||||
WidevineDecryptor::OnDeferredInitializationDone(StreamType aStreamType,
|
||||
Status aDecoderStatus)
|
||||
{
|
||||
Log("Decryptor::OnDeferredInitializationDone()");
|
||||
}
|
||||
|
||||
FileIO*
|
||||
WidevineDecryptor::CreateFileIO(FileIOClient* aClient)
|
||||
{
|
||||
Log("Decryptor::CreateFileIO()");
|
||||
// Persistent storage not required or supported!
|
||||
MOZ_ASSERT(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,127 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef WidevineDecryptor_h_
|
||||
#define WidevineDecryptor_h_
|
||||
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "gmp-api/gmp-decryption.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "WidevineUtils.h"
|
||||
#include <map>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class WidevineDecryptor : public GMPDecryptor
|
||||
, public cdm::Host_8
|
||||
{
|
||||
public:
|
||||
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WidevineDecryptor)
|
||||
|
||||
WidevineDecryptor();
|
||||
|
||||
void SetCDM(RefPtr<CDMWrapper> aCDM);
|
||||
|
||||
// GMPDecryptor
|
||||
void Init(GMPDecryptorCallback* aCallback) override;
|
||||
|
||||
void CreateSession(uint32_t aCreateSessionToken,
|
||||
uint32_t aPromiseId,
|
||||
const char* aInitDataType,
|
||||
uint32_t aInitDataTypeSize,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize,
|
||||
GMPSessionType aSessionType) override;
|
||||
|
||||
void LoadSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength) override;
|
||||
|
||||
void UpdateSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength,
|
||||
const uint8_t* aResponse,
|
||||
uint32_t aResponseSize) override;
|
||||
|
||||
void CloseSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength) override;
|
||||
|
||||
void RemoveSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength) override;
|
||||
|
||||
void SetServerCertificate(uint32_t aPromiseId,
|
||||
const uint8_t* aServerCert,
|
||||
uint32_t aServerCertSize) override;
|
||||
|
||||
void Decrypt(GMPBuffer* aBuffer,
|
||||
GMPEncryptedBufferMetadata* aMetadata) override;
|
||||
|
||||
void DecryptingComplete() override;
|
||||
|
||||
|
||||
// cdm::Host_8
|
||||
cdm::Buffer* Allocate(uint32_t aCapacity) override;
|
||||
void SetTimer(int64_t aDelayMs, void* aContext) override;
|
||||
cdm::Time GetCurrentWallTime() override;
|
||||
void OnResolveNewSessionPromise(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdSize) override;
|
||||
void OnResolvePromise(uint32_t aPromiseId) override;
|
||||
void OnRejectPromise(uint32_t aPromiseId,
|
||||
cdm::Error aError,
|
||||
uint32_t aSystemCode,
|
||||
const char* aErrorMessage,
|
||||
uint32_t aErrorMessageSize) override;
|
||||
void OnSessionMessage(const char* aSessionId,
|
||||
uint32_t aSessionIdSize,
|
||||
cdm::MessageType aMessageType,
|
||||
const char* aMessage,
|
||||
uint32_t aMessageSize,
|
||||
const char* aLegacyDestinationUrl,
|
||||
uint32_t aLegacyDestinationUrlLength) override;
|
||||
void OnSessionKeysChange(const char* aSessionId,
|
||||
uint32_t aSessionIdSize,
|
||||
bool aHasAdditionalUsableKey,
|
||||
const cdm::KeyInformation* aKeysInfo,
|
||||
uint32_t aKeysInfoCount) override;
|
||||
void OnExpirationChange(const char* aSessionId,
|
||||
uint32_t aSessionIdSize,
|
||||
cdm::Time aNewExpiryTime) override;
|
||||
void OnSessionClosed(const char* aSessionId,
|
||||
uint32_t aSessionIdSize) override;
|
||||
void OnLegacySessionError(const char* aSessionId,
|
||||
uint32_t aSessionId_length,
|
||||
cdm::Error aError,
|
||||
uint32_t aSystemCode,
|
||||
const char* aErrorMessage,
|
||||
uint32_t aErrorMessageLength) override;
|
||||
void SendPlatformChallenge(const char* aServiceId,
|
||||
uint32_t aServiceIdSize,
|
||||
const char* aChallenge,
|
||||
uint32_t aChallengeSize) override;
|
||||
void EnableOutputProtection(uint32_t aDesiredProtectionMask) override;
|
||||
void QueryOutputProtectionStatus() override;
|
||||
void OnDeferredInitializationDone(cdm::StreamType aStreamType,
|
||||
cdm::Status aDecoderStatus) override;
|
||||
cdm::FileIO* CreateFileIO(cdm::FileIOClient* aClient) override;
|
||||
|
||||
GMPDecryptorCallback* Callback() const { return mCallback; }
|
||||
RefPtr<CDMWrapper> GetCDMWrapper() const { return mCDM; }
|
||||
private:
|
||||
~WidevineDecryptor();
|
||||
RefPtr<CDMWrapper> mCDM;
|
||||
cdm::ContentDecryptionModule_8* CDM() { return mCDM->GetCDM(); }
|
||||
|
||||
GMPDecryptorCallback* mCallback;
|
||||
std::map<uint32_t, uint32_t> mPromiseIdToNewSessionTokens;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // WidevineDecryptor_h_
|
|
@ -0,0 +1,79 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "WidevineUtils.h"
|
||||
|
||||
#include "gmp-api/gmp-errors.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#ifdef ENABLE_WIDEVINE_LOG
|
||||
void
|
||||
Log(const char* aFormat, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, aFormat);
|
||||
const size_t len = 1024;
|
||||
char buf[len];
|
||||
vsnprintf(buf, len, aFormat, ap);
|
||||
va_end(ap);
|
||||
if (getenv("GMP_LOG_FILE")) {
|
||||
FILE* f = fopen(getenv("GMP_LOG_FILE"), "a");
|
||||
if (f) {
|
||||
fprintf(f, "%s\n", buf);
|
||||
fflush(f);
|
||||
fclose(f);
|
||||
f = nullptr;
|
||||
}
|
||||
} else {
|
||||
printf("LOG: %s\n", buf);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_WIDEVINE_LOG
|
||||
|
||||
GMPErr
|
||||
ToGMPErr(cdm::Status aStatus)
|
||||
{
|
||||
switch (aStatus) {
|
||||
case cdm::kSuccess: return GMPNoErr;
|
||||
case cdm::kNeedMoreData: return GMPGenericErr;
|
||||
case cdm::kNoKey: return GMPNoKeyErr;
|
||||
case cdm::kSessionError: return GMPGenericErr;
|
||||
case cdm::kDecryptError: return GMPCryptoErr;
|
||||
case cdm::kDecodeError: return GMPDecodeErr;
|
||||
case cdm::kDeferredInitialization: return GMPGenericErr;
|
||||
default: return GMPGenericErr;
|
||||
}
|
||||
}
|
||||
|
||||
void InitInputBuffer(const GMPEncryptedBufferMetadata* aCrypto,
|
||||
int64_t aTimestamp,
|
||||
const uint8_t* aData,
|
||||
size_t aDataSize,
|
||||
cdm::InputBuffer &aInputBuffer,
|
||||
nsTArray<cdm::SubsampleEntry> &aSubsamples)
|
||||
{
|
||||
if (aCrypto) {
|
||||
aInputBuffer.key_id = aCrypto->KeyId();
|
||||
aInputBuffer.key_id_size = aCrypto->KeyIdSize();
|
||||
aInputBuffer.iv = aCrypto->IV();
|
||||
aInputBuffer.iv_size = aCrypto->IVSize();
|
||||
aInputBuffer.num_subsamples = aCrypto->NumSubsamples();
|
||||
aSubsamples.SetCapacity(aInputBuffer.num_subsamples);
|
||||
const uint16_t* clear = aCrypto->ClearBytes();
|
||||
const uint32_t* cipher = aCrypto->CipherBytes();
|
||||
for (size_t i = 0; i < aCrypto->NumSubsamples(); i++) {
|
||||
aSubsamples.AppendElement(cdm::SubsampleEntry(clear[i], cipher[i]));
|
||||
}
|
||||
}
|
||||
aInputBuffer.data = aData;
|
||||
aInputBuffer.data_size = aDataSize;
|
||||
aInputBuffer.subsamples = aSubsamples.Elements();
|
||||
aInputBuffer.timestamp = aTimestamp;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,71 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef WidevineUtils_h_
|
||||
#define WidevineUtils_h_
|
||||
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "gmp-api/gmp-decryption.h"
|
||||
#include "gmp-api/gmp-platform.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// Uncomment for logging...
|
||||
//#define ENABLE_WIDEVINE_LOG 1
|
||||
#ifdef ENABLE_WIDEVINE_LOG
|
||||
void
|
||||
Log(const char* aFormat, ...);
|
||||
#else
|
||||
#define Log(...)
|
||||
#endif // ENABLE_WIDEVINE_LOG
|
||||
|
||||
|
||||
#define ENSURE_TRUE(condition, rv) { \
|
||||
if (!(condition)) {\
|
||||
Log("ENSURE_TRUE FAILED %s:%d", __FILE__, __LINE__); \
|
||||
return rv; \
|
||||
} \
|
||||
} \
|
||||
|
||||
#define ENSURE_GMP_SUCCESS(err, rv) { \
|
||||
if (GMP_FAILED(err)) {\
|
||||
Log("ENSURE_GMP_SUCCESS FAILED %s:%d", __FILE__, __LINE__); \
|
||||
return rv; \
|
||||
} \
|
||||
} \
|
||||
|
||||
GMPErr
|
||||
ToGMPErr(cdm::Status aStatus);
|
||||
|
||||
class CDMWrapper {
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CDMWrapper)
|
||||
|
||||
CDMWrapper(cdm::ContentDecryptionModule_8* aCDM)
|
||||
: mCDM(aCDM)
|
||||
{}
|
||||
cdm::ContentDecryptionModule_8* GetCDM() const { return mCDM; }
|
||||
private:
|
||||
cdm::ContentDecryptionModule_8* mCDM;
|
||||
~CDMWrapper() {
|
||||
Log("CDMWrapper destroying CDM=%p", mCDM);
|
||||
mCDM->Destroy();
|
||||
mCDM = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
void InitInputBuffer(const GMPEncryptedBufferMetadata* aCrypto,
|
||||
int64_t aTimestamp,
|
||||
const uint8_t* aData,
|
||||
size_t aDataSize,
|
||||
cdm::InputBuffer &aInputBuffer,
|
||||
nsTArray<cdm::SubsampleEntry> &aSubsamples);
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // WidevineUtils_h_
|
|
@ -0,0 +1,243 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "WidevineVideoDecoder.h"
|
||||
|
||||
#include "mp4_demuxer/AnnexB.h"
|
||||
#include "WidevineUtils.h"
|
||||
#include "WidevineVideoFrame.h"
|
||||
|
||||
using namespace cdm;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
WidevineVideoDecoder::WidevineVideoDecoder(GMPVideoHost* aVideoHost,
|
||||
RefPtr<CDMWrapper> aCDM)
|
||||
: mVideoHost(aVideoHost)
|
||||
, mCDM(aCDM)
|
||||
, mExtraData(new MediaByteBuffer())
|
||||
, mSentInput(false)
|
||||
{
|
||||
Log("WidevineVideoDecoder created this=%p", this);
|
||||
|
||||
AddRef();
|
||||
}
|
||||
|
||||
WidevineVideoDecoder::~WidevineVideoDecoder()
|
||||
{
|
||||
Log("WidevineVideoDecoder destroyed this=%p", this);
|
||||
}
|
||||
|
||||
static
|
||||
VideoDecoderConfig::VideoCodecProfile
|
||||
ToCDMH264Profile(uint8_t aProfile)
|
||||
{
|
||||
switch (aProfile) {
|
||||
case 66: return VideoDecoderConfig::kH264ProfileBaseline;
|
||||
case 77: return VideoDecoderConfig::kH264ProfileMain;
|
||||
case 88: return VideoDecoderConfig::kH264ProfileExtended;
|
||||
case 100: return VideoDecoderConfig::kH264ProfileHigh;
|
||||
case 110: return VideoDecoderConfig::kH264ProfileHigh10;
|
||||
case 122: return VideoDecoderConfig::kH264ProfileHigh422;
|
||||
case 144: return VideoDecoderConfig::kH264ProfileHigh444Predictive;
|
||||
}
|
||||
return VideoDecoderConfig::kUnknownVideoCodecProfile;
|
||||
}
|
||||
|
||||
void
|
||||
WidevineVideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
|
||||
const uint8_t* aCodecSpecific,
|
||||
uint32_t aCodecSpecificLength,
|
||||
GMPVideoDecoderCallback* aCallback,
|
||||
int32_t aCoreCount)
|
||||
{
|
||||
mCallback = aCallback;
|
||||
VideoDecoderConfig config;
|
||||
config.codec = VideoDecoderConfig::kCodecH264; // TODO: others.
|
||||
const GMPVideoCodecH264* h264 = (const GMPVideoCodecH264*)(aCodecSpecific);
|
||||
config.profile = ToCDMH264Profile(h264->mAVCC.mProfile);
|
||||
config.format = kYv12;
|
||||
config.coded_size = Size(aCodecSettings.mWidth, aCodecSettings.mHeight);
|
||||
mExtraData->AppendElements(aCodecSpecific + 1, aCodecSpecificLength);
|
||||
config.extra_data = mExtraData->Elements();
|
||||
config.extra_data_size = mExtraData->Length();
|
||||
Status rv = CDM()->InitializeVideoDecoder(config);
|
||||
if (rv != kSuccess) {
|
||||
mCallback->Error(ToGMPErr(rv));
|
||||
return;
|
||||
}
|
||||
Log("WidevineVideoDecoder::InitDecode() rv=%d", rv);
|
||||
mAnnexB = mp4_demuxer::AnnexB::ConvertExtraDataToAnnexB(mExtraData);
|
||||
}
|
||||
|
||||
void
|
||||
WidevineVideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
|
||||
bool aMissingFrames,
|
||||
const uint8_t* aCodecSpecificInfo,
|
||||
uint32_t aCodecSpecificInfoLength,
|
||||
int64_t aRenderTimeMs)
|
||||
{
|
||||
// We may not get the same out of the CDM decoder as we put in, and there
|
||||
// may be some latency, i.e. we may need to input (say) 30 frames before
|
||||
// we receive output. So we need to store the durations of the frames input,
|
||||
// and retrieve them on output.
|
||||
mFrameDurations[aInputFrame->TimeStamp()] = aInputFrame->Duration();
|
||||
|
||||
mSentInput = true;
|
||||
InputBuffer sample;
|
||||
|
||||
RefPtr<MediaRawData> raw(new MediaRawData(aInputFrame->Buffer(), aInputFrame->Size()));
|
||||
raw->mExtraData = mExtraData;
|
||||
raw->mKeyframe = (aInputFrame->FrameType() == kGMPKeyFrame);
|
||||
// Convert input from AVCC, which GMPAPI passes in, to AnnexB, which
|
||||
// Chromium uses internally.
|
||||
mp4_demuxer::AnnexB::ConvertSampleToAnnexB(raw);
|
||||
|
||||
const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
|
||||
nsTArray<SubsampleEntry> subsamples;
|
||||
InitInputBuffer(crypto, aInputFrame->TimeStamp(), raw->Data(), raw->Size(), sample, subsamples);
|
||||
|
||||
// For keyframes, ConvertSampleToAnnexB will stick the AnnexB extra data
|
||||
// at the start of the input. So we need to account for that as clear data
|
||||
// in the subsamples.
|
||||
if (raw->mKeyframe && !subsamples.IsEmpty()) {
|
||||
subsamples[0].clear_bytes += mAnnexB->Length();
|
||||
}
|
||||
|
||||
WidevineVideoFrame frame;
|
||||
Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
|
||||
Log("WidevineVideoDecoder::Decode(timestamp=%lld) rv=%d", sample.timestamp, rv);
|
||||
|
||||
// Destroy frame, so that the shmem is now free to be used to return
|
||||
// output to the Gecko process.
|
||||
aInputFrame->Destroy();
|
||||
aInputFrame = nullptr;
|
||||
|
||||
if (rv == kSuccess) {
|
||||
if (!ReturnOutput(frame)) {
|
||||
Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
|
||||
mCallback->Error(GMPDecodeErr);
|
||||
return;
|
||||
}
|
||||
mCallback->InputDataExhausted();
|
||||
} else if (rv == kNeedMoreData) {
|
||||
mCallback->InputDataExhausted();
|
||||
} else {
|
||||
mCallback->Error(ToGMPErr(rv));
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
WidevineVideoDecoder::ReturnOutput(WidevineVideoFrame& aCDMFrame)
|
||||
{
|
||||
GMPVideoFrame* f = nullptr;
|
||||
auto err = mVideoHost->CreateFrame(kGMPI420VideoFrame, &f);
|
||||
if (GMP_FAILED(err) || !f) {
|
||||
Log("Failed to create i420 frame!\n");
|
||||
return false;
|
||||
}
|
||||
auto gmpFrame = static_cast<GMPVideoi420Frame*>(f);
|
||||
Size size = aCDMFrame.Size();
|
||||
const int32_t yStride = aCDMFrame.Stride(VideoFrame::kYPlane);
|
||||
const int32_t uStride = aCDMFrame.Stride(VideoFrame::kUPlane);
|
||||
const int32_t vStride = aCDMFrame.Stride(VideoFrame::kVPlane);
|
||||
const int32_t halfHeight = size.height / 2;
|
||||
err = gmpFrame->CreateEmptyFrame(size.width,
|
||||
size.height,
|
||||
yStride,
|
||||
uStride,
|
||||
vStride);
|
||||
ENSURE_GMP_SUCCESS(err, false);
|
||||
|
||||
err = gmpFrame->SetWidth(size.width);
|
||||
ENSURE_GMP_SUCCESS(err, false);
|
||||
|
||||
err = gmpFrame->SetHeight(size.height);
|
||||
ENSURE_GMP_SUCCESS(err, false);
|
||||
|
||||
Buffer* buffer = aCDMFrame.FrameBuffer();
|
||||
uint8_t* outBuffer = gmpFrame->Buffer(kGMPYPlane);
|
||||
ENSURE_TRUE(outBuffer != nullptr, false);
|
||||
MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPYPlane) >= yStride*size.height);
|
||||
memcpy(outBuffer,
|
||||
buffer->Data() + aCDMFrame.PlaneOffset(VideoFrame::kYPlane),
|
||||
yStride * size.height);
|
||||
|
||||
outBuffer = gmpFrame->Buffer(kGMPUPlane);
|
||||
ENSURE_TRUE(outBuffer != nullptr, false);
|
||||
MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPUPlane) >= uStride * halfHeight);
|
||||
memcpy(outBuffer,
|
||||
buffer->Data() + aCDMFrame.PlaneOffset(VideoFrame::kUPlane),
|
||||
uStride * halfHeight);
|
||||
|
||||
outBuffer = gmpFrame->Buffer(kGMPVPlane);
|
||||
ENSURE_TRUE(outBuffer != nullptr, false);
|
||||
MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPVPlane) >= vStride * halfHeight);
|
||||
memcpy(outBuffer,
|
||||
buffer->Data() + aCDMFrame.PlaneOffset(VideoFrame::kVPlane),
|
||||
vStride * halfHeight);
|
||||
|
||||
gmpFrame->SetTimestamp(aCDMFrame.Timestamp());
|
||||
|
||||
auto d = mFrameDurations.find(aCDMFrame.Timestamp());
|
||||
if (d != mFrameDurations.end()) {
|
||||
gmpFrame->SetDuration(d->second);
|
||||
mFrameDurations.erase(d);
|
||||
}
|
||||
|
||||
mCallback->Decoded(gmpFrame);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
WidevineVideoDecoder::Reset()
|
||||
{
|
||||
Log("WidevineVideoDecoder::Reset() mSentInput=%d", mSentInput);
|
||||
if (mSentInput) {
|
||||
CDM()->ResetDecoder(kStreamTypeVideo);
|
||||
}
|
||||
mFrameDurations.clear();
|
||||
mCallback->ResetComplete();
|
||||
mSentInput = false;
|
||||
}
|
||||
|
||||
void
|
||||
WidevineVideoDecoder::Drain()
|
||||
{
|
||||
Log("WidevineVideoDecoder::Drain()");
|
||||
|
||||
Status rv = kSuccess;
|
||||
while (rv == kSuccess) {
|
||||
WidevineVideoFrame frame;
|
||||
InputBuffer sample;
|
||||
Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
|
||||
Log("WidevineVideoDecoder::Drain(); DecryptAndDecodeFrame() rv=%d", rv);
|
||||
if (frame.Format() == kUnknownVideoFormat) {
|
||||
break;
|
||||
}
|
||||
if (rv == kSuccess) {
|
||||
if (!ReturnOutput(frame)) {
|
||||
Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CDM()->ResetDecoder(kStreamTypeVideo);
|
||||
mCallback->DrainComplete();
|
||||
}
|
||||
|
||||
void
|
||||
WidevineVideoDecoder::DecodingComplete()
|
||||
{
|
||||
Log("WidevineVideoDecoder::DecodingComplete()");
|
||||
if (mCDM) {
|
||||
CDM()->DeinitializeDecoder(kStreamTypeVideo);
|
||||
mCDM = nullptr;
|
||||
}
|
||||
Release();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,62 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef WidevineVideoDecoder_h_
|
||||
#define WidevineVideoDecoder_h_
|
||||
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "gmp-api/gmp-video-decode.h"
|
||||
#include "gmp-api/gmp-video-host.h"
|
||||
#include "MediaData.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsTArray.h"
|
||||
#include "WidevineDecryptor.h"
|
||||
#include "WidevineVideoFrame.h"
|
||||
#include <map>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class WidevineVideoDecoder : public GMPVideoDecoder {
|
||||
public:
|
||||
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WidevineVideoDecoder)
|
||||
|
||||
WidevineVideoDecoder(GMPVideoHost* aVideoHost,
|
||||
RefPtr<CDMWrapper> aCDM);
|
||||
void InitDecode(const GMPVideoCodec& aCodecSettings,
|
||||
const uint8_t* aCodecSpecific,
|
||||
uint32_t aCodecSpecificLength,
|
||||
GMPVideoDecoderCallback* aCallback,
|
||||
int32_t aCoreCount) override;
|
||||
void Decode(GMPVideoEncodedFrame* aInputFrame,
|
||||
bool aMissingFrames,
|
||||
const uint8_t* aCodecSpecificInfo,
|
||||
uint32_t aCodecSpecificInfoLength,
|
||||
int64_t aRenderTimeMs = -1) override;
|
||||
void Reset() override;
|
||||
void Drain() override;
|
||||
void DecodingComplete() override;
|
||||
|
||||
private:
|
||||
|
||||
~WidevineVideoDecoder();
|
||||
|
||||
cdm::ContentDecryptionModule_8* CDM() { return mCDM->GetCDM(); }
|
||||
|
||||
bool ReturnOutput(WidevineVideoFrame& aFrame);
|
||||
|
||||
GMPVideoHost* mVideoHost;
|
||||
RefPtr<CDMWrapper> mCDM;
|
||||
RefPtr<MediaByteBuffer> mExtraData;
|
||||
RefPtr<MediaByteBuffer> mAnnexB;
|
||||
GMPVideoDecoderCallback* mCallback;
|
||||
std::map<uint64_t, uint64_t> mFrameDurations;
|
||||
bool mSentInput;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // WidevineVideoDecoder_h_
|
|
@ -0,0 +1,113 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "WidevineVideoFrame.h"
|
||||
|
||||
#include "WidevineUtils.h"
|
||||
|
||||
using namespace cdm;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
WidevineVideoFrame::WidevineVideoFrame()
|
||||
: mFormat(kUnknownVideoFormat)
|
||||
, mSize(0,0)
|
||||
, mBuffer(nullptr)
|
||||
, mTimestamp(0)
|
||||
{
|
||||
Log("WidevineVideoFrame::WidevineVideoFrame() this=%p", this);
|
||||
memset(mPlaneOffsets, 0, sizeof(mPlaneOffsets));
|
||||
memset(mPlaneStrides, 0, sizeof(mPlaneStrides));
|
||||
}
|
||||
|
||||
WidevineVideoFrame::~WidevineVideoFrame()
|
||||
{
|
||||
if (mBuffer) {
|
||||
mBuffer->Destroy();
|
||||
mBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WidevineVideoFrame::SetFormat(cdm::VideoFormat aFormat)
|
||||
{
|
||||
Log("WidevineVideoFrame::SetFormat(%d) this=%p", aFormat, this);
|
||||
mFormat = aFormat;
|
||||
}
|
||||
|
||||
cdm::VideoFormat
|
||||
WidevineVideoFrame::Format() const
|
||||
{
|
||||
return mFormat;
|
||||
}
|
||||
|
||||
void
|
||||
WidevineVideoFrame::SetSize(cdm::Size aSize)
|
||||
{
|
||||
Log("WidevineVideoFrame::SetSize(%d,%d) this=%p", aSize.width, aSize.height, this);
|
||||
mSize.width = aSize.width;
|
||||
mSize.height = aSize.height;
|
||||
}
|
||||
|
||||
cdm::Size
|
||||
WidevineVideoFrame::Size() const
|
||||
{
|
||||
return mSize;
|
||||
}
|
||||
|
||||
void
|
||||
WidevineVideoFrame::SetFrameBuffer(cdm::Buffer* aFrameBuffer)
|
||||
{
|
||||
Log("WidevineVideoFrame::SetFrameBuffer(%p) this=%p", aFrameBuffer, this);
|
||||
MOZ_ASSERT(!mBuffer);
|
||||
mBuffer = aFrameBuffer;
|
||||
}
|
||||
|
||||
cdm::Buffer*
|
||||
WidevineVideoFrame::FrameBuffer()
|
||||
{
|
||||
return mBuffer;
|
||||
}
|
||||
|
||||
void
|
||||
WidevineVideoFrame::SetPlaneOffset(cdm::VideoFrame::VideoPlane aPlane, uint32_t aOffset)
|
||||
{
|
||||
Log("WidevineVideoFrame::SetPlaneOffset(%d, %d) this=%p", aPlane, aOffset, this);
|
||||
mPlaneOffsets[aPlane] = aOffset;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
WidevineVideoFrame::PlaneOffset(cdm::VideoFrame::VideoPlane aPlane)
|
||||
{
|
||||
return mPlaneOffsets[aPlane];
|
||||
}
|
||||
|
||||
void
|
||||
WidevineVideoFrame::SetStride(cdm::VideoFrame::VideoPlane aPlane, uint32_t aStride)
|
||||
{
|
||||
Log("WidevineVideoFrame::SetStride(%d, %d) this=%p", aPlane, aStride, this);
|
||||
mPlaneStrides[aPlane] = aStride;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
WidevineVideoFrame::Stride(cdm::VideoFrame::VideoPlane aPlane)
|
||||
{
|
||||
return mPlaneStrides[aPlane];
|
||||
}
|
||||
|
||||
void
|
||||
WidevineVideoFrame::SetTimestamp(int64_t timestamp)
|
||||
{
|
||||
Log("WidevineVideoFrame::SetTimestamp(%lld) this=%p", timestamp, this);
|
||||
mTimestamp = timestamp;
|
||||
}
|
||||
|
||||
int64_t
|
||||
WidevineVideoFrame::Timestamp() const
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,49 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef WidevineVideoFrame_h_
|
||||
#define WidevineVideoFrame_h_
|
||||
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include <vector>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class WidevineVideoFrame : public cdm::VideoFrame {
|
||||
public:
|
||||
WidevineVideoFrame();
|
||||
~WidevineVideoFrame();
|
||||
|
||||
void SetFormat(cdm::VideoFormat aFormat) override;
|
||||
cdm::VideoFormat Format() const override;
|
||||
|
||||
void SetSize(cdm::Size aSize) override;
|
||||
cdm::Size Size() const override;
|
||||
|
||||
void SetFrameBuffer(cdm::Buffer* aFrameBuffer) override;
|
||||
cdm::Buffer* FrameBuffer() override;
|
||||
|
||||
void SetPlaneOffset(cdm::VideoFrame::VideoPlane aPlane, uint32_t aOffset) override;
|
||||
uint32_t PlaneOffset(cdm::VideoFrame::VideoPlane aPlane) override;
|
||||
|
||||
void SetStride(cdm::VideoFrame::VideoPlane aPlane, uint32_t aStride) override;
|
||||
uint32_t Stride(cdm::VideoFrame::VideoPlane aPlane) override;
|
||||
|
||||
void SetTimestamp(int64_t aTimestamp) override;
|
||||
int64_t Timestamp() const override;
|
||||
|
||||
protected:
|
||||
cdm::VideoFormat mFormat;
|
||||
cdm::Size mSize;
|
||||
cdm::Buffer* mBuffer;
|
||||
uint32_t mPlaneOffsets[kMaxPlanes];
|
||||
uint32_t mPlaneStrides[kMaxPlanes];
|
||||
int64_t mTimestamp;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,19 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
SOURCES += [
|
||||
'WidevineAdapter.cpp',
|
||||
'WidevineDecryptor.cpp',
|
||||
'WidevineUtils.cpp',
|
||||
'WidevineVideoDecoder.cpp',
|
||||
'WidevineVideoFrame.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/media/gmp',
|
||||
]
|
|
@ -29,6 +29,7 @@ public:
|
|||
private:
|
||||
void MarkFinished()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mFinished = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -228,14 +228,29 @@ void
|
|||
GMPRemoveTest::Setup()
|
||||
{
|
||||
GeneratePlugin();
|
||||
EXPECT_OK(GetServiceParent()->RemovePluginDirectory(mOriginalPath));
|
||||
GetService()->GetThread(getter_AddRefs(mGMPThread));
|
||||
|
||||
GetServiceParent()->AddPluginDirectory(mTmpPath);
|
||||
// Spin the event loop until the GMP service has had a chance to complete
|
||||
// adding GMPs from MOZ_GMP_PATH. Otherwise, the RemovePluginDirectory()
|
||||
// below may complete before we're finished adding GMPs from MOZ_GMP_PATH,
|
||||
// and we'll end up not removing the GMP, and the test will fail.
|
||||
RefPtr<AbstractThread> thread(GetServiceParent()->GetAbstractGMPThread());
|
||||
GMPTestMonitor* mon = &mTestMonitor;
|
||||
GetServiceParent()->EnsureInitialized()->Then(thread, __func__,
|
||||
[mon]() { mon->SetFinished(); },
|
||||
[mon]() { mon->SetFinished(); }
|
||||
);
|
||||
mTestMonitor.AwaitFinished();
|
||||
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
obs->AddObserver(this, GMP_DELETED_TOPIC, false /* strong ref */);
|
||||
EXPECT_OK(GetServiceParent()->RemovePluginDirectory(mOriginalPath));
|
||||
|
||||
GetService()->GetThread(getter_AddRefs(mGMPThread));
|
||||
GetServiceParent()->AsyncAddPluginDirectory(mTmpPath)->Then(thread, __func__,
|
||||
[mon]() { mon->SetFinished(); },
|
||||
[mon]() { mon->SetFinished(); }
|
||||
);
|
||||
mTestMonitor.AwaitFinished();
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче