зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to central, a=merge
This commit is contained in:
Коммит
a1046f5db1
|
@ -29,7 +29,7 @@ class JetpackRunner(MozbuildObject):
|
||||||
@CommandProvider
|
@CommandProvider
|
||||||
class MachCommands(MachCommandBase):
|
class MachCommands(MachCommandBase):
|
||||||
@Command('jetpack-test', category='testing',
|
@Command('jetpack-test', category='testing',
|
||||||
description='Runs the jetpack test suite (Add-on SDK).')
|
description='Run the jetpack test suite (Add-on SDK).')
|
||||||
def run_jetpack_test(self, **params):
|
def run_jetpack_test(self, **params):
|
||||||
# We should probably have a utility function to ensure the tree is
|
# We should probably have a utility function to ensure the tree is
|
||||||
# ready to run tests. Until then, we just create the state dir (in
|
# ready to run tests. Until then, we just create the state dir (in
|
||||||
|
|
|
@ -1022,14 +1022,9 @@ chatbox:-moz-full-screen-ancestor > .chat-titlebar {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contentSelectDropdown-optgroup {
|
/* Indent options in optgroups */
|
||||||
font-weight: bold;
|
.contentSelectDropdown-ingroup .menu-iconic-text {
|
||||||
/* color: menutext used to overwrite the disabled color */
|
-moz-padding-start: 2em;
|
||||||
color: menutext;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentSelectDropdown-ingroup {
|
|
||||||
-moz-margin-start: 2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Give this menupopup an arrow panel styling */
|
/* Give this menupopup an arrow panel styling */
|
||||||
|
|
|
@ -380,6 +380,8 @@ skip-if = e10s
|
||||||
support-files =
|
support-files =
|
||||||
searchSuggestionUI.html
|
searchSuggestionUI.html
|
||||||
searchSuggestionUI.js
|
searchSuggestionUI.js
|
||||||
|
[browser_selectpopup.js]
|
||||||
|
run-if = e10s
|
||||||
[browser_selectTabAtIndex.js]
|
[browser_selectTabAtIndex.js]
|
||||||
[browser_ssl_error_reports.js]
|
[browser_ssl_error_reports.js]
|
||||||
[browser_star_hsts.js]
|
[browser_star_hsts.js]
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
// This test checks that a <select> with an <optgroup> opens and can be navigated
|
||||||
|
// in a child process. This is different than single-process as a <menulist> is used
|
||||||
|
// to implement the dropdown list.
|
||||||
|
|
||||||
|
const PAGECONTENT =
|
||||||
|
"<html><body onload='document.body.firstChild.focus()'><select>" +
|
||||||
|
" <optgroup label='First Group'>" +
|
||||||
|
" <option value=One>One" +
|
||||||
|
" <option value=Two>Two" +
|
||||||
|
" </optgroup>" +
|
||||||
|
" <option value=Three>Three" +
|
||||||
|
" <optgroup label='Second Group' disabled='true'>" +
|
||||||
|
" <option value=Four>Four" +
|
||||||
|
" <option value=Five>Five" +
|
||||||
|
" </optgroup>" +
|
||||||
|
" <option value=Six disabled='true'>Six" +
|
||||||
|
" <optgroup label='Third Group'>" +
|
||||||
|
" <option value=Seven>Seven" +
|
||||||
|
" <option value=Eight>Eight" +
|
||||||
|
" </optgroup>" +
|
||||||
|
"</body></html>";
|
||||||
|
|
||||||
|
function openSelectPopup(selectPopup)
|
||||||
|
{
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
selectPopup.addEventListener("popupshown", function popupListener(event) {
|
||||||
|
selectPopup.removeEventListener("popupshown", popupListener, false)
|
||||||
|
resolve();
|
||||||
|
}, false);
|
||||||
|
setTimeout(() => EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" }), 1500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideSelectPopup(selectPopup)
|
||||||
|
{
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
selectPopup.addEventListener("popuphidden", function popupListener(event) {
|
||||||
|
selectPopup.removeEventListener("popuphidden", popupListener, false)
|
||||||
|
resolve();
|
||||||
|
}, false);
|
||||||
|
EventUtils.synthesizeKey("KEY_Enter", { code: "Enter" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(function*() {
|
||||||
|
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||||
|
let browser = gBrowser.getBrowserForTab(tab);
|
||||||
|
yield promiseTabLoadEvent(tab, "data:text/html," + escape(PAGECONTENT));
|
||||||
|
|
||||||
|
yield SimpleTest.promiseFocus(browser.contentWindow);
|
||||||
|
|
||||||
|
let menulist = document.getElementById("ContentSelectDropdown");
|
||||||
|
let selectPopup = menulist.menupopup;
|
||||||
|
|
||||||
|
yield openSelectPopup(selectPopup);
|
||||||
|
|
||||||
|
is(menulist.selectedIndex, 1, "Initial selection");
|
||||||
|
is(selectPopup.firstChild.localName, "menucaption", "optgroup is caption");
|
||||||
|
is(selectPopup.firstChild.getAttribute("label"), "First Group", "optgroup label");
|
||||||
|
is(selectPopup.childNodes[1].localName, "menuitem", "option is menuitem");
|
||||||
|
is(selectPopup.childNodes[1].getAttribute("label"), "One", "option label");
|
||||||
|
|
||||||
|
EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
|
||||||
|
is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(2), "Select item 2");
|
||||||
|
|
||||||
|
EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
|
||||||
|
is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3");
|
||||||
|
|
||||||
|
EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
|
||||||
|
|
||||||
|
// On Windows, one can navigate on disabled menuitems
|
||||||
|
let expectedIndex = (navigator.platform.indexOf("Win") >= 0) ? 5 : 9;
|
||||||
|
|
||||||
|
is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(expectedIndex),
|
||||||
|
"Skip optgroup header and disabled items select item 7");
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
is(menulist.getItemAtIndex(i).disabled, i >= 4 && i <= 7, "item " + i + " disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
EventUtils.synthesizeKey("KEY_ArrowUp", { code: "ArrowUp" });
|
||||||
|
is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3 again");
|
||||||
|
|
||||||
|
yield hideSelectPopup(selectPopup);
|
||||||
|
|
||||||
|
is(menulist.selectedIndex, 3, "Item 3 still selected");
|
||||||
|
|
||||||
|
gBrowser.removeCurrentTab();
|
||||||
|
});
|
||||||
|
|
|
@ -355,8 +355,7 @@ nsMacShellService::OpenApplication(int32_t aApplication)
|
||||||
if (!exists)
|
if (!exists)
|
||||||
return NS_ERROR_FILE_NOT_FOUND;
|
return NS_ERROR_FILE_NOT_FOUND;
|
||||||
return lf->Launch();
|
return lf->Launch();
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case nsIMacShellService::APPLICATION_DESKTOP:
|
case nsIMacShellService::APPLICATION_DESKTOP:
|
||||||
{
|
{
|
||||||
nsCOMPtr<nsIFile> lf;
|
nsCOMPtr<nsIFile> lf;
|
||||||
|
@ -367,8 +366,7 @@ nsMacShellService::OpenApplication(int32_t aApplication)
|
||||||
if (!exists)
|
if (!exists)
|
||||||
return NS_ERROR_FILE_NOT_FOUND;
|
return NS_ERROR_FILE_NOT_FOUND;
|
||||||
return lf->Launch();
|
return lf->Launch();
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appURL && err == noErr) {
|
if (appURL && err == noErr) {
|
||||||
|
|
|
@ -25,7 +25,7 @@ let testData = [
|
||||||
["VK_TAB", {shiftKey: true}, "display", -1, 0],
|
["VK_TAB", {shiftKey: true}, "display", -1, 0],
|
||||||
["VK_BACK_SPACE", {}, "", -1, 0],
|
["VK_BACK_SPACE", {}, "", -1, 0],
|
||||||
["c", {}, "caption-side", 0, 10],
|
["c", {}, "caption-side", 0, 10],
|
||||||
["o", {}, "color", 0, 6],
|
["o", {}, "color", 0, 7],
|
||||||
["VK_TAB", {}, "none", -1, 0],
|
["VK_TAB", {}, "none", -1, 0],
|
||||||
["r", {}, "rebeccapurple", 0, 6],
|
["r", {}, "rebeccapurple", 0, 6],
|
||||||
["VK_DOWN", {}, "red", 1, 6],
|
["VK_DOWN", {}, "red", 1, 6],
|
||||||
|
|
36
configure.in
36
configure.in
|
@ -5166,20 +5166,6 @@ else
|
||||||
AC_SUBST(MOZ_SAMPLE_TYPE_FLOAT32)
|
AC_SUBST(MOZ_SAMPLE_TYPE_FLOAT32)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
dnl ========================================================
|
|
||||||
dnl = Disable Speech API test backend
|
|
||||||
dnl ========================================================
|
|
||||||
MOZ_ARG_DISABLE_BOOL(webspeechtestbackend,
|
|
||||||
[ --disable-webspeechtestbackend Disable support for HTML Speech API Test Backend],
|
|
||||||
MOZ_WEBSPEECH_TEST_BACKEND=,
|
|
||||||
MOZ_WEBSPEECH_TEST_BACKEND=1)
|
|
||||||
|
|
||||||
if test -n "$MOZ_WEBSPEECH_TEST_BACKEND"; then
|
|
||||||
AC_DEFINE(MOZ_WEBSPEECH_TEST_BACKEND)
|
|
||||||
fi
|
|
||||||
|
|
||||||
AC_SUBST(MOZ_WEBSPEECH_TEST_BACKEND)
|
|
||||||
|
|
||||||
dnl ========================================================
|
dnl ========================================================
|
||||||
dnl = Disable Speech API pocketsphinx backend
|
dnl = Disable Speech API pocketsphinx backend
|
||||||
dnl ========================================================
|
dnl ========================================================
|
||||||
|
@ -5208,6 +5194,24 @@ fi
|
||||||
|
|
||||||
AC_SUBST(MOZ_WEBSPEECH)
|
AC_SUBST(MOZ_WEBSPEECH)
|
||||||
|
|
||||||
|
dnl ========================================================
|
||||||
|
dnl = Disable Speech API test backend
|
||||||
|
dnl ========================================================
|
||||||
|
MOZ_ARG_DISABLE_BOOL(webspeechtestbackend,
|
||||||
|
[ --disable-webspeechtestbackend Disable support for HTML Speech API Test Backend],
|
||||||
|
MOZ_WEBSPEECH_TEST_BACKEND=,
|
||||||
|
MOZ_WEBSPEECH_TEST_BACKEND=1)
|
||||||
|
|
||||||
|
if test -z "$MOZ_WEBSPEECH"; then
|
||||||
|
MOZ_WEBSPEECH_TEST_BACKEND=
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -n "$MOZ_WEBSPEECH_TEST_BACKEND"; then
|
||||||
|
AC_DEFINE(MOZ_WEBSPEECH_TEST_BACKEND)
|
||||||
|
fi
|
||||||
|
|
||||||
|
AC_SUBST(MOZ_WEBSPEECH_TEST_BACKEND)
|
||||||
|
|
||||||
dnl ========================================================
|
dnl ========================================================
|
||||||
dnl = Disable Speech API models
|
dnl = Disable Speech API models
|
||||||
dnl ========================================================
|
dnl ========================================================
|
||||||
|
@ -5216,6 +5220,10 @@ MOZ_ARG_DISABLE_BOOL(webspeechmodels,
|
||||||
MOZ_WEBSPEECH_MODELS=,
|
MOZ_WEBSPEECH_MODELS=,
|
||||||
MOZ_WEBSPEECH_MODELS=1)
|
MOZ_WEBSPEECH_MODELS=1)
|
||||||
|
|
||||||
|
if test -z "$MOZ_WEBSPEECH"; then
|
||||||
|
MOZ_WEBSPEECH_MODELS=
|
||||||
|
fi
|
||||||
|
|
||||||
if test -n "$MOZ_WEBSPEECH_MODELS"; then
|
if test -n "$MOZ_WEBSPEECH_MODELS"; then
|
||||||
AC_DEFINE(MOZ_WEBSPEECH_MODELS)
|
AC_DEFINE(MOZ_WEBSPEECH_MODELS)
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -725,7 +725,7 @@ DOMException::Create(nsresult aRv)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* static */already_AddRefed<DOMException>
|
/* static */already_AddRefed<DOMException>
|
||||||
DOMException::Create(nsresult aRv, const nsCString& aMessage)
|
DOMException::Create(nsresult aRv, const nsACString& aMessage)
|
||||||
{
|
{
|
||||||
nsCString name;
|
nsCString name;
|
||||||
nsCString message;
|
nsCString message;
|
||||||
|
|
|
@ -158,7 +158,7 @@ public:
|
||||||
Create(nsresult aRv);
|
Create(nsresult aRv);
|
||||||
|
|
||||||
static already_AddRefed<DOMException>
|
static already_AddRefed<DOMException>
|
||||||
Create(nsresult aRv, const nsCString& aMessage);
|
Create(nsresult aRv, const nsACString& aMessage);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
|
|
@ -34,15 +34,12 @@ JSObject* GetDefaultScopeFromJSContext(JSContext *cx);
|
||||||
|
|
||||||
// A factory function for turning a JS::Value argv into an nsIArray
|
// A factory function for turning a JS::Value argv into an nsIArray
|
||||||
// but also supports an effecient way of extracting the original argv.
|
// but also supports an effecient way of extracting the original argv.
|
||||||
// Bug 312003 describes why this must be "void *", but argv will be cast to
|
|
||||||
// JS::Value* and the args are found at:
|
|
||||||
// ((JS::Value*)aArgv)[0], ..., ((JS::Value*)aArgv)[aArgc - 1]
|
|
||||||
// The resulting object will take a copy of the array, and ensure each
|
// The resulting object will take a copy of the array, and ensure each
|
||||||
// element is rooted.
|
// element is rooted.
|
||||||
// Optionally, aArgv may be nullptr, in which case the array is allocated and
|
// Optionally, aArgv may be nullptr, in which case the array is allocated and
|
||||||
// rooted, but all items remain nullptr. This presumably means the caller
|
// rooted, but all items remain nullptr. This presumably means the caller
|
||||||
// will then QI us for nsIJSArgArray, and set our array elements.
|
// will then QI us for nsIJSArgArray, and set our array elements.
|
||||||
nsresult NS_CreateJSArgv(JSContext *aContext, uint32_t aArgc, void *aArgv,
|
nsresult NS_CreateJSArgv(JSContext *aContext, uint32_t aArgc,
|
||||||
nsIJSArgArray **aArray);
|
const JS::Value* aArgv, nsIJSArgArray **aArray);
|
||||||
|
|
||||||
#endif // nsDOMJSUtils_h__
|
#endif // nsDOMJSUtils_h__
|
||||||
|
|
|
@ -294,37 +294,6 @@ nsFrameLoader::ReallyStartLoading()
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DelayedStartLoadingRunnable : public nsRunnable
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit DelayedStartLoadingRunnable(nsFrameLoader* aFrameLoader)
|
|
||||||
: mFrameLoader(aFrameLoader)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHOD Run()
|
|
||||||
{
|
|
||||||
// Retry the request.
|
|
||||||
mFrameLoader->ReallyStartLoading();
|
|
||||||
|
|
||||||
// We delayed nsFrameLoader::ReallyStartLoading() after the child process is
|
|
||||||
// ready and might not be able to notify the remote browser in
|
|
||||||
// UpdatePositionAndSize() when reflow finished. Retrigger reflow.
|
|
||||||
nsIFrame* frame = mFrameLoader->GetPrimaryFrameOfOwningContent();
|
|
||||||
if (!frame) {
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
frame->InvalidateFrame();
|
|
||||||
frame->PresContext()->PresShell()->
|
|
||||||
FrameNeedsReflow(frame, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
|
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
nsRefPtr<nsFrameLoader> mFrameLoader;
|
|
||||||
};
|
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
nsFrameLoader::ReallyStartLoadingInternal()
|
nsFrameLoader::ReallyStartLoadingInternal()
|
||||||
{
|
{
|
||||||
|
|
|
@ -7844,7 +7844,7 @@ nsGlobalWindow::OpenDialog(JSContext* aCx, const nsAString& aUrl,
|
||||||
|
|
||||||
nsCOMPtr<nsIJSArgArray> argvArray;
|
nsCOMPtr<nsIJSArgArray> argvArray;
|
||||||
aError = NS_CreateJSArgv(aCx, aExtraArgument.Length(),
|
aError = NS_CreateJSArgv(aCx, aExtraArgument.Length(),
|
||||||
const_cast<JS::Value*>(aExtraArgument.Elements()),
|
aExtraArgument.Elements(),
|
||||||
getter_AddRefs(argvArray));
|
getter_AddRefs(argvArray));
|
||||||
if (aError.Failed()) {
|
if (aError.Failed()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
|
@ -192,10 +192,6 @@ protected:
|
||||||
|
|
||||||
void ClearBrokenState() { mBroken = false; }
|
void ClearBrokenState() { mBroken = false; }
|
||||||
|
|
||||||
// Sets blocking state only if the desired state is different from the
|
|
||||||
// current one. See the comment for mBlockingOnload for more information.
|
|
||||||
void SetBlockingOnload(bool aBlocking);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the CORS mode that will be used for all future image loads. The
|
* Returns the CORS mode that will be used for all future image loads. The
|
||||||
* default implementation returns CORS_NONE unconditionally.
|
* default implementation returns CORS_NONE unconditionally.
|
||||||
|
|
|
@ -2868,7 +2868,7 @@ mozilla::dom::ShutdownJSEnvironment()
|
||||||
// on-the-fly.
|
// on-the-fly.
|
||||||
class nsJSArgArray final : public nsIJSArgArray {
|
class nsJSArgArray final : public nsIJSArgArray {
|
||||||
public:
|
public:
|
||||||
nsJSArgArray(JSContext *aContext, uint32_t argc, JS::Value *argv,
|
nsJSArgArray(JSContext *aContext, uint32_t argc, const JS::Value* argv,
|
||||||
nsresult *prv);
|
nsresult *prv);
|
||||||
|
|
||||||
// nsISupports
|
// nsISupports
|
||||||
|
@ -2891,11 +2891,11 @@ protected:
|
||||||
uint32_t mArgc;
|
uint32_t mArgc;
|
||||||
};
|
};
|
||||||
|
|
||||||
nsJSArgArray::nsJSArgArray(JSContext *aContext, uint32_t argc, JS::Value *argv,
|
nsJSArgArray::nsJSArgArray(JSContext *aContext, uint32_t argc,
|
||||||
nsresult *prv) :
|
const JS::Value* argv, nsresult *prv)
|
||||||
mContext(aContext),
|
: mContext(aContext)
|
||||||
mArgv(nullptr),
|
, mArgv(nullptr)
|
||||||
mArgc(argc)
|
, mArgc(argc)
|
||||||
{
|
{
|
||||||
// copy the array - we don't know its lifetime, and ours is tied to xpcom
|
// copy the array - we don't know its lifetime, and ours is tied to xpcom
|
||||||
// refcounting.
|
// refcounting.
|
||||||
|
@ -3010,12 +3010,11 @@ NS_IMETHODIMP nsJSArgArray::Enumerate(nsISimpleEnumerator **_retval)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The factory function
|
// The factory function
|
||||||
nsresult NS_CreateJSArgv(JSContext *aContext, uint32_t argc, void *argv,
|
nsresult NS_CreateJSArgv(JSContext *aContext, uint32_t argc,
|
||||||
nsIJSArgArray **aArray)
|
const JS::Value* argv, nsIJSArgArray **aArray)
|
||||||
{
|
{
|
||||||
nsresult rv;
|
nsresult rv;
|
||||||
nsCOMPtr<nsIJSArgArray> ret = new nsJSArgArray(aContext, argc,
|
nsCOMPtr<nsIJSArgArray> ret = new nsJSArgArray(aContext, argc, argv, &rv);
|
||||||
static_cast<JS::Value *>(argv), &rv);
|
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,7 @@ NS_INTERFACE_MAP_END
|
||||||
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsMimeTypeArray,
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsMimeTypeArray,
|
||||||
mWindow,
|
mWindow,
|
||||||
mMimeTypes,
|
mMimeTypes)
|
||||||
mHiddenMimeTypes)
|
|
||||||
|
|
||||||
nsMimeTypeArray::nsMimeTypeArray(nsPIDOMWindow* aWindow)
|
nsMimeTypeArray::nsMimeTypeArray(nsPIDOMWindow* aWindow)
|
||||||
: mWindow(aWindow)
|
: mWindow(aWindow)
|
||||||
|
@ -49,7 +48,6 @@ void
|
||||||
nsMimeTypeArray::Refresh()
|
nsMimeTypeArray::Refresh()
|
||||||
{
|
{
|
||||||
mMimeTypes.Clear();
|
mMimeTypes.Clear();
|
||||||
mHiddenMimeTypes.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nsPIDOMWindow*
|
nsPIDOMWindow*
|
||||||
|
@ -114,10 +112,6 @@ nsMimeTypeArray::NamedGetter(const nsAString& aName, bool &aFound)
|
||||||
ToLowerCase(lowerName);
|
ToLowerCase(lowerName);
|
||||||
|
|
||||||
nsMimeType* mimeType = FindMimeType(mMimeTypes, lowerName);
|
nsMimeType* mimeType = FindMimeType(mMimeTypes, lowerName);
|
||||||
if (!mimeType) {
|
|
||||||
mimeType = FindMimeType(mHiddenMimeTypes, lowerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mimeType) {
|
if (mimeType) {
|
||||||
aFound = true;
|
aFound = true;
|
||||||
return mimeType;
|
return mimeType;
|
||||||
|
@ -164,11 +158,8 @@ nsMimeTypeArray::NamedGetter(const nsAString& aName, bool &aFound)
|
||||||
// If we got here, we support this type! Say so.
|
// If we got here, we support this type! Say so.
|
||||||
aFound = true;
|
aFound = true;
|
||||||
|
|
||||||
// We don't want navigator.mimeTypes enumeration to expose MIME types with
|
|
||||||
// application handlers, so add them to the list of hidden MIME types.
|
|
||||||
nsMimeType *mt = new nsMimeType(mWindow, lowerName);
|
nsMimeType *mt = new nsMimeType(mWindow, lowerName);
|
||||||
mHiddenMimeTypes.AppendElement(mt);
|
mMimeTypes.AppendElement(mt);
|
||||||
|
|
||||||
return mt;
|
return mt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +190,7 @@ nsMimeTypeArray::GetSupportedNames(unsigned, nsTArray< nsString >& aRetval)
|
||||||
void
|
void
|
||||||
nsMimeTypeArray::EnsurePluginMimeTypes()
|
nsMimeTypeArray::EnsurePluginMimeTypes()
|
||||||
{
|
{
|
||||||
if (!mMimeTypes.IsEmpty() || !mHiddenMimeTypes.IsEmpty() || !mWindow) {
|
if (!mMimeTypes.IsEmpty() || !mWindow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +208,7 @@ nsMimeTypeArray::EnsurePluginMimeTypes()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginArray->GetMimeTypes(mMimeTypes, mHiddenMimeTypes);
|
pluginArray->GetMimeTypes(mMimeTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsMimeType, AddRef)
|
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsMimeType, AddRef)
|
||||||
|
|
|
@ -47,16 +47,9 @@ protected:
|
||||||
|
|
||||||
nsCOMPtr<nsPIDOMWindow> mWindow;
|
nsCOMPtr<nsPIDOMWindow> mWindow;
|
||||||
|
|
||||||
// mMimeTypes contains MIME types handled by non-hidden plugins, those
|
// mMimeTypes contains MIME types handled by plugins or by an OS
|
||||||
// popular plugins that must be exposed in navigator.plugins enumeration to
|
// PreferredApplicationHandler.
|
||||||
// avoid breaking web content. Likewise, mMimeTypes are exposed in
|
|
||||||
// navigator.mimeTypes enumeration.
|
|
||||||
nsTArray<nsRefPtr<nsMimeType> > mMimeTypes;
|
nsTArray<nsRefPtr<nsMimeType> > mMimeTypes;
|
||||||
|
|
||||||
// mHiddenMimeTypes contains MIME types handled by plugins hidden from
|
|
||||||
// navigator.plugins enumeration or by an OS PreferredApplicationHandler.
|
|
||||||
// mHiddenMimeTypes are hidden from navigator.mimeTypes enumeration.
|
|
||||||
nsTArray<nsRefPtr<nsMimeType> > mHiddenMimeTypes;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class nsMimeType final : public nsWrapperCache
|
class nsMimeType final : public nsWrapperCache
|
||||||
|
|
|
@ -6,11 +6,9 @@
|
||||||
|
|
||||||
#include "nsPluginArray.h"
|
#include "nsPluginArray.h"
|
||||||
|
|
||||||
#include "mozilla/Preferences.h"
|
|
||||||
#include "mozilla/dom/PluginArrayBinding.h"
|
#include "mozilla/dom/PluginArrayBinding.h"
|
||||||
#include "mozilla/dom/PluginBinding.h"
|
#include "mozilla/dom/PluginBinding.h"
|
||||||
|
|
||||||
#include "nsCharSeparatedTokenizer.h"
|
|
||||||
#include "nsMimeTypeArray.h"
|
#include "nsMimeTypeArray.h"
|
||||||
#include "Navigator.h"
|
#include "Navigator.h"
|
||||||
#include "nsIDocShell.h"
|
#include "nsIDocShell.h"
|
||||||
|
@ -68,8 +66,7 @@ NS_INTERFACE_MAP_END
|
||||||
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginArray,
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginArray,
|
||||||
mWindow,
|
mWindow,
|
||||||
mPlugins,
|
mPlugins)
|
||||||
mHiddenPlugins)
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
GetPluginMimeTypes(const nsTArray<nsRefPtr<nsPluginElement> >& aPlugins,
|
GetPluginMimeTypes(const nsTArray<nsRefPtr<nsPluginElement> >& aPlugins,
|
||||||
|
@ -89,11 +86,9 @@ operator<(const nsRefPtr<nsMimeType>& lhs, const nsRefPtr<nsMimeType>& rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
nsPluginArray::GetMimeTypes(nsTArray<nsRefPtr<nsMimeType> >& aMimeTypes,
|
nsPluginArray::GetMimeTypes(nsTArray<nsRefPtr<nsMimeType>>& aMimeTypes)
|
||||||
nsTArray<nsRefPtr<nsMimeType> >& aHiddenMimeTypes)
|
|
||||||
{
|
{
|
||||||
aMimeTypes.Clear();
|
aMimeTypes.Clear();
|
||||||
aHiddenMimeTypes.Clear();
|
|
||||||
|
|
||||||
if (!AllowPlugins()) {
|
if (!AllowPlugins()) {
|
||||||
return;
|
return;
|
||||||
|
@ -102,7 +97,6 @@ nsPluginArray::GetMimeTypes(nsTArray<nsRefPtr<nsMimeType> >& aMimeTypes,
|
||||||
EnsurePlugins();
|
EnsurePlugins();
|
||||||
|
|
||||||
GetPluginMimeTypes(mPlugins, aMimeTypes);
|
GetPluginMimeTypes(mPlugins, aMimeTypes);
|
||||||
GetPluginMimeTypes(mHiddenPlugins, aHiddenMimeTypes);
|
|
||||||
|
|
||||||
// Alphabetize the enumeration order of non-hidden MIME types to reduce
|
// Alphabetize the enumeration order of non-hidden MIME types to reduce
|
||||||
// fingerprintable entropy based on plugins' installation file times.
|
// fingerprintable entropy based on plugins' installation file times.
|
||||||
|
@ -146,14 +140,12 @@ nsPluginArray::Refresh(bool aReloadDocuments)
|
||||||
// happens, and therefore the lengths will be in sync only when
|
// happens, and therefore the lengths will be in sync only when
|
||||||
// the both arrays contain the same plugin tags (though as
|
// the both arrays contain the same plugin tags (though as
|
||||||
// different types).
|
// different types).
|
||||||
uint32_t pluginCount = mPlugins.Length() + mHiddenPlugins.Length();
|
if (newPluginTags.Length() == mPlugins.Length()) {
|
||||||
if (newPluginTags.Length() == pluginCount) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mPlugins.Clear();
|
mPlugins.Clear();
|
||||||
mHiddenPlugins.Clear();
|
|
||||||
|
|
||||||
nsCOMPtr<nsIDOMNavigator> navigator;
|
nsCOMPtr<nsIDOMNavigator> navigator;
|
||||||
mWindow->GetNavigator(getter_AddRefs(navigator));
|
mWindow->GetNavigator(getter_AddRefs(navigator));
|
||||||
|
@ -225,10 +217,6 @@ nsPluginArray::NamedGetter(const nsAString& aName, bool &aFound)
|
||||||
EnsurePlugins();
|
EnsurePlugins();
|
||||||
|
|
||||||
nsPluginElement* plugin = FindPlugin(mPlugins, aName);
|
nsPluginElement* plugin = FindPlugin(mPlugins, aName);
|
||||||
if (!plugin) {
|
|
||||||
plugin = FindPlugin(mHiddenPlugins, aName);
|
|
||||||
}
|
|
||||||
|
|
||||||
aFound = (plugin != nullptr);
|
aFound = (plugin != nullptr);
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
@ -286,28 +274,6 @@ nsPluginArray::AllowPlugins() const
|
||||||
return docShell && docShell->PluginsAllowedInCurrentDoc();
|
return docShell && docShell->PluginsAllowedInCurrentDoc();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
HasStringPrefix(const nsCString& str, const nsACString& prefix) {
|
|
||||||
return str.Compare(prefix.BeginReading(), false, prefix.Length()) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
IsPluginEnumerable(const nsTArray<nsCString>& enumerableNames,
|
|
||||||
const nsPluginTag* pluginTag)
|
|
||||||
{
|
|
||||||
const nsCString& pluginName = pluginTag->mName;
|
|
||||||
|
|
||||||
const uint32_t length = enumerableNames.Length();
|
|
||||||
for (uint32_t i = 0; i < length; i++) {
|
|
||||||
const nsCString& name = enumerableNames[i];
|
|
||||||
if (HasStringPrefix(pluginName, name)) {
|
|
||||||
return true; // don't hide plugin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // hide plugin!
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
operator<(const nsRefPtr<nsPluginElement>& lhs,
|
operator<(const nsRefPtr<nsPluginElement>& lhs,
|
||||||
const nsRefPtr<nsPluginElement>& rhs)
|
const nsRefPtr<nsPluginElement>& rhs)
|
||||||
|
@ -319,7 +285,7 @@ operator<(const nsRefPtr<nsPluginElement>& lhs,
|
||||||
void
|
void
|
||||||
nsPluginArray::EnsurePlugins()
|
nsPluginArray::EnsurePlugins()
|
||||||
{
|
{
|
||||||
if (!mPlugins.IsEmpty() || !mHiddenPlugins.IsEmpty()) {
|
if (!mPlugins.IsEmpty()) {
|
||||||
// We already have an array of plugin elements.
|
// We already have an array of plugin elements.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -333,36 +299,11 @@ nsPluginArray::EnsurePlugins()
|
||||||
nsTArray<nsRefPtr<nsPluginTag> > pluginTags;
|
nsTArray<nsRefPtr<nsPluginTag> > pluginTags;
|
||||||
pluginHost->GetPlugins(pluginTags);
|
pluginHost->GetPlugins(pluginTags);
|
||||||
|
|
||||||
nsTArray<nsCString> enumerableNames;
|
|
||||||
|
|
||||||
const nsAdoptingCString& enumerableNamesPref =
|
|
||||||
Preferences::GetCString("plugins.enumerable_names");
|
|
||||||
|
|
||||||
bool disablePluginHiding = !enumerableNamesPref ||
|
|
||||||
enumerableNamesPref.EqualsLiteral("*");
|
|
||||||
|
|
||||||
if (!disablePluginHiding) {
|
|
||||||
nsCCharSeparatedTokenizer tokens(enumerableNamesPref, ',');
|
|
||||||
while (tokens.hasMoreTokens()) {
|
|
||||||
const nsCSubstring& token = tokens.nextToken();
|
|
||||||
if (!token.IsEmpty()) {
|
|
||||||
enumerableNames.AppendElement(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// need to wrap each of these with a nsPluginElement, which is
|
// need to wrap each of these with a nsPluginElement, which is
|
||||||
// scriptable.
|
// scriptable.
|
||||||
for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
|
for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
|
||||||
nsPluginTag* pluginTag = pluginTags[i];
|
nsPluginTag* pluginTag = pluginTags[i];
|
||||||
|
mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTag));
|
||||||
// Add the plugin to the list of hidden plugins or non-hidden plugins?
|
|
||||||
nsTArray<nsRefPtr<nsPluginElement> >& pluginArray =
|
|
||||||
(disablePluginHiding || IsPluginEnumerable(enumerableNames, pluginTag))
|
|
||||||
? mPlugins
|
|
||||||
: mHiddenPlugins;
|
|
||||||
|
|
||||||
pluginArray.AppendElement(new nsPluginElement(mWindow, pluginTag));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alphabetize the enumeration order of non-hidden plugins to reduce
|
// Alphabetize the enumeration order of non-hidden plugins to reduce
|
||||||
|
|
|
@ -40,8 +40,7 @@ public:
|
||||||
void Init();
|
void Init();
|
||||||
void Invalidate();
|
void Invalidate();
|
||||||
|
|
||||||
void GetMimeTypes(nsTArray<nsRefPtr<nsMimeType> >& aMimeTypes,
|
void GetMimeTypes(nsTArray<nsRefPtr<nsMimeType>>& aMimeTypes);
|
||||||
nsTArray<nsRefPtr<nsMimeType> >& aHiddenMimeTypes);
|
|
||||||
|
|
||||||
// PluginArray WebIDL methods
|
// PluginArray WebIDL methods
|
||||||
|
|
||||||
|
@ -61,18 +60,7 @@ private:
|
||||||
void EnsurePlugins();
|
void EnsurePlugins();
|
||||||
|
|
||||||
nsCOMPtr<nsPIDOMWindow> mWindow;
|
nsCOMPtr<nsPIDOMWindow> mWindow;
|
||||||
|
|
||||||
// Many sites check whether a particular plugin is installed by enumerating
|
|
||||||
// all navigator.plugins, checking each plugin's name. These sites should
|
|
||||||
// just check navigator.plugins["Popular Plugin Name"] instead. mPlugins
|
|
||||||
// contains those popular plugins that must be exposed in navigator.plugins
|
|
||||||
// enumeration to avoid breaking web content.
|
|
||||||
nsTArray<nsRefPtr<nsPluginElement> > mPlugins;
|
nsTArray<nsRefPtr<nsPluginElement> > mPlugins;
|
||||||
|
|
||||||
// mHiddenPlugins contains plugins that can be queried by
|
|
||||||
// navigator.plugins["Hidden Plugin Name"] but do not need to be exposed in
|
|
||||||
// navigator.plugins enumeration.
|
|
||||||
nsTArray<nsRefPtr<nsPluginElement> > mHiddenPlugins;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class nsPluginElement final : public nsISupports,
|
class nsPluginElement final : public nsISupports,
|
||||||
|
|
|
@ -21464,7 +21464,7 @@ function test_getImageData_after_zero_canvas() {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p>Canvas test: zero_dimensions_image_data</p>
|
<p>Canvas test: linedash</p>
|
||||||
<canvas id="c687" width="150" height="50"></canvas>
|
<canvas id="c687" width="150" height="50"></canvas>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
@ -21536,6 +21536,16 @@ function test_linedash() {
|
||||||
isPixel(ctx, 105, 40, 0, 255, 0, 255, 0);
|
isPixel(ctx, 105, 40, 0, 255, 0, 255, 0);
|
||||||
isPixel(ctx, 90, 35, 0, 0, 0, 0, 0);
|
isPixel(ctx, 90, 35, 0, 0, 0, 0, 0);
|
||||||
isPixel(ctx, 90, 25, 0, 255, 0, 255, 0);
|
isPixel(ctx, 90, 25, 0, 255, 0, 255, 0);
|
||||||
|
|
||||||
|
// Ensure that all zeros or negative pattern does not cause error state in context, see bug 1169609
|
||||||
|
ctx.setLineDash([0, 0]);
|
||||||
|
ctx.strokeRect(130.5, 10.5, 10, 10);
|
||||||
|
ctx.setLineDash([-1]);
|
||||||
|
ctx.strokeRect(130.5, 10.5, 10, 10);
|
||||||
|
isPixel(ctx, 135, 15, 0, 0, 0, 0, 0);
|
||||||
|
ctx.fillStyle = '#00FF00';
|
||||||
|
ctx.fillRect(130, 10, 10, 10);
|
||||||
|
isPixel(ctx, 135, 15, 0, 255, 0, 255, 0);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -3281,12 +3281,13 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
|
||||||
}
|
}
|
||||||
if (dispatchedToContentProcess) {
|
if (dispatchedToContentProcess) {
|
||||||
dragSession->SetCanDrop(true);
|
dragSession->SetCanDrop(true);
|
||||||
}
|
} else if (initialDataTransfer) {
|
||||||
|
// Now set the drop effect in the initial dataTransfer. This ensures
|
||||||
// now set the drop effect in the initial dataTransfer. This ensures
|
// that we can get the desired drop effect in the drop event. For events
|
||||||
// that we can get the desired drop effect in the drop event.
|
// dispatched to content, the content process will take care of setting
|
||||||
if (initialDataTransfer)
|
// this.
|
||||||
initialDataTransfer->SetDropEffectInt(dropEffect);
|
initialDataTransfer->SetDropEffectInt(dropEffect);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -1179,6 +1179,9 @@ IMEContentObserver::FlushMergeableNotifications()
|
||||||
nsContentUtils::AddScriptRunner(new TextChangeEvent(this, mTextChangeData));
|
nsContentUtils::AddScriptRunner(new TextChangeEvent(this, mTextChangeData));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Be aware, PuppetWidget depends on the order of this. A selection change
|
||||||
|
// notification should not be sent before a text change notification because
|
||||||
|
// PuppetWidget shouldn't query new text content every selection change.
|
||||||
if (mIsSelectionChangeEventPending) {
|
if (mIsSelectionChangeEventPending) {
|
||||||
mIsSelectionChangeEventPending = false;
|
mIsSelectionChangeEventPending = false;
|
||||||
nsContentUtils::AddScriptRunner(
|
nsContentUtils::AddScriptRunner(
|
||||||
|
|
|
@ -684,6 +684,13 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
|
||||||
uint32_t responseStatus = 200;
|
uint32_t responseStatus = 200;
|
||||||
nsAutoCString statusText;
|
nsAutoCString statusText;
|
||||||
response = new InternalResponse(responseStatus, NS_LITERAL_CSTRING("OK"));
|
response = new InternalResponse(responseStatus, NS_LITERAL_CSTRING("OK"));
|
||||||
|
ErrorResult result;
|
||||||
|
nsAutoCString contentType;
|
||||||
|
jarChannel->GetContentType(contentType);
|
||||||
|
response->Headers()->Append(NS_LITERAL_CSTRING("content-type"),
|
||||||
|
contentType,
|
||||||
|
result);
|
||||||
|
MOZ_ASSERT(!result.Failed());
|
||||||
}
|
}
|
||||||
|
|
||||||
// We open a pipe so that we can immediately set the pipe's read end as the
|
// We open a pipe so that we can immediately set the pipe's read end as the
|
||||||
|
|
|
@ -430,6 +430,7 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement)
|
||||||
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource)
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcMediaSource)
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcStream)
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcStream)
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaybackStream)
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaybackStream)
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcAttrStream)
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcAttrStream)
|
||||||
|
@ -458,6 +459,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLE
|
||||||
}
|
}
|
||||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcAttrStream)
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcAttrStream)
|
||||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource)
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcMediaSource)
|
||||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer)
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer)
|
||||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc)
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc)
|
||||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
|
||||||
|
@ -515,10 +517,7 @@ HTMLMediaElement::IsVideo()
|
||||||
already_AddRefed<MediaSource>
|
already_AddRefed<MediaSource>
|
||||||
HTMLMediaElement::GetMozMediaSourceObject() const
|
HTMLMediaElement::GetMozMediaSourceObject() const
|
||||||
{
|
{
|
||||||
nsRefPtr<MediaSource> source;
|
nsRefPtr<MediaSource> source = mMediaSource;
|
||||||
if (mLoadingSrc && IsMediaSourceURI(mLoadingSrc)) {
|
|
||||||
NS_GetSourceForMediaSourceURI(mLoadingSrc, getter_AddRefs(source));
|
|
||||||
}
|
|
||||||
return source.forget();
|
return source.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -631,7 +630,7 @@ void HTMLMediaElement::ShutdownDecoder()
|
||||||
RemoveMediaElementFromURITable();
|
RemoveMediaElementFromURITable();
|
||||||
NS_ASSERTION(mDecoder, "Must have decoder to shut down");
|
NS_ASSERTION(mDecoder, "Must have decoder to shut down");
|
||||||
mDecoder->Shutdown();
|
mDecoder->Shutdown();
|
||||||
SetDecoder(nullptr);
|
mDecoder = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTMLMediaElement::AbortExistingLoads()
|
void HTMLMediaElement::AbortExistingLoads()
|
||||||
|
@ -667,12 +666,9 @@ void HTMLMediaElement::AbortExistingLoads()
|
||||||
if (mSrcStream) {
|
if (mSrcStream) {
|
||||||
EndSrcMediaStreamPlayback();
|
EndSrcMediaStreamPlayback();
|
||||||
}
|
}
|
||||||
if (mMediaSource) {
|
|
||||||
mMediaSource->Detach();
|
|
||||||
mMediaSource = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
mLoadingSrc = nullptr;
|
mLoadingSrc = nullptr;
|
||||||
|
mMediaSource = nullptr;
|
||||||
|
|
||||||
if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING ||
|
if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING ||
|
||||||
mNetworkState == nsIDOMHTMLMediaElement::NETWORK_IDLE)
|
mNetworkState == nsIDOMHTMLMediaElement::NETWORK_IDLE)
|
||||||
|
@ -872,6 +868,7 @@ void HTMLMediaElement::SelectResource()
|
||||||
"Should think we're not loading from source children by default");
|
"Should think we're not loading from source children by default");
|
||||||
|
|
||||||
mLoadingSrc = uri;
|
mLoadingSrc = uri;
|
||||||
|
mMediaSource = mSrcMediaSource;
|
||||||
UpdatePreloadAction();
|
UpdatePreloadAction();
|
||||||
if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE &&
|
if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE &&
|
||||||
!IsMediaStreamURI(mLoadingSrc)) {
|
!IsMediaStreamURI(mLoadingSrc)) {
|
||||||
|
@ -1009,6 +1006,7 @@ void HTMLMediaElement::LoadFromSourceChildren()
|
||||||
}
|
}
|
||||||
|
|
||||||
mLoadingSrc = uri;
|
mLoadingSrc = uri;
|
||||||
|
mMediaSource = childSrc->GetSrcMediaSource();
|
||||||
NS_ASSERTION(mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING,
|
NS_ASSERTION(mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING,
|
||||||
"Network state should be loading");
|
"Network state should be loading");
|
||||||
|
|
||||||
|
@ -1084,7 +1082,7 @@ void HTMLMediaElement::UpdatePreloadAction()
|
||||||
kNameSpaceID_None);
|
kNameSpaceID_None);
|
||||||
// MSE doesn't work if preload is none, so it ignores the pref when src is
|
// MSE doesn't work if preload is none, so it ignores the pref when src is
|
||||||
// from MSE.
|
// from MSE.
|
||||||
uint32_t preloadDefault = (mLoadingSrc && IsMediaSourceURI(mLoadingSrc)) ?
|
uint32_t preloadDefault = mMediaSource ?
|
||||||
HTMLMediaElement::PRELOAD_ATTR_METADATA :
|
HTMLMediaElement::PRELOAD_ATTR_METADATA :
|
||||||
Preferences::GetInt("media.preload.default",
|
Preferences::GetInt("media.preload.default",
|
||||||
HTMLMediaElement::PRELOAD_ATTR_METADATA);
|
HTMLMediaElement::PRELOAD_ATTR_METADATA);
|
||||||
|
@ -1193,9 +1191,8 @@ nsresult HTMLMediaElement::LoadResource()
|
||||||
nsRefPtr<DOMMediaStream> stream;
|
nsRefPtr<DOMMediaStream> stream;
|
||||||
rv = NS_GetStreamForMediaStreamURI(mLoadingSrc, getter_AddRefs(stream));
|
rv = NS_GetStreamForMediaStreamURI(mLoadingSrc, getter_AddRefs(stream));
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
nsCString specUTF8;
|
nsAutoString spec;
|
||||||
mLoadingSrc->GetSpec(specUTF8);
|
GetCurrentSrc(spec);
|
||||||
NS_ConvertUTF8toUTF16 spec(specUTF8);
|
|
||||||
const char16_t* params[] = { spec.get() };
|
const char16_t* params[] = { spec.get() };
|
||||||
ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
|
ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
|
||||||
return rv;
|
return rv;
|
||||||
|
@ -1204,25 +1201,14 @@ nsresult HTMLMediaElement::LoadResource()
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsMediaSourceURI(mLoadingSrc)) {
|
if (mMediaSource) {
|
||||||
nsRefPtr<MediaSource> source;
|
|
||||||
rv = NS_GetSourceForMediaSourceURI(mLoadingSrc, getter_AddRefs(source));
|
|
||||||
if (NS_FAILED(rv)) {
|
|
||||||
nsCString specUTF8;
|
|
||||||
mLoadingSrc->GetSpec(specUTF8);
|
|
||||||
NS_ConvertUTF8toUTF16 spec(specUTF8);
|
|
||||||
const char16_t* params[] = { spec.get() };
|
|
||||||
ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
nsRefPtr<MediaSourceDecoder> decoder = new MediaSourceDecoder(this);
|
nsRefPtr<MediaSourceDecoder> decoder = new MediaSourceDecoder(this);
|
||||||
if (!source->Attach(decoder)) {
|
if (!mMediaSource->Attach(decoder)) {
|
||||||
// TODO: Handle failure: run "If the media data cannot be fetched at
|
// TODO: Handle failure: run "If the media data cannot be fetched at
|
||||||
// all, due to network errors, causing the user agent to give up
|
// all, due to network errors, causing the user agent to give up
|
||||||
// trying to fetch the resource" section of resource fetch algorithm.
|
// trying to fetch the resource" section of resource fetch algorithm.
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
mMediaSource = source.forget();
|
|
||||||
nsRefPtr<MediaResource> resource =
|
nsRefPtr<MediaResource> resource =
|
||||||
MediaSourceDecoder::CreateResource(mMediaSource->GetPrincipal());
|
MediaSourceDecoder::CreateResource(mMediaSource->GetPrincipal());
|
||||||
if (IsAutoplayEnabled()) {
|
if (IsAutoplayEnabled()) {
|
||||||
|
@ -2129,10 +2115,6 @@ HTMLMediaElement::~HTMLMediaElement()
|
||||||
if (mSrcStream) {
|
if (mSrcStream) {
|
||||||
EndSrcMediaStreamPlayback();
|
EndSrcMediaStreamPlayback();
|
||||||
}
|
}
|
||||||
if (mMediaSource) {
|
|
||||||
mMediaSource->Detach();
|
|
||||||
mMediaSource = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
|
NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
|
||||||
"Destroyed media element should no longer be in element table");
|
"Destroyed media element should no longer be in element table");
|
||||||
|
@ -2526,6 +2508,33 @@ nsresult HTMLMediaElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
HTMLMediaElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
|
||||||
|
const nsAttrValue* aValue, bool aNotify)
|
||||||
|
{
|
||||||
|
if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::src) {
|
||||||
|
mSrcMediaSource = nullptr;
|
||||||
|
if (aValue) {
|
||||||
|
nsString srcStr = aValue->GetStringValue();
|
||||||
|
nsCOMPtr<nsIURI> uri;
|
||||||
|
NewURIFromString(srcStr, getter_AddRefs(uri));
|
||||||
|
if (uri && IsMediaSourceURI(uri)) {
|
||||||
|
nsresult rv =
|
||||||
|
NS_GetSourceForMediaSourceURI(uri, getter_AddRefs(mSrcMediaSource));
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
nsAutoString spec;
|
||||||
|
GetCurrentSrc(spec);
|
||||||
|
const char16_t* params[] = { spec.get() };
|
||||||
|
ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
|
||||||
|
aValue, aNotify);
|
||||||
|
}
|
||||||
|
|
||||||
nsresult HTMLMediaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
nsresult HTMLMediaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
||||||
nsIContent* aBindingParent,
|
nsIContent* aBindingParent,
|
||||||
bool aCompileEventHandlers)
|
bool aCompileEventHandlers)
|
||||||
|
@ -2797,7 +2806,7 @@ nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder,
|
||||||
|
|
||||||
nsresult rv = aDecoder->Load(aListener, aCloneDonor);
|
nsresult rv = aDecoder->Load(aListener, aCloneDonor);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
SetDecoder(nullptr);
|
ShutdownDecoder();
|
||||||
LOG(LogLevel::Debug, ("%p Failed to load for decoder %p", this, aDecoder));
|
LOG(LogLevel::Debug, ("%p Failed to load for decoder %p", this, aDecoder));
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
@ -3259,6 +3268,7 @@ void HTMLMediaElement::DecodeError()
|
||||||
ShutdownDecoder();
|
ShutdownDecoder();
|
||||||
}
|
}
|
||||||
mLoadingSrc = nullptr;
|
mLoadingSrc = nullptr;
|
||||||
|
mMediaSource = nullptr;
|
||||||
if (mIsLoadingFromSourceChildren) {
|
if (mIsLoadingFromSourceChildren) {
|
||||||
mError = nullptr;
|
mError = nullptr;
|
||||||
if (mSourceLoadCandidate) {
|
if (mSourceLoadCandidate) {
|
||||||
|
@ -3425,7 +3435,7 @@ void HTMLMediaElement::CheckProgress(bool aHaveNewProgress)
|
||||||
if (now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) {
|
if (now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) {
|
||||||
DispatchAsyncEvent(NS_LITERAL_STRING("stalled"));
|
DispatchAsyncEvent(NS_LITERAL_STRING("stalled"));
|
||||||
|
|
||||||
if (mLoadingSrc && IsMediaSourceURI(mLoadingSrc)) {
|
if (mMediaSource) {
|
||||||
ChangeDelayLoadStatus(false);
|
ChangeDelayLoadStatus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,6 +132,9 @@ public:
|
||||||
bool aNotify) override;
|
bool aNotify) override;
|
||||||
virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
|
virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
|
||||||
bool aNotify) override;
|
bool aNotify) override;
|
||||||
|
virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
|
||||||
|
const nsAttrValue* aValue,
|
||||||
|
bool aNotify) override;
|
||||||
|
|
||||||
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
||||||
nsIContent* aBindingParent,
|
nsIContent* aBindingParent,
|
||||||
|
@ -645,7 +648,10 @@ protected:
|
||||||
class StreamSizeListener;
|
class StreamSizeListener;
|
||||||
|
|
||||||
MediaDecoderOwner::NextFrameStatus NextFrameStatus();
|
MediaDecoderOwner::NextFrameStatus NextFrameStatus();
|
||||||
void SetDecoder(MediaDecoder* aDecoder) { mDecoder = aDecoder; }
|
void SetDecoder(MediaDecoder* aDecoder) {
|
||||||
|
MOZ_ASSERT(aDecoder); // Use ShutdownDecoder() to clear.
|
||||||
|
mDecoder = aDecoder;
|
||||||
|
}
|
||||||
|
|
||||||
virtual void GetItemValueText(DOMString& text) override;
|
virtual void GetItemValueText(DOMString& text) override;
|
||||||
virtual void SetItemValueText(const nsAString& text) override;
|
virtual void SetItemValueText(const nsAString& text) override;
|
||||||
|
@ -1064,7 +1070,14 @@ protected:
|
||||||
// mSrcStream.
|
// mSrcStream.
|
||||||
nsRefPtr<StreamSizeListener> mMediaStreamSizeListener;
|
nsRefPtr<StreamSizeListener> mMediaStreamSizeListener;
|
||||||
|
|
||||||
// Holds a reference to the MediaSource supplying data for playback.
|
// Holds a reference to the MediaSource, if any, referenced by the src
|
||||||
|
// attribute on the media element.
|
||||||
|
nsRefPtr<MediaSource> mSrcMediaSource;
|
||||||
|
|
||||||
|
// Holds a reference to the MediaSource supplying data for playback. This
|
||||||
|
// may either match mSrcMediaSource or come from Source element children.
|
||||||
|
// This is set when and only when mLoadingSrc corresponds to an object url
|
||||||
|
// that resolved to a MediaSource.
|
||||||
nsRefPtr<MediaSource> mMediaSource;
|
nsRefPtr<MediaSource> mMediaSource;
|
||||||
|
|
||||||
// Holds a reference to the first channel we open to the media resource.
|
// Holds a reference to the first channel we open to the media resource.
|
||||||
|
|
|
@ -9,11 +9,13 @@
|
||||||
|
|
||||||
#include "mozilla/dom/HTMLImageElement.h"
|
#include "mozilla/dom/HTMLImageElement.h"
|
||||||
#include "mozilla/dom/ResponsiveImageSelector.h"
|
#include "mozilla/dom/ResponsiveImageSelector.h"
|
||||||
|
#include "mozilla/dom/MediaSource.h"
|
||||||
|
|
||||||
#include "nsGkAtoms.h"
|
#include "nsGkAtoms.h"
|
||||||
|
|
||||||
#include "nsIMediaList.h"
|
#include "nsIMediaList.h"
|
||||||
#include "nsCSSParser.h"
|
#include "nsCSSParser.h"
|
||||||
|
#include "nsHostObjectProtocolHandler.h"
|
||||||
|
|
||||||
#include "mozilla/Preferences.h"
|
#include "mozilla/Preferences.h"
|
||||||
|
|
||||||
|
@ -31,8 +33,15 @@ HTMLSourceElement::~HTMLSourceElement()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMPL_ISUPPORTS_INHERITED(HTMLSourceElement, nsGenericHTMLElement,
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLSourceElement, nsGenericHTMLElement,
|
||||||
nsIDOMHTMLSourceElement)
|
mSrcMediaSource)
|
||||||
|
|
||||||
|
NS_IMPL_ADDREF_INHERITED(HTMLSourceElement, nsGenericHTMLElement)
|
||||||
|
NS_IMPL_RELEASE_INHERITED(HTMLSourceElement, nsGenericHTMLElement)
|
||||||
|
|
||||||
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLSourceElement)
|
||||||
|
NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLSourceElement)
|
||||||
|
NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
|
||||||
|
|
||||||
NS_IMPL_ELEMENT_CLONE(HTMLSourceElement)
|
NS_IMPL_ELEMENT_CLONE(HTMLSourceElement)
|
||||||
|
|
||||||
|
@ -114,6 +123,16 @@ HTMLSourceElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
|
||||||
cssParser.ParseMediaList(mediaStr, nullptr, 0, mMediaList, false);
|
cssParser.ParseMediaList(mediaStr, nullptr, 0, mMediaList, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::src) {
|
||||||
|
mSrcMediaSource = nullptr;
|
||||||
|
if (aValue) {
|
||||||
|
nsString srcStr = aValue->GetStringValue();
|
||||||
|
nsCOMPtr<nsIURI> uri;
|
||||||
|
NewURIFromString(srcStr, getter_AddRefs(uri));
|
||||||
|
if (uri && IsMediaSourceURI(uri)) {
|
||||||
|
NS_GetSourceForMediaSourceURI(uri, getter_AddRefs(mSrcMediaSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
|
return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
|
||||||
|
|
|
@ -25,6 +25,8 @@ public:
|
||||||
|
|
||||||
// nsISupports
|
// nsISupports
|
||||||
NS_DECL_ISUPPORTS_INHERITED
|
NS_DECL_ISUPPORTS_INHERITED
|
||||||
|
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLSourceElement,
|
||||||
|
nsGenericHTMLElement)
|
||||||
|
|
||||||
NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLSourceElement, source)
|
NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLSourceElement, source)
|
||||||
|
|
||||||
|
@ -49,6 +51,10 @@ public:
|
||||||
static bool WouldMatchMediaForDocument(const nsAString& aMediaStr,
|
static bool WouldMatchMediaForDocument(const nsAString& aMediaStr,
|
||||||
const nsIDocument *aDocument);
|
const nsIDocument *aDocument);
|
||||||
|
|
||||||
|
// Return the MediaSource object if any associated with the src attribute
|
||||||
|
// when it was set.
|
||||||
|
MediaSource* GetSrcMediaSource() { return mSrcMediaSource; };
|
||||||
|
|
||||||
// WebIDL
|
// WebIDL
|
||||||
void GetSrc(nsString& aSrc)
|
void GetSrc(nsString& aSrc)
|
||||||
{
|
{
|
||||||
|
@ -111,6 +117,7 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
nsRefPtr<nsMediaList> mMediaList;
|
nsRefPtr<nsMediaList> mMediaList;
|
||||||
|
nsRefPtr<MediaSource> mSrcMediaSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
|
|
|
@ -3194,7 +3194,7 @@ nsGenericHTMLElement::IsEventAttributeName(nsIAtom *aName)
|
||||||
* would be set to. Helper for the media elements.
|
* would be set to. Helper for the media elements.
|
||||||
*/
|
*/
|
||||||
nsresult
|
nsresult
|
||||||
nsGenericHTMLElement::NewURIFromString(const nsAutoString& aURISpec,
|
nsGenericHTMLElement::NewURIFromString(const nsAString& aURISpec,
|
||||||
nsIURI** aURI)
|
nsIURI** aURI)
|
||||||
{
|
{
|
||||||
NS_ENSURE_ARG_POINTER(aURI);
|
NS_ENSURE_ARG_POINTER(aURI);
|
||||||
|
|
|
@ -1011,7 +1011,7 @@ protected:
|
||||||
* Returns INVALID_STATE_ERR and nulls *aURI if aURISpec is empty
|
* Returns INVALID_STATE_ERR and nulls *aURI if aURISpec is empty
|
||||||
* and the document's URI matches the element's base URI.
|
* and the document's URI matches the element's base URI.
|
||||||
*/
|
*/
|
||||||
nsresult NewURIFromString(const nsAutoString& aURISpec, nsIURI** aURI);
|
nsresult NewURIFromString(const nsAString& aURISpec, nsIURI** aURI);
|
||||||
|
|
||||||
void GetHTMLAttr(nsIAtom* aName, nsAString& aResult) const
|
void GetHTMLAttr(nsIAtom* aName, nsAString& aResult) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -132,18 +132,18 @@ group2 = [ createNode('r', 'g2', false),
|
||||||
group3 = [ createNode('r', 'g1', false),
|
group3 = [ createNode('r', 'g1', false),
|
||||||
createNode('r', 'g1', false),
|
createNode('r', 'g1', false),
|
||||||
createNode('r', 'g1', false) ];
|
createNode('r', 'g1', false) ];
|
||||||
for each (let g in group1) {
|
for (let g of group1) {
|
||||||
$(tag == "input" ? "f1" : "m1").appendChild(g);
|
$(tag == "input" ? "f1" : "m1").appendChild(g);
|
||||||
}
|
}
|
||||||
for each (let g in group2) {
|
for (let g of group2) {
|
||||||
$(tag == "input" ? "f1" : "m1").appendChild(g);
|
$(tag == "input" ? "f1" : "m1").appendChild(g);
|
||||||
}
|
}
|
||||||
for each (let g in group3) {
|
for (let g of group3) {
|
||||||
$(tag == "input" ? "f2" : "m2").appendChild(g);
|
$(tag == "input" ? "f2" : "m2").appendChild(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, false,
|
is(g.defaultChecked, false,
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
"] defaultChecked wrong pass 1");
|
"] defaultChecked wrong pass 1");
|
||||||
|
@ -154,8 +154,8 @@ for each (let n in [1, 2, 3]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
group1[1].defaultChecked = true;
|
group1[1].defaultChecked = true;
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, n == 1 && group1.indexOf(g) == 1,
|
is(g.defaultChecked, n == 1 && group1.indexOf(g) == 1,
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
"] defaultChecked wrong pass 2");
|
"] defaultChecked wrong pass 2");
|
||||||
|
@ -166,8 +166,8 @@ for each (let n in [1, 2, 3]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
group1[0].defaultChecked = true;
|
group1[0].defaultChecked = true;
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 1 ||
|
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 1 ||
|
||||||
group1.indexOf(g) == 0),
|
group1.indexOf(g) == 0),
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
|
@ -179,8 +179,8 @@ for each (let n in [1, 2, 3]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
group1[2].defaultChecked = true;
|
group1[2].defaultChecked = true;
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, n == 1,
|
is(g.defaultChecked, n == 1,
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
"] defaultChecked wrong pass 4");
|
"] defaultChecked wrong pass 4");
|
||||||
|
@ -196,8 +196,8 @@ p.removeChild(group1[1]);
|
||||||
group1[1].defaultChecked = false;
|
group1[1].defaultChecked = false;
|
||||||
group1[1].defaultChecked = true;
|
group1[1].defaultChecked = true;
|
||||||
p.insertBefore(group1[1], next);
|
p.insertBefore(group1[1], next);
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, n == 1,
|
is(g.defaultChecked, n == 1,
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
"] defaultChecked wrong pass 5");
|
"] defaultChecked wrong pass 5");
|
||||||
|
@ -207,11 +207,11 @@ for each (let n in [1, 2, 3]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for each(let g in group1) {
|
for (let g of group1) {
|
||||||
g.defaultChecked = false;
|
g.defaultChecked = false;
|
||||||
}
|
}
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, false,
|
is(g.defaultChecked, false,
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
"] defaultChecked wrong pass 6");
|
"] defaultChecked wrong pass 6");
|
||||||
|
@ -222,8 +222,8 @@ for each (let n in [1, 2, 3]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
group1[1].checked = true;
|
group1[1].checked = true;
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, false,
|
is(g.defaultChecked, false,
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
"] defaultChecked wrong pass 7");
|
"] defaultChecked wrong pass 7");
|
||||||
|
@ -234,8 +234,8 @@ for each (let n in [1, 2, 3]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
group1[0].defaultChecked = true;
|
group1[0].defaultChecked = true;
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, n == 1 && group1.indexOf(g) == 0,
|
is(g.defaultChecked, n == 1 && group1.indexOf(g) == 0,
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
"] defaultChecked wrong pass 8");
|
"] defaultChecked wrong pass 8");
|
||||||
|
@ -246,8 +246,8 @@ for each (let n in [1, 2, 3]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
group1[2].defaultChecked = true;
|
group1[2].defaultChecked = true;
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
|
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
|
||||||
group1.indexOf(g) == 2),
|
group1.indexOf(g) == 2),
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
|
@ -258,8 +258,8 @@ for each (let n in [1, 2, 3]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
group1[1].parentNode.removeChild(group1[1]);
|
group1[1].parentNode.removeChild(group1[1]);
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
|
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
|
||||||
group1.indexOf(g) == 2),
|
group1.indexOf(g) == 2),
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
|
@ -271,8 +271,8 @@ for each (let n in [1, 2, 3]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
group1[2].checked = true;
|
group1[2].checked = true;
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
|
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
|
||||||
group1.indexOf(g) == 2),
|
group1.indexOf(g) == 2),
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
|
@ -285,8 +285,8 @@ for each (let n in [1, 2, 3]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
group1[0].checked = true;
|
group1[0].checked = true;
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
|
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
|
||||||
group1.indexOf(g) == 2),
|
group1.indexOf(g) == 2),
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
|
@ -303,8 +303,8 @@ p = group2[1].parentNode;
|
||||||
p.removeChild(group2[1]);
|
p.removeChild(group2[1]);
|
||||||
p.insertBefore(group2[1], next);
|
p.insertBefore(group2[1], next);
|
||||||
group2[0].checked = true;
|
group2[0].checked = true;
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
|
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
|
||||||
group1.indexOf(g) == 2),
|
group1.indexOf(g) == 2),
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
|
@ -318,8 +318,8 @@ for each (let n in [1, 2, 3]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
p.insertBefore(group2[1], next);
|
p.insertBefore(group2[1], next);
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
|
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
|
||||||
group1.indexOf(g) == 2),
|
group1.indexOf(g) == 2),
|
||||||
"group" + n + "[" + window["group"+n].indexOf(g) +
|
"group" + n + "[" + window["group"+n].indexOf(g) +
|
||||||
|
@ -333,8 +333,8 @@ for each (let n in [1, 2, 3]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
group2[1].defaultChecked = true;
|
group2[1].defaultChecked = true;
|
||||||
for each (let n in [1, 2, 3]) {
|
for (let n of [1, 2, 3]) {
|
||||||
for each (let g in window["group"+n]) {
|
for (let g of window["group"+n]) {
|
||||||
is(g.defaultChecked, (n == 1 && (group1.indexOf(g) == 0 ||
|
is(g.defaultChecked, (n == 1 && (group1.indexOf(g) == 0 ||
|
||||||
group1.indexOf(g) == 2)) ||
|
group1.indexOf(g) == 2)) ||
|
||||||
(n == 2 && group2.indexOf(g) == 1),
|
(n == 2 && group2.indexOf(g) == 1),
|
||||||
|
|
|
@ -41,6 +41,7 @@ using struct nsIMEUpdatePreference from "nsIWidget.h";
|
||||||
using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
|
using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
|
||||||
using mozilla::gfx::IntPoint from "mozilla/gfx/Point.h";
|
using mozilla::gfx::IntPoint from "mozilla/gfx/Point.h";
|
||||||
using mozilla::gfx::IntRect from "mozilla/gfx/Rect.h";
|
using mozilla::gfx::IntRect from "mozilla/gfx/Rect.h";
|
||||||
|
using class mozilla::ContentCache from "ipc/nsGUIEventIPC.h";
|
||||||
using class mozilla::WidgetKeyboardEvent from "ipc/nsGUIEventIPC.h";
|
using class mozilla::WidgetKeyboardEvent from "ipc/nsGUIEventIPC.h";
|
||||||
using class mozilla::WidgetMouseEvent from "ipc/nsGUIEventIPC.h";
|
using class mozilla::WidgetMouseEvent from "ipc/nsGUIEventIPC.h";
|
||||||
using class mozilla::WidgetWheelEvent from "ipc/nsGUIEventIPC.h";
|
using class mozilla::WidgetWheelEvent from "ipc/nsGUIEventIPC.h";
|
||||||
|
@ -171,9 +172,10 @@ parent:
|
||||||
*
|
*
|
||||||
* focus PR_TRUE if editable object is receiving focus
|
* focus PR_TRUE if editable object is receiving focus
|
||||||
* PR_FALSE if losing focus
|
* PR_FALSE if losing focus
|
||||||
|
* contentCache Cache of content
|
||||||
* preference Native widget preference for IME updates
|
* preference Native widget preference for IME updates
|
||||||
*/
|
*/
|
||||||
prio(urgent) sync NotifyIMEFocus(bool focus)
|
prio(urgent) sync NotifyIMEFocus(bool focus, ContentCache contentCache)
|
||||||
returns (nsIMEUpdatePreference preference);
|
returns (nsIMEUpdatePreference preference);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -181,6 +183,7 @@ parent:
|
||||||
* One call can encompass both a delete and an insert operation
|
* One call can encompass both a delete and an insert operation
|
||||||
* Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates
|
* Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates
|
||||||
*
|
*
|
||||||
|
* contentCache Cache of content
|
||||||
* offset Starting offset of the change
|
* offset Starting offset of the change
|
||||||
* end Ending offset of the range deleted
|
* end Ending offset of the range deleted
|
||||||
* newEnd New ending offset after insertion
|
* newEnd New ending offset after insertion
|
||||||
|
@ -189,43 +192,35 @@ parent:
|
||||||
* for insertion, offset == end
|
* for insertion, offset == end
|
||||||
* for deletion, offset == newEnd
|
* for deletion, offset == newEnd
|
||||||
*/
|
*/
|
||||||
prio(urgent) async NotifyIMETextChange(uint32_t offset, uint32_t end,
|
prio(urgent) async NotifyIMETextChange(ContentCache contentCache,
|
||||||
|
uint32_t offset, uint32_t end,
|
||||||
uint32_t newEnd,
|
uint32_t newEnd,
|
||||||
bool causedByComposition);
|
bool causedByComposition);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies chrome that there is a IME compostion rect updated
|
* Notifies chrome that there is a IME compostion rect updated
|
||||||
*
|
*
|
||||||
* offset The starting offset of this rect
|
* contentCache Cache of content
|
||||||
* rect The rect of first character of selected IME composition
|
|
||||||
* caretOffset The offset of caret position
|
|
||||||
* caretRect The rect of IME caret
|
|
||||||
*/
|
*/
|
||||||
prio(urgent) async NotifyIMESelectedCompositionRect(uint32_t offset,
|
prio(urgent) async NotifyIMESelectedCompositionRect(ContentCache contentCache);
|
||||||
LayoutDeviceIntRect[] rect,
|
|
||||||
uint32_t caretOffset,
|
|
||||||
LayoutDeviceIntRect caretRect);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies chrome that there has been a change in selection
|
* Notifies chrome that there has been a change in selection
|
||||||
* Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates
|
* Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates
|
||||||
*
|
*
|
||||||
* anchor Offset where the selection started
|
* contentCache Cache of content
|
||||||
* focus Offset where the caret is
|
|
||||||
* writingMode CSS writing-mode in effect at the focus
|
|
||||||
* causedByComposition true if the change is caused by composition
|
* causedByComposition true if the change is caused by composition
|
||||||
*/
|
*/
|
||||||
prio(urgent) async NotifyIMESelection(uint32_t anchor,
|
prio(urgent) async NotifyIMESelection(ContentCache contentCache,
|
||||||
uint32_t focus,
|
|
||||||
WritingMode writingMode,
|
|
||||||
bool causedByComposition);
|
bool causedByComposition);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies chrome to refresh its text cache
|
* Notifies chrome of updating its content cache.
|
||||||
|
* This is useful if content is modified but we don't need to notify IME.
|
||||||
*
|
*
|
||||||
* text The entire content of the text field
|
* contentCache Cache of content
|
||||||
*/
|
*/
|
||||||
prio(urgent) async NotifyIMETextHint(nsString text);
|
prio(urgent) async UpdateContentCache(ContentCache contentCache);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies IME of mouse button event on a character in focused editor.
|
* Notifies IME of mouse button event on a character in focused editor.
|
||||||
|
@ -235,22 +230,12 @@ parent:
|
||||||
prio(urgent) sync NotifyIMEMouseButtonEvent(IMENotification notification)
|
prio(urgent) sync NotifyIMEMouseButtonEvent(IMENotification notification)
|
||||||
returns (bool consumedByIME);
|
returns (bool consumedByIME);
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies chrome to currect editor rect
|
|
||||||
*
|
|
||||||
* rect Rect of current focused editor
|
|
||||||
*/
|
|
||||||
prio(urgent) async NotifyIMEEditorRect(LayoutDeviceIntRect rect);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies chrome to position change
|
* Notifies chrome to position change
|
||||||
*
|
*
|
||||||
* editorRect Rect of current focused editor
|
* contentCache Cache of content
|
||||||
* compositionRects Rects of current composition string
|
|
||||||
*/
|
*/
|
||||||
prio(urgent) async NotifyIMEPositionChange(LayoutDeviceIntRect editorRect,
|
prio(urgent) async NotifyIMEPositionChange(ContentCache contentCache);
|
||||||
LayoutDeviceIntRect[] compositionRects,
|
|
||||||
LayoutDeviceIntRect caretRect);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instructs chrome to end any pending composition
|
* Instructs chrome to end any pending composition
|
||||||
|
|
|
@ -261,14 +261,6 @@ TabParent::TabParent(nsIContentParent* aManager,
|
||||||
uint32_t aChromeFlags)
|
uint32_t aChromeFlags)
|
||||||
: TabContext(aContext)
|
: TabContext(aContext)
|
||||||
, mFrameElement(nullptr)
|
, mFrameElement(nullptr)
|
||||||
, mIMESelectionAnchor(0)
|
|
||||||
, mIMESelectionFocus(0)
|
|
||||||
, mWritingMode()
|
|
||||||
, mIMEComposing(false)
|
|
||||||
, mIMECompositionEnding(false)
|
|
||||||
, mIMEEventCountAfterEnding(0)
|
|
||||||
, mIMECompositionStart(0)
|
|
||||||
, mIMECompositionRectOffset(0)
|
|
||||||
, mRect(0, 0, 0, 0)
|
, mRect(0, 0, 0, 0)
|
||||||
, mDimensions(0, 0)
|
, mDimensions(0, 0)
|
||||||
, mOrientation(0)
|
, mOrientation(0)
|
||||||
|
@ -1889,6 +1881,7 @@ TabParent::RecvHideTooltip()
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TabParent::RecvNotifyIMEFocus(const bool& aFocus,
|
TabParent::RecvNotifyIMEFocus(const bool& aFocus,
|
||||||
|
const ContentCache& aContentCache,
|
||||||
nsIMEUpdatePreference* aPreference)
|
nsIMEUpdatePreference* aPreference)
|
||||||
{
|
{
|
||||||
nsCOMPtr<nsIWidget> widget = GetWidget();
|
nsCOMPtr<nsIWidget> widget = GetWidget();
|
||||||
|
@ -1898,21 +1891,20 @@ TabParent::RecvNotifyIMEFocus(const bool& aFocus,
|
||||||
}
|
}
|
||||||
|
|
||||||
mIMETabParent = aFocus ? this : nullptr;
|
mIMETabParent = aFocus ? this : nullptr;
|
||||||
mIMESelectionAnchor = 0;
|
IMENotification notification(aFocus ? NOTIFY_IME_OF_FOCUS :
|
||||||
mIMESelectionFocus = 0;
|
NOTIFY_IME_OF_BLUR);
|
||||||
widget->NotifyIME(IMENotification(aFocus ? NOTIFY_IME_OF_FOCUS :
|
mContentCache.AssignContent(aContentCache, ¬ification);
|
||||||
NOTIFY_IME_OF_BLUR));
|
widget->NotifyIME(notification);
|
||||||
|
|
||||||
if (aFocus) {
|
if (aFocus) {
|
||||||
*aPreference = widget->GetIMEUpdatePreference();
|
*aPreference = widget->GetIMEUpdatePreference();
|
||||||
} else {
|
|
||||||
mIMECacheText.Truncate(0);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TabParent::RecvNotifyIMETextChange(const uint32_t& aStart,
|
TabParent::RecvNotifyIMETextChange(const ContentCache& aContentCache,
|
||||||
|
const uint32_t& aStart,
|
||||||
const uint32_t& aEnd,
|
const uint32_t& aEnd,
|
||||||
const uint32_t& aNewEnd,
|
const uint32_t& aNewEnd,
|
||||||
const bool& aCausedByComposition)
|
const bool& aCausedByComposition)
|
||||||
|
@ -1935,59 +1927,45 @@ TabParent::RecvNotifyIMETextChange(const uint32_t& aStart,
|
||||||
notification.mTextChangeData.mOldEndOffset = aEnd;
|
notification.mTextChangeData.mOldEndOffset = aEnd;
|
||||||
notification.mTextChangeData.mNewEndOffset = aNewEnd;
|
notification.mTextChangeData.mNewEndOffset = aNewEnd;
|
||||||
notification.mTextChangeData.mCausedByComposition = aCausedByComposition;
|
notification.mTextChangeData.mCausedByComposition = aCausedByComposition;
|
||||||
|
|
||||||
|
mContentCache.AssignContent(aContentCache, ¬ification);
|
||||||
widget->NotifyIME(notification);
|
widget->NotifyIME(notification);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TabParent::RecvNotifyIMESelectedCompositionRect(
|
TabParent::RecvNotifyIMESelectedCompositionRect(
|
||||||
const uint32_t& aOffset,
|
const ContentCache& aContentCache)
|
||||||
InfallibleTArray<LayoutDeviceIntRect>&& aRects,
|
|
||||||
const uint32_t& aCaretOffset,
|
|
||||||
const LayoutDeviceIntRect& aCaretRect)
|
|
||||||
{
|
{
|
||||||
// add rect to cache for another query
|
|
||||||
mIMECompositionRectOffset = aOffset;
|
|
||||||
mIMECompositionRects = aRects;
|
|
||||||
mIMECaretOffset = aCaretOffset;
|
|
||||||
mIMECaretRect = aCaretRect;
|
|
||||||
|
|
||||||
nsCOMPtr<nsIWidget> widget = GetWidget();
|
nsCOMPtr<nsIWidget> widget = GetWidget();
|
||||||
if (!widget) {
|
if (!widget) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
widget->NotifyIME(IMENotification(NOTIFY_IME_OF_COMPOSITION_UPDATE));
|
|
||||||
|
IMENotification notification(NOTIFY_IME_OF_COMPOSITION_UPDATE);
|
||||||
|
mContentCache.AssignContent(aContentCache, ¬ification);
|
||||||
|
|
||||||
|
widget->NotifyIME(notification);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TabParent::RecvNotifyIMESelection(const uint32_t& aAnchor,
|
TabParent::RecvNotifyIMESelection(const ContentCache& aContentCache,
|
||||||
const uint32_t& aFocus,
|
|
||||||
const mozilla::WritingMode& aWritingMode,
|
|
||||||
const bool& aCausedByComposition)
|
const bool& aCausedByComposition)
|
||||||
{
|
{
|
||||||
nsCOMPtr<nsIWidget> widget = GetWidget();
|
nsCOMPtr<nsIWidget> widget = GetWidget();
|
||||||
if (!widget)
|
if (!widget)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
mIMESelectionAnchor = aAnchor;
|
IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
|
||||||
mIMESelectionFocus = aFocus;
|
mContentCache.AssignContent(aContentCache, ¬ification);
|
||||||
mWritingMode = aWritingMode;
|
|
||||||
const nsIMEUpdatePreference updatePreference =
|
const nsIMEUpdatePreference updatePreference =
|
||||||
widget->GetIMEUpdatePreference();
|
widget->GetIMEUpdatePreference();
|
||||||
if (updatePreference.WantSelectionChange() &&
|
if (updatePreference.WantSelectionChange() &&
|
||||||
(updatePreference.WantChangesCausedByComposition() ||
|
(updatePreference.WantChangesCausedByComposition() ||
|
||||||
!aCausedByComposition)) {
|
!aCausedByComposition)) {
|
||||||
IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
|
mContentCache.InitNotification(notification);
|
||||||
notification.mSelectionChangeData.mOffset =
|
|
||||||
std::min(mIMESelectionAnchor, mIMESelectionFocus);
|
|
||||||
notification.mSelectionChangeData.mLength =
|
|
||||||
mIMESelectionAnchor > mIMESelectionFocus ?
|
|
||||||
mIMESelectionAnchor - mIMESelectionFocus :
|
|
||||||
mIMESelectionFocus - mIMESelectionAnchor;
|
|
||||||
notification.mSelectionChangeData.mReversed =
|
|
||||||
mIMESelectionFocus < mIMESelectionAnchor;
|
|
||||||
notification.mSelectionChangeData.SetWritingMode(mWritingMode);
|
|
||||||
notification.mSelectionChangeData.mCausedByComposition =
|
notification.mSelectionChangeData.mCausedByComposition =
|
||||||
aCausedByComposition;
|
aCausedByComposition;
|
||||||
widget->NotifyIME(notification);
|
widget->NotifyIME(notification);
|
||||||
|
@ -1996,10 +1974,14 @@ TabParent::RecvNotifyIMESelection(const uint32_t& aAnchor,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TabParent::RecvNotifyIMETextHint(const nsString& aText)
|
TabParent::RecvUpdateContentCache(const ContentCache& aContentCache)
|
||||||
{
|
{
|
||||||
// Replace our cache with new text
|
nsCOMPtr<nsIWidget> widget = GetWidget();
|
||||||
mIMECacheText = aText;
|
if (!widget) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mContentCache.AssignContent(aContentCache);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2020,31 +2002,20 @@ TabParent::RecvNotifyIMEMouseButtonEvent(
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TabParent::RecvNotifyIMEEditorRect(const LayoutDeviceIntRect& aRect)
|
TabParent::RecvNotifyIMEPositionChange(const ContentCache& aContentCache)
|
||||||
{
|
{
|
||||||
mIMEEditorRect = aRect;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
TabParent::RecvNotifyIMEPositionChange(
|
|
||||||
const LayoutDeviceIntRect& aEditorRect,
|
|
||||||
InfallibleTArray<LayoutDeviceIntRect>&& aCompositionRects,
|
|
||||||
const LayoutDeviceIntRect& aCaretRect)
|
|
||||||
{
|
|
||||||
mIMEEditorRect = aEditorRect;
|
|
||||||
mIMECompositionRects = aCompositionRects;
|
|
||||||
mIMECaretRect = aCaretRect;
|
|
||||||
|
|
||||||
nsCOMPtr<nsIWidget> widget = GetWidget();
|
nsCOMPtr<nsIWidget> widget = GetWidget();
|
||||||
if (!widget) {
|
if (!widget) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IMENotification notification(NOTIFY_IME_OF_POSITION_CHANGE);
|
||||||
|
mContentCache.AssignContent(aContentCache, ¬ification);
|
||||||
|
|
||||||
const nsIMEUpdatePreference updatePreference =
|
const nsIMEUpdatePreference updatePreference =
|
||||||
widget->GetIMEUpdatePreference();
|
widget->GetIMEUpdatePreference();
|
||||||
if (updatePreference.WantPositionChanged()) {
|
if (updatePreference.WantPositionChanged()) {
|
||||||
widget->NotifyIME(IMENotification(NOTIFY_IME_OF_POSITION_CHANGE));
|
widget->NotifyIME(notification);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2194,125 +2165,25 @@ TabParent::RecvDispatchAfterKeyboardEvent(const WidgetKeyboardEvent& aEvent)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to answer query event using cached text.
|
|
||||||
*
|
|
||||||
* For NS_QUERY_SELECTED_TEXT, fail if the cache doesn't contain the whole
|
|
||||||
* selected range. (This shouldn't happen because PuppetWidget should have
|
|
||||||
* already sent the whole selection.)
|
|
||||||
*
|
|
||||||
* For NS_QUERY_TEXT_CONTENT, fail only if the cache doesn't overlap with
|
|
||||||
* the queried range. Note the difference from above. We use
|
|
||||||
* this behavior because a normal NS_QUERY_TEXT_CONTENT event is allowed to
|
|
||||||
* have out-of-bounds offsets, so that widget can request content without
|
|
||||||
* knowing the exact length of text. It's up to widget to handle cases when
|
|
||||||
* the returned offset/length are different from the queried offset/length.
|
|
||||||
*
|
|
||||||
* For NS_QUERY_TEXT_RECT, fail if cached offset/length aren't equals to input.
|
|
||||||
* Cocoa widget always queries selected offset, so it works on it.
|
|
||||||
*
|
|
||||||
* For NS_QUERY_CARET_RECT, fail if cached offset isn't equals to input
|
|
||||||
*
|
|
||||||
* For NS_QUERY_EDITOR_RECT, always success
|
|
||||||
*/
|
|
||||||
bool
|
bool
|
||||||
TabParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent)
|
TabParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent)
|
||||||
{
|
{
|
||||||
aEvent.mSucceeded = false;
|
nsCOMPtr<nsIWidget> widget = GetWidget();
|
||||||
aEvent.mWasAsync = false;
|
if (!widget) {
|
||||||
aEvent.mReply.mFocusedWidget = nsCOMPtr<nsIWidget>(GetWidget()).get();
|
return true;
|
||||||
|
}
|
||||||
switch (aEvent.message)
|
if (NS_WARN_IF(!mContentCache.HandleQueryContentEvent(aEvent, widget)) ||
|
||||||
{
|
NS_WARN_IF(!aEvent.mSucceeded)) {
|
||||||
case NS_QUERY_SELECTED_TEXT:
|
return true;
|
||||||
{
|
}
|
||||||
aEvent.mReply.mOffset = std::min(mIMESelectionAnchor, mIMESelectionFocus);
|
switch (aEvent.message) {
|
||||||
if (mIMESelectionAnchor == mIMESelectionFocus) {
|
case NS_QUERY_TEXT_RECT:
|
||||||
aEvent.mReply.mString.Truncate(0);
|
case NS_QUERY_CARET_RECT:
|
||||||
} else {
|
case NS_QUERY_EDITOR_RECT:
|
||||||
if (mIMESelectionAnchor > mIMECacheText.Length() ||
|
aEvent.mReply.mRect -= GetChildProcessOffset();
|
||||||
mIMESelectionFocus > mIMECacheText.Length()) {
|
break;
|
||||||
break;
|
default:
|
||||||
}
|
break;
|
||||||
uint32_t selLen = mIMESelectionAnchor > mIMESelectionFocus ?
|
|
||||||
mIMESelectionAnchor - mIMESelectionFocus :
|
|
||||||
mIMESelectionFocus - mIMESelectionAnchor;
|
|
||||||
aEvent.mReply.mString = Substring(mIMECacheText,
|
|
||||||
aEvent.mReply.mOffset,
|
|
||||||
selLen);
|
|
||||||
}
|
|
||||||
aEvent.mReply.mReversed = mIMESelectionFocus < mIMESelectionAnchor;
|
|
||||||
aEvent.mReply.mHasSelection = true;
|
|
||||||
aEvent.mReply.mWritingMode = mWritingMode;
|
|
||||||
aEvent.mSucceeded = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case NS_QUERY_TEXT_CONTENT:
|
|
||||||
{
|
|
||||||
uint32_t inputOffset = aEvent.mInput.mOffset,
|
|
||||||
inputEnd = inputOffset + aEvent.mInput.mLength;
|
|
||||||
|
|
||||||
if (inputEnd > mIMECacheText.Length()) {
|
|
||||||
inputEnd = mIMECacheText.Length();
|
|
||||||
}
|
|
||||||
if (inputEnd < inputOffset) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
aEvent.mReply.mOffset = inputOffset;
|
|
||||||
aEvent.mReply.mString = Substring(mIMECacheText,
|
|
||||||
inputOffset,
|
|
||||||
inputEnd - inputOffset);
|
|
||||||
aEvent.mSucceeded = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case NS_QUERY_TEXT_RECT:
|
|
||||||
{
|
|
||||||
if (aEvent.mInput.mOffset < mIMECompositionRectOffset ||
|
|
||||||
(aEvent.mInput.mOffset + aEvent.mInput.mLength >
|
|
||||||
mIMECompositionRectOffset + mIMECompositionRects.Length())) {
|
|
||||||
// XXX
|
|
||||||
// we doesn't have cache for this request.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t baseOffset = aEvent.mInput.mOffset - mIMECompositionRectOffset;
|
|
||||||
uint32_t endOffset = baseOffset + aEvent.mInput.mLength;
|
|
||||||
aEvent.mReply.mRect.SetEmpty();
|
|
||||||
for (uint32_t i = baseOffset; i < endOffset; i++) {
|
|
||||||
aEvent.mReply.mRect =
|
|
||||||
aEvent.mReply.mRect.Union(mIMECompositionRects[i]);
|
|
||||||
}
|
|
||||||
if (aEvent.mInput.mOffset < mIMECacheText.Length()) {
|
|
||||||
aEvent.mReply.mString =
|
|
||||||
Substring(mIMECacheText, aEvent.mInput.mOffset,
|
|
||||||
mIMECacheText.Length() >= aEvent.mInput.EndOffset() ?
|
|
||||||
aEvent.mInput.mLength : UINT32_MAX);
|
|
||||||
} else {
|
|
||||||
aEvent.mReply.mString.Truncate();
|
|
||||||
}
|
|
||||||
aEvent.mReply.mOffset = aEvent.mInput.mOffset;
|
|
||||||
aEvent.mReply.mRect = aEvent.mReply.mRect - GetChildProcessOffset();
|
|
||||||
aEvent.mReply.mWritingMode = mWritingMode;
|
|
||||||
aEvent.mSucceeded = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case NS_QUERY_CARET_RECT:
|
|
||||||
{
|
|
||||||
if (aEvent.mInput.mOffset != mIMECaretOffset) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
aEvent.mReply.mOffset = mIMECaretOffset;
|
|
||||||
aEvent.mReply.mRect = mIMECaretRect - GetChildProcessOffset();
|
|
||||||
aEvent.mSucceeded = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case NS_QUERY_EDITOR_RECT:
|
|
||||||
{
|
|
||||||
aEvent.mReply.mRect = mIMEEditorRect - GetChildProcessOffset();
|
|
||||||
aEvent.mSucceeded = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2324,56 +2195,22 @@ TabParent::SendCompositionEvent(WidgetCompositionEvent& event)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.CausesDOMTextEvent()) {
|
if (!mContentCache.OnCompositionEvent(event)) {
|
||||||
return SendCompositionChangeEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
mIMEComposing = !event.CausesDOMCompositionEndEvent();
|
|
||||||
mIMECompositionStart = std::min(mIMESelectionAnchor, mIMESelectionFocus);
|
|
||||||
if (mIMECompositionEnding) {
|
|
||||||
mIMEEventCountAfterEnding++;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return PBrowserParent::SendCompositionEvent(event);
|
return PBrowserParent::SendCompositionEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
|
|
||||||
* widget usually sends a NS_COMPOSITION_CHANGE event to finalize or
|
|
||||||
* clear the composition, respectively
|
|
||||||
*
|
|
||||||
* Because the event will not reach content in time, we intercept it
|
|
||||||
* here and pass the text as the EndIMEComposition return value
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
TabParent::SendCompositionChangeEvent(WidgetCompositionEvent& event)
|
|
||||||
{
|
|
||||||
if (mIMECompositionEnding) {
|
|
||||||
mIMECompositionText = event.mData;
|
|
||||||
mIMEEventCountAfterEnding++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We must be able to simulate the selection because
|
|
||||||
// we might not receive selection updates in time
|
|
||||||
if (!mIMEComposing) {
|
|
||||||
mIMECompositionStart = std::min(mIMESelectionAnchor, mIMESelectionFocus);
|
|
||||||
}
|
|
||||||
mIMESelectionAnchor = mIMESelectionFocus =
|
|
||||||
mIMECompositionStart + event.mData.Length();
|
|
||||||
mIMEComposing = !event.CausesDOMCompositionEndEvent();
|
|
||||||
|
|
||||||
return PBrowserParent::SendCompositionEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TabParent::SendSelectionEvent(WidgetSelectionEvent& event)
|
TabParent::SendSelectionEvent(WidgetSelectionEvent& event)
|
||||||
{
|
{
|
||||||
if (mIsDestroyed) {
|
if (mIsDestroyed) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
mIMESelectionAnchor = event.mOffset + (event.mReversed ? event.mLength : 0);
|
nsCOMPtr<nsIWidget> widget = GetWidget();
|
||||||
mIMESelectionFocus = event.mOffset + (!event.mReversed ? event.mLength : 0);
|
if (!widget) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return PBrowserParent::SendSelectionEvent(event);
|
return PBrowserParent::SendSelectionEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2443,19 +2280,11 @@ TabParent::RecvEndIMEComposition(const bool& aCancel,
|
||||||
nsString* aComposition)
|
nsString* aComposition)
|
||||||
{
|
{
|
||||||
nsCOMPtr<nsIWidget> widget = GetWidget();
|
nsCOMPtr<nsIWidget> widget = GetWidget();
|
||||||
if (!widget)
|
if (!widget) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
mIMECompositionEnding = true;
|
*aNoCompositionEvent =
|
||||||
mIMEEventCountAfterEnding = 0;
|
!mContentCache.RequestToCommitComposition(widget, aCancel, *aComposition);
|
||||||
|
|
||||||
widget->NotifyIME(IMENotification(aCancel ? REQUEST_TO_CANCEL_COMPOSITION :
|
|
||||||
REQUEST_TO_COMMIT_COMPOSITION));
|
|
||||||
|
|
||||||
mIMECompositionEnding = false;
|
|
||||||
*aNoCompositionEvent = !mIMEEventCountAfterEnding;
|
|
||||||
*aComposition = mIMECompositionText;
|
|
||||||
mIMECompositionText.Truncate(0);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,13 @@
|
||||||
#define mozilla_tabs_TabParent_h
|
#define mozilla_tabs_TabParent_h
|
||||||
|
|
||||||
#include "js/TypeDecls.h"
|
#include "js/TypeDecls.h"
|
||||||
|
#include "mozilla/ContentCache.h"
|
||||||
#include "mozilla/dom/ipc/IdType.h"
|
#include "mozilla/dom/ipc/IdType.h"
|
||||||
#include "mozilla/dom/PBrowserParent.h"
|
#include "mozilla/dom/PBrowserParent.h"
|
||||||
#include "mozilla/dom/PFilePickerParent.h"
|
#include "mozilla/dom/PFilePickerParent.h"
|
||||||
#include "mozilla/dom/TabContext.h"
|
#include "mozilla/dom/TabContext.h"
|
||||||
#include "mozilla/EventForwards.h"
|
#include "mozilla/EventForwards.h"
|
||||||
#include "mozilla/dom/File.h"
|
#include "mozilla/dom/File.h"
|
||||||
#include "mozilla/WritingModes.h"
|
|
||||||
#include "mozilla/RefPtr.h"
|
#include "mozilla/RefPtr.h"
|
||||||
#include "nsCOMPtr.h"
|
#include "nsCOMPtr.h"
|
||||||
#include "nsIAuthPromptProvider.h"
|
#include "nsIAuthPromptProvider.h"
|
||||||
|
@ -161,29 +161,21 @@ public:
|
||||||
InfallibleTArray<CpowEntry>&& aCpows,
|
InfallibleTArray<CpowEntry>&& aCpows,
|
||||||
const IPC::Principal& aPrincipal) override;
|
const IPC::Principal& aPrincipal) override;
|
||||||
virtual bool RecvNotifyIMEFocus(const bool& aFocus,
|
virtual bool RecvNotifyIMEFocus(const bool& aFocus,
|
||||||
|
const ContentCache& aContentCache,
|
||||||
nsIMEUpdatePreference* aPreference)
|
nsIMEUpdatePreference* aPreference)
|
||||||
override;
|
override;
|
||||||
virtual bool RecvNotifyIMETextChange(const uint32_t& aStart,
|
virtual bool RecvNotifyIMETextChange(const ContentCache& aContentCache,
|
||||||
|
const uint32_t& aStart,
|
||||||
const uint32_t& aEnd,
|
const uint32_t& aEnd,
|
||||||
const uint32_t& aNewEnd,
|
const uint32_t& aNewEnd,
|
||||||
const bool& aCausedByComposition) override;
|
const bool& aCausedByComposition) override;
|
||||||
virtual bool RecvNotifyIMESelectedCompositionRect(
|
virtual bool RecvNotifyIMESelectedCompositionRect(const ContentCache& aContentCache) override;
|
||||||
const uint32_t& aOffset,
|
virtual bool RecvNotifyIMESelection(const ContentCache& aContentCache,
|
||||||
InfallibleTArray<LayoutDeviceIntRect>&& aRects,
|
|
||||||
const uint32_t& aCaretOffset,
|
|
||||||
const LayoutDeviceIntRect& aCaretRect) override;
|
|
||||||
virtual bool RecvNotifyIMESelection(const uint32_t& aAnchor,
|
|
||||||
const uint32_t& aFocus,
|
|
||||||
const mozilla::WritingMode& aWritingMode,
|
|
||||||
const bool& aCausedByComposition) override;
|
const bool& aCausedByComposition) override;
|
||||||
virtual bool RecvNotifyIMETextHint(const nsString& aText) override;
|
virtual bool RecvUpdateContentCache(const ContentCache& aContentCache) override;
|
||||||
virtual bool RecvNotifyIMEMouseButtonEvent(const widget::IMENotification& aEventMessage,
|
virtual bool RecvNotifyIMEMouseButtonEvent(const widget::IMENotification& aEventMessage,
|
||||||
bool* aConsumedByIME) override;
|
bool* aConsumedByIME) override;
|
||||||
virtual bool RecvNotifyIMEEditorRect(const LayoutDeviceIntRect& aRect) override;
|
virtual bool RecvNotifyIMEPositionChange(const ContentCache& aContentCache) override;
|
||||||
virtual bool RecvNotifyIMEPositionChange(
|
|
||||||
const LayoutDeviceIntRect& aEditorRect,
|
|
||||||
InfallibleTArray<LayoutDeviceIntRect>&& aCompositionRects,
|
|
||||||
const LayoutDeviceIntRect& aCaretRect) override;
|
|
||||||
virtual bool RecvEndIMEComposition(const bool& aCancel,
|
virtual bool RecvEndIMEComposition(const bool& aCancel,
|
||||||
bool* aNoCompositionEvent,
|
bool* aNoCompositionEvent,
|
||||||
nsString* aComposition) override;
|
nsString* aComposition) override;
|
||||||
|
@ -469,8 +461,6 @@ protected:
|
||||||
const int32_t& aX, const int32_t& aY,
|
const int32_t& aX, const int32_t& aY,
|
||||||
const int32_t& aCx, const int32_t& aCy) override;
|
const int32_t& aCx, const int32_t& aCy) override;
|
||||||
|
|
||||||
bool SendCompositionChangeEvent(mozilla::WidgetCompositionEvent& event);
|
|
||||||
|
|
||||||
bool InitBrowserConfiguration(const nsCString& aURI,
|
bool InitBrowserConfiguration(const nsCString& aURI,
|
||||||
BrowserConfiguration& aConfiguration);
|
BrowserConfiguration& aConfiguration);
|
||||||
|
|
||||||
|
@ -478,23 +468,7 @@ protected:
|
||||||
|
|
||||||
// IME
|
// IME
|
||||||
static TabParent *mIMETabParent;
|
static TabParent *mIMETabParent;
|
||||||
nsString mIMECacheText;
|
ContentCache mContentCache;
|
||||||
uint32_t mIMESelectionAnchor;
|
|
||||||
uint32_t mIMESelectionFocus;
|
|
||||||
mozilla::WritingMode mWritingMode;
|
|
||||||
bool mIMEComposing;
|
|
||||||
bool mIMECompositionEnding;
|
|
||||||
uint32_t mIMEEventCountAfterEnding;
|
|
||||||
// Buffer to store composition text during ResetInputState
|
|
||||||
// Compositions in almost all cases are small enough for nsAutoString
|
|
||||||
nsAutoString mIMECompositionText;
|
|
||||||
uint32_t mIMECompositionStart;
|
|
||||||
|
|
||||||
uint32_t mIMECompositionRectOffset;
|
|
||||||
InfallibleTArray<LayoutDeviceIntRect> mIMECompositionRects;
|
|
||||||
uint32_t mIMECaretOffset;
|
|
||||||
LayoutDeviceIntRect mIMECaretRect;
|
|
||||||
LayoutDeviceIntRect mIMEEditorRect;
|
|
||||||
|
|
||||||
nsIntRect mRect;
|
nsIntRect mRect;
|
||||||
ScreenIntSize mDimensions;
|
ScreenIntSize mDimensions;
|
||||||
|
|
|
@ -75,6 +75,9 @@
|
||||||
#endif
|
#endif
|
||||||
#include "MediaFormatReader.h"
|
#include "MediaFormatReader.h"
|
||||||
|
|
||||||
|
#include "MP3Decoder.h"
|
||||||
|
#include "MP3Demuxer.h"
|
||||||
|
|
||||||
namespace mozilla
|
namespace mozilla
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -358,6 +361,12 @@ IsMP4SupportedType(const nsACString& aType,
|
||||||
MP4Decoder::CanHandleMediaType(aType, aCodecs, haveAAC, haveH264, haveMP3);
|
MP4Decoder::CanHandleMediaType(aType, aCodecs, haveAAC, haveH264, haveMP3);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
static bool
|
||||||
|
IsMP3SupportedType(const nsACString& aType,
|
||||||
|
const nsAString& aCodecs = EmptyString())
|
||||||
|
{
|
||||||
|
return aType.EqualsASCII("audio/mpeg") && MP3Decoder::IsEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef MOZ_APPLEMEDIA
|
#ifdef MOZ_APPLEMEDIA
|
||||||
static const char * const gAppleMP3Types[] = {
|
static const char * const gAppleMP3Types[] = {
|
||||||
|
@ -444,6 +453,10 @@ DecoderTraits::CanHandleMediaType(const char* aMIMEType,
|
||||||
return aHaveRequestedCodecs ? CANPLAY_YES : CANPLAY_MAYBE;
|
return aHaveRequestedCodecs ? CANPLAY_YES : CANPLAY_MAYBE;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if (IsMP3SupportedType(nsDependentCString(aMIMEType),
|
||||||
|
aRequestedCodecs)) {
|
||||||
|
return aHaveRequestedCodecs ? CANPLAY_YES : CANPLAY_MAYBE;
|
||||||
|
}
|
||||||
#ifdef MOZ_GSTREAMER
|
#ifdef MOZ_GSTREAMER
|
||||||
if (GStreamerDecoder::CanHandleMediaType(nsDependentCString(aMIMEType),
|
if (GStreamerDecoder::CanHandleMediaType(nsDependentCString(aMIMEType),
|
||||||
aHaveRequestedCodecs ? &aRequestedCodecs : nullptr)) {
|
aHaveRequestedCodecs ? &aRequestedCodecs : nullptr)) {
|
||||||
|
@ -538,6 +551,10 @@ InstantiateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner)
|
||||||
return decoder.forget();
|
return decoder.forget();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if (IsMP3SupportedType(aType)) {
|
||||||
|
decoder = new MP3Decoder();
|
||||||
|
return decoder.forget();
|
||||||
|
}
|
||||||
#ifdef MOZ_GSTREAMER
|
#ifdef MOZ_GSTREAMER
|
||||||
if (IsGStreamerSupportedType(aType)) {
|
if (IsGStreamerSupportedType(aType)) {
|
||||||
decoder = new GStreamerDecoder();
|
decoder = new GStreamerDecoder();
|
||||||
|
@ -666,6 +683,9 @@ MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, Abstrac
|
||||||
static_cast<MediaDecoderReader*>(new MP4Reader(aDecoder));
|
static_cast<MediaDecoderReader*>(new MP4Reader(aDecoder));
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
|
if (IsMP3SupportedType(aType)) {
|
||||||
|
decoderReader = new MediaFormatReader(aDecoder, new mp3::MP3Demuxer(aDecoder->GetResource()));
|
||||||
|
} else
|
||||||
#ifdef MOZ_GSTREAMER
|
#ifdef MOZ_GSTREAMER
|
||||||
if (IsGStreamerSupportedType(aType)) {
|
if (IsGStreamerSupportedType(aType)) {
|
||||||
decoderReader = new GStreamerReader(aDecoder);
|
decoderReader = new GStreamerReader(aDecoder);
|
||||||
|
@ -760,6 +780,7 @@ bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType)
|
||||||
#ifdef MOZ_FMP4
|
#ifdef MOZ_FMP4
|
||||||
IsMP4SupportedType(aType) ||
|
IsMP4SupportedType(aType) ||
|
||||||
#endif
|
#endif
|
||||||
|
IsMP3SupportedType(aType) ||
|
||||||
#ifdef MOZ_WMF
|
#ifdef MOZ_WMF
|
||||||
IsWMFSupportedType(aType) ||
|
IsWMFSupportedType(aType) ||
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include "MP3Decoder.h"
|
||||||
|
#include "MediaDecoderStateMachine.h"
|
||||||
|
#include "MediaFormatReader.h"
|
||||||
|
#include "MP3Demuxer.h"
|
||||||
|
#include "mozilla/Preferences.h"
|
||||||
|
|
||||||
|
#ifdef MOZ_WIDGET_ANDROID
|
||||||
|
#include "AndroidBridge.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
|
||||||
|
MediaDecoder*
|
||||||
|
MP3Decoder::Clone() {
|
||||||
|
if (!IsEnabled()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return new MP3Decoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaDecoderStateMachine*
|
||||||
|
MP3Decoder::CreateStateMachine() {
|
||||||
|
nsRefPtr<MediaDecoderReader> reader =
|
||||||
|
new MediaFormatReader(this, new mp3::MP3Demuxer(GetResource()));
|
||||||
|
return new MediaDecoderStateMachine(this, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MP3Decoder::IsEnabled() {
|
||||||
|
#ifdef MOZ_WIDGET_ANDROID
|
||||||
|
// We need android.media.MediaCodec which exists in API level 16 and higher.
|
||||||
|
return AndroidBridge::Bridge()->GetAPIVersion() >= 16;
|
||||||
|
#else
|
||||||
|
return Preferences::GetBool("media.mp3.enabled");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mozilla
|
|
@ -0,0 +1,26 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
#ifndef MP3Decoder_h_
|
||||||
|
#define MP3Decoder_h_
|
||||||
|
|
||||||
|
#include "MediaDecoder.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
|
||||||
|
class MP3Decoder : public MediaDecoder {
|
||||||
|
public:
|
||||||
|
// MediaDecoder interface.
|
||||||
|
MediaDecoder* Clone() override;
|
||||||
|
MediaDecoderStateMachine* CreateStateMachine() override;
|
||||||
|
|
||||||
|
// Returns true if the MP3 backend is preffed on, and we're running on a
|
||||||
|
// platform that is likely to have decoders for the format.
|
||||||
|
static bool IsEnabled();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
|
#endif
|
|
@ -4,20 +4,98 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "mp4_demuxer/MP3TrackDemuxer.h"
|
#include "MP3Demuxer.h"
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "mozilla/Assertions.h"
|
#include "mozilla/Assertions.h"
|
||||||
#include "mozilla/Endian.h"
|
#include "mozilla/Endian.h"
|
||||||
#include "VideoUtils.h"
|
#include "VideoUtils.h"
|
||||||
|
#include "TimeUnits.h"
|
||||||
|
|
||||||
namespace mp4_demuxer {
|
using media::TimeUnit;
|
||||||
|
using media::TimeIntervals;
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace mp3 {
|
||||||
|
|
||||||
// MP3Demuxer
|
// MP3Demuxer
|
||||||
|
|
||||||
MP3Demuxer::MP3Demuxer(Stream* aSource)
|
MP3Demuxer::MP3Demuxer(MediaResource* aSource)
|
||||||
|
: mSource(aSource)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MP3Demuxer::InitInternal() {
|
||||||
|
if (!mTrackDemuxer) {
|
||||||
|
mTrackDemuxer = new MP3TrackDemuxer(mSource);
|
||||||
|
}
|
||||||
|
return mTrackDemuxer->Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
nsRefPtr<MP3Demuxer::InitPromise>
|
||||||
|
MP3Demuxer::Init() {
|
||||||
|
if (!InitInternal()) {
|
||||||
|
return InitPromise::CreateAndReject(
|
||||||
|
DemuxerFailureReason::WAITING_FOR_DATA, __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
return InitPromise::CreateAndResolve(NS_OK, __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
already_AddRefed<MediaDataDemuxer>
|
||||||
|
MP3Demuxer::Clone() const {
|
||||||
|
nsRefPtr<MP3Demuxer> demuxer = new MP3Demuxer(mSource);
|
||||||
|
if (!demuxer->InitInternal()) {
|
||||||
|
NS_WARNING("Couldn't recreate MP3Demuxer");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return demuxer.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MP3Demuxer::HasTrackType(TrackInfo::TrackType aType) const {
|
||||||
|
return aType == TrackInfo::kAudioTrack;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
MP3Demuxer::GetNumberTracks(TrackInfo::TrackType aType) const {
|
||||||
|
return aType == TrackInfo::kAudioTrack ? 1u : 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
already_AddRefed<MediaTrackDemuxer>
|
||||||
|
MP3Demuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber) {
|
||||||
|
if (!mTrackDemuxer) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return nsRefPtr<MP3TrackDemuxer>(mTrackDemuxer).forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MP3Demuxer::IsSeekable() const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MP3Demuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset) {
|
||||||
|
// TODO: bug 1169485.
|
||||||
|
NS_WARNING("Unimplemented function NotifyDataArrived");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MP3Demuxer::NotifyDataRemoved() {
|
||||||
|
// TODO: bug 1169485.
|
||||||
|
NS_WARNING("Unimplemented function NotifyDataRemoved");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MP3TrackDemuxer
|
||||||
|
|
||||||
|
MP3TrackDemuxer::MP3TrackDemuxer(MediaResource* aSource)
|
||||||
: mSource(aSource),
|
: mSource(aSource),
|
||||||
mOffset(0),
|
mOffset(0),
|
||||||
mFirstFrameOffset(0),
|
mFirstFrameOffset(0),
|
||||||
mStreamLength(-1),
|
|
||||||
mNumParsedFrames(0),
|
mNumParsedFrames(0),
|
||||||
mFrameIndex(0),
|
mFrameIndex(0),
|
||||||
mTotalFrameLen(0),
|
mTotalFrameLen(0),
|
||||||
|
@ -28,128 +106,199 @@ MP3Demuxer::MP3Demuxer(Stream* aSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
MP3Demuxer::Init() {
|
MP3TrackDemuxer::Init() {
|
||||||
if (!mSource->Length(&mStreamLength)) {
|
FastSeek(TimeUnit());
|
||||||
// Length is unknown.
|
// Read the first frame to fetch sample rate and other meta data.
|
||||||
mStreamLength = -1;
|
nsRefPtr<MediaRawData> frame(GetNextFrame(FindNextFrame()));
|
||||||
|
if (!frame) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
// Rewind back to the stream begin to avoid dropping the first frame.
|
||||||
|
FastSeek(TimeUnit());
|
||||||
|
|
||||||
|
if (!mInfo) {
|
||||||
|
mInfo = MakeUnique<AudioInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
mInfo->mRate = mSamplesPerSecond;
|
||||||
|
mInfo->mChannels = mChannels;
|
||||||
|
mInfo->mBitDepth = 16;
|
||||||
|
mInfo->mMimeType = "audio/mpeg";
|
||||||
|
mInfo->mDuration = Duration().ToMicroseconds();
|
||||||
|
|
||||||
|
return mSamplesPerSecond && mChannels;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_TESTS
|
#ifdef ENABLE_TESTS
|
||||||
const FrameParser::Frame&
|
const FrameParser::Frame&
|
||||||
MP3Demuxer::LastFrame() const {
|
MP3TrackDemuxer::LastFrame() const {
|
||||||
return mParser.PrevFrame();
|
return mParser.PrevFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsRefPtr<MediaRawData>
|
||||||
|
MP3TrackDemuxer::DemuxSample() {
|
||||||
|
return GetNextFrame(FindNextFrame());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const ID3Parser::ID3Header&
|
const ID3Parser::ID3Header&
|
||||||
MP3Demuxer::ID3Header() const {
|
MP3TrackDemuxer::ID3Header() const {
|
||||||
return mParser.ID3Header();
|
return mParser.ID3Header();
|
||||||
}
|
}
|
||||||
|
|
||||||
const FrameParser::VBRHeader&
|
const FrameParser::VBRHeader&
|
||||||
MP3Demuxer::VBRInfo() const {
|
MP3TrackDemuxer::VBRInfo() const {
|
||||||
return mParser.VBRInfo();
|
return mParser.VBRInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
UniquePtr<TrackInfo>
|
||||||
MP3Demuxer::Seek(Microseconds aTime) {
|
MP3TrackDemuxer::GetInfo() const {
|
||||||
SlowSeek(aTime);
|
return mInfo->Clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
nsRefPtr<MP3TrackDemuxer::SeekPromise>
|
||||||
MP3Demuxer::FastSeek(Microseconds aTime) {
|
MP3TrackDemuxer::Seek(TimeUnit aTime) {
|
||||||
if (!aTime) {
|
const TimeUnit seekTime = ScanUntil(aTime);
|
||||||
|
|
||||||
|
return SeekPromise::CreateAndResolve(seekTime, __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeUnit
|
||||||
|
MP3TrackDemuxer::FastSeek(TimeUnit aTime) {
|
||||||
|
if (!aTime.ToMicroseconds()) {
|
||||||
// Quick seek to the beginning of the stream.
|
// Quick seek to the beginning of the stream.
|
||||||
mOffset = mFirstFrameOffset;
|
mOffset = mFirstFrameOffset;
|
||||||
mFrameIndex = 0;
|
mFrameIndex = 0;
|
||||||
mParser.FinishParsing();
|
mParser.FinishParsing();
|
||||||
return;
|
return TimeUnit();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mSamplesPerFrame || !mNumParsedFrames) {
|
if (!mSamplesPerFrame || !mNumParsedFrames) {
|
||||||
return;
|
return TimeUnit::FromMicroseconds(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int64_t numFrames = static_cast<double>(aTime) / USECS_PER_S *
|
const int64_t numFrames = aTime.ToSeconds() *
|
||||||
mSamplesPerSecond / mSamplesPerFrame;
|
mSamplesPerSecond / mSamplesPerFrame;
|
||||||
mOffset = mFirstFrameOffset + numFrames * AverageFrameLength();
|
mOffset = mFirstFrameOffset + numFrames * AverageFrameLength();
|
||||||
mFrameIndex = numFrames;
|
mFrameIndex = numFrames;
|
||||||
mParser.FinishParsing();
|
mParser.FinishParsing();
|
||||||
|
|
||||||
|
return Duration(mFrameIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
TimeUnit
|
||||||
MP3Demuxer::SlowSeek(Microseconds aTime) {
|
MP3TrackDemuxer::ScanUntil(TimeUnit aTime) {
|
||||||
if (!aTime) {
|
if (!aTime.ToMicroseconds()) {
|
||||||
FastSeek(aTime);
|
return FastSeek(aTime);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Duration(mFrameIndex) > aTime) {
|
if (Duration(mFrameIndex) > aTime) {
|
||||||
FastSeek(aTime);
|
FastSeek(aTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
nsRefPtr<MediaRawData> frameData(GetNext());
|
MediaByteRange nextRange = FindNextFrame();
|
||||||
while (frameData && Duration(mFrameIndex + 1) < aTime) {
|
while (SkipNextFrame(nextRange) && Duration(mFrameIndex + 1) < aTime) {
|
||||||
frameData = GetNext();
|
nextRange = FindNextFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Duration(mFrameIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsRefPtr<MP3TrackDemuxer::SamplesPromise>
|
||||||
already_AddRefed<MediaRawData>
|
MP3TrackDemuxer::GetSamples(int32_t aNumSamples) {
|
||||||
MP3Demuxer::DemuxSample() {
|
if (!aNumSamples) {
|
||||||
nsRefPtr<MediaRawData> sample(GetNext());
|
return SamplesPromise::CreateAndReject(
|
||||||
if (!sample) {
|
DemuxerFailureReason::DEMUXER_ERROR, __func__);
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
return sample.forget();
|
|
||||||
|
nsRefPtr<SamplesHolder> frames = new SamplesHolder();
|
||||||
|
|
||||||
|
while (aNumSamples--) {
|
||||||
|
nsRefPtr<MediaRawData> frame(GetNextFrame(FindNextFrame()));
|
||||||
|
if (!frame) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
frames->mSamples.AppendElement(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frames->mSamples.IsEmpty()) {
|
||||||
|
return SamplesPromise::CreateAndReject(
|
||||||
|
DemuxerFailureReason::END_OF_STREAM, __func__);
|
||||||
|
}
|
||||||
|
return SamplesPromise::CreateAndResolve(frames, __func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
Microseconds
|
void
|
||||||
MP3Demuxer::GetNextKeyframeTime() {
|
MP3TrackDemuxer::Reset() {
|
||||||
return -1;
|
FastSeek(TimeUnit());
|
||||||
|
}
|
||||||
|
|
||||||
|
nsRefPtr<MP3TrackDemuxer::SkipAccessPointPromise>
|
||||||
|
MP3TrackDemuxer::SkipToNextRandomAccessPoint(TimeUnit aTimeThreshold) {
|
||||||
|
// Will not be called for audio-only resources.
|
||||||
|
return SkipAccessPointPromise::CreateAndReject(
|
||||||
|
SkipFailureHolder(DemuxerFailureReason::DEMUXER_ERROR, 0), __func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t
|
int64_t
|
||||||
MP3Demuxer::StreamLength() const {
|
MP3TrackDemuxer::GetResourceOffset() const {
|
||||||
return mStreamLength;
|
return mOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeIntervals
|
||||||
|
MP3TrackDemuxer::GetBuffered() {
|
||||||
|
// TODO: bug 1169485.
|
||||||
|
NS_WARNING("Unimplemented function GetBuffered");
|
||||||
|
return TimeIntervals();
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t
|
int64_t
|
||||||
MP3Demuxer::Duration() const {
|
MP3TrackDemuxer::GetEvictionOffset(TimeUnit aTime) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t
|
||||||
|
MP3TrackDemuxer::StreamLength() const {
|
||||||
|
return mSource->GetLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeUnit
|
||||||
|
MP3TrackDemuxer::Duration() const {
|
||||||
if (!mNumParsedFrames) {
|
if (!mNumParsedFrames) {
|
||||||
return -1;
|
return TimeUnit::FromMicroseconds(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int64_t streamLen = StreamLength();
|
||||||
// Assume we know the exact number of frames from the VBR header.
|
// Assume we know the exact number of frames from the VBR header.
|
||||||
int64_t numFrames = mParser.VBRInfo().NumFrames();
|
int64_t numFrames = mParser.VBRInfo().NumFrames();
|
||||||
if (numFrames < 0) {
|
if (numFrames < 0) {
|
||||||
if (mStreamLength < 0) {
|
if (streamLen < 0) {
|
||||||
// Unknown length, we can't estimate duration.
|
// Unknown length, we can't estimate duration.
|
||||||
return -1;
|
return TimeUnit::FromMicroseconds(-1);
|
||||||
}
|
}
|
||||||
numFrames = (mStreamLength - mFirstFrameOffset) / AverageFrameLength();
|
numFrames = (streamLen - mFirstFrameOffset) / AverageFrameLength();
|
||||||
}
|
}
|
||||||
return Duration(numFrames);
|
return Duration(numFrames);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t
|
TimeUnit
|
||||||
MP3Demuxer::Duration(int64_t aNumFrames) const {
|
MP3TrackDemuxer::Duration(int64_t aNumFrames) const {
|
||||||
if (!mSamplesPerSecond) {
|
if (!mSamplesPerSecond) {
|
||||||
return -1;
|
return TimeUnit::FromMicroseconds(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const double usPerFrame = USECS_PER_S * mSamplesPerFrame / mSamplesPerSecond;
|
const double usPerFrame = USECS_PER_S * mSamplesPerFrame / mSamplesPerSecond;
|
||||||
return aNumFrames * usPerFrame;
|
return TimeUnit::FromMicroseconds(aNumFrames * usPerFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
already_AddRefed<mozilla::MediaRawData>
|
MediaByteRange
|
||||||
MP3Demuxer::GetNext() {
|
MP3TrackDemuxer::FindNextFrame() {
|
||||||
static const int BUFFER_SIZE = 4096;
|
static const int BUFFER_SIZE = 4096;
|
||||||
|
|
||||||
uint8_t buffer[BUFFER_SIZE];
|
uint8_t buffer[BUFFER_SIZE];
|
||||||
uint32_t read = 0;
|
int32_t read = 0;
|
||||||
const uint8_t* frameBeg = nullptr;
|
const uint8_t* frameBeg = nullptr;
|
||||||
const uint8_t* bufferEnd = nullptr;
|
const uint8_t* bufferEnd = nullptr;
|
||||||
|
|
||||||
|
@ -162,47 +311,53 @@ MP3Demuxer::GetNext() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frameBeg == bufferEnd || !mParser.CurrentFrame().Length()) {
|
if (frameBeg == bufferEnd || !mParser.CurrentFrame().Length()) {
|
||||||
|
return { 0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const int64_t nextBeg = mOffset - (bufferEnd - frameBeg) + 1;
|
||||||
|
return { nextBeg, nextBeg + mParser.CurrentFrame().Length() };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MP3TrackDemuxer::SkipNextFrame(const MediaByteRange& aRange) {
|
||||||
|
if (!mNumParsedFrames || !aRange.Length()) {
|
||||||
|
// We can't skip the first frame, since it could contain VBR headers.
|
||||||
|
nsRefPtr<MediaRawData> frame(GetNextFrame(aRange));
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateState(aRange);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
already_AddRefed<MediaRawData>
|
||||||
|
MP3TrackDemuxer::GetNextFrame(const MediaByteRange& aRange) {
|
||||||
|
if (!aRange.Length()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid frame header was fully parsed, let's read the whole frame.
|
|
||||||
const int32_t frameLen = mParser.CurrentFrame().Length();
|
|
||||||
nsRefPtr<MediaRawData> frame = new MediaRawData();
|
nsRefPtr<MediaRawData> frame = new MediaRawData();
|
||||||
frame->mOffset = mOffset - (bufferEnd - frameBeg) + 1;
|
frame->mOffset = aRange.mStart;
|
||||||
|
|
||||||
nsAutoPtr<MediaRawDataWriter> frameWriter(frame->CreateWriter());
|
nsAutoPtr<MediaRawDataWriter> frameWriter(frame->CreateWriter());
|
||||||
if (!frameWriter->SetSize(frameLen)) {
|
if (!frameWriter->SetSize(aRange.Length())) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
read = Read(frameWriter->mData, frame->mOffset, frame->mSize);
|
const uint32_t read = Read(frameWriter->mData, frame->mOffset, frame->mSize);
|
||||||
|
|
||||||
if (read != frame->mSize) {
|
if (read != aRange.Length()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent overflow.
|
UpdateState(aRange);
|
||||||
if (mTotalFrameLen + frameLen < 0) {
|
|
||||||
// These variables have a linear dependency and are only used to derive the
|
|
||||||
// average frame length.
|
|
||||||
mTotalFrameLen /= 2;
|
|
||||||
mNumParsedFrames /= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full frame parsed, move offset to its end.
|
frame->mTime = Duration(mFrameIndex - 1).ToMicroseconds();
|
||||||
mOffset = frame->mOffset + frame->mSize;
|
frame->mDuration = Duration(1).ToMicroseconds();
|
||||||
MOZ_ASSERT(mOffset > frame->mOffset);
|
|
||||||
|
|
||||||
mTotalFrameLen += frameLen;
|
MOZ_ASSERT(frame->mTime >= 0);
|
||||||
mSamplesPerFrame = mParser.CurrentFrame().Header().SamplesPerFrame();
|
MOZ_ASSERT(frame->mDuration > 0);
|
||||||
mSamplesPerSecond = mParser.CurrentFrame().Header().SampleRate();
|
|
||||||
mChannels = mParser.CurrentFrame().Header().Channels();
|
|
||||||
++mNumParsedFrames;
|
|
||||||
++mFrameIndex;
|
|
||||||
MOZ_ASSERT(mFrameIndex > 0);
|
|
||||||
|
|
||||||
frame->mTime = Duration(mFrameIndex - 1);
|
|
||||||
frame->mDuration = Duration(1);
|
|
||||||
|
|
||||||
if (mNumParsedFrames == 1) {
|
if (mNumParsedFrames == 1) {
|
||||||
// First frame parsed, let's read VBR info if available.
|
// First frame parsed, let's read VBR info if available.
|
||||||
|
@ -211,22 +366,50 @@ MP3Demuxer::GetNext() {
|
||||||
mFirstFrameOffset = frame->mOffset;
|
mFirstFrameOffset = frame->mOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the parser for the next frame parsing session.
|
|
||||||
mParser.FinishParsing();
|
|
||||||
return frame.forget();
|
return frame.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t
|
void
|
||||||
MP3Demuxer::Read(uint8_t* aBuffer, uint32_t aOffset, uint32_t aSize) {
|
MP3TrackDemuxer::UpdateState(const MediaByteRange& aRange) {
|
||||||
size_t read = 0;
|
// Prevent overflow.
|
||||||
if (!mSource->ReadAt(aOffset, aBuffer, aSize, &read)) {
|
if (mTotalFrameLen + aRange.Length() < mTotalFrameLen) {
|
||||||
read = 0;
|
// These variables have a linear dependency and are only used to derive the
|
||||||
|
// average frame length.
|
||||||
|
mTotalFrameLen /= 2;
|
||||||
|
mNumParsedFrames /= 2;
|
||||||
}
|
}
|
||||||
return read;
|
|
||||||
|
// Full frame parsed, move offset to its end.
|
||||||
|
mOffset = aRange.mEnd;
|
||||||
|
|
||||||
|
mTotalFrameLen += aRange.Length();
|
||||||
|
mSamplesPerFrame = mParser.CurrentFrame().Header().SamplesPerFrame();
|
||||||
|
mSamplesPerSecond = mParser.CurrentFrame().Header().SampleRate();
|
||||||
|
mChannels = mParser.CurrentFrame().Header().Channels();
|
||||||
|
++mNumParsedFrames;
|
||||||
|
++mFrameIndex;
|
||||||
|
MOZ_ASSERT(mFrameIndex > 0);
|
||||||
|
|
||||||
|
// Prepare the parser for the next frame parsing session.
|
||||||
|
mParser.FinishParsing();
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t
|
||||||
|
MP3TrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize) {
|
||||||
|
aSize = std::min<int64_t>(aSize, StreamLength() - aOffset);
|
||||||
|
if (aSize <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t read = 0;
|
||||||
|
const nsresult rv = mSource->ReadAt(aOffset, reinterpret_cast<char*>(aBuffer),
|
||||||
|
static_cast<uint32_t>(aSize), &read);
|
||||||
|
NS_ENSURE_SUCCESS(rv, 0);
|
||||||
|
return static_cast<int32_t>(read);
|
||||||
}
|
}
|
||||||
|
|
||||||
double
|
double
|
||||||
MP3Demuxer::AverageFrameLength() const {
|
MP3TrackDemuxer::AverageFrameLength() const {
|
||||||
if (!mNumParsedFrames) {
|
if (!mNumParsedFrames) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
@ -752,4 +935,5 @@ ID3Parser::ID3Header::Update(uint8_t c) {
|
||||||
return IsValid(mPos++);
|
return IsValid(mPos++);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mp4_demuxer
|
} // namespace mp3
|
||||||
|
} // namespace mozilla
|
|
@ -2,13 +2,39 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#ifndef MP3_TRACK_DEMUXER_H_
|
#ifndef MP3_DEMUXER_H_
|
||||||
#define MP3_TRACK_DEMUXER_H_
|
#define MP3_DEMUXER_H_
|
||||||
|
|
||||||
#include "mozilla/Attributes.h"
|
#include "mozilla/Attributes.h"
|
||||||
#include "demuxer/TrackDemuxer.h"
|
#include "MediaDataDemuxer.h"
|
||||||
|
#include "MediaResource.h"
|
||||||
|
|
||||||
namespace mp4_demuxer {
|
namespace mozilla {
|
||||||
|
namespace mp3 {
|
||||||
|
|
||||||
|
class MP3TrackDemuxer;
|
||||||
|
|
||||||
|
class MP3Demuxer : public MediaDataDemuxer {
|
||||||
|
public:
|
||||||
|
// MediaDataDemuxer interface.
|
||||||
|
explicit MP3Demuxer(MediaResource* aSource);
|
||||||
|
nsRefPtr<InitPromise> Init() override;
|
||||||
|
already_AddRefed<MediaDataDemuxer> Clone() const override;
|
||||||
|
bool HasTrackType(TrackInfo::TrackType aType) const override;
|
||||||
|
uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override;
|
||||||
|
already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(
|
||||||
|
TrackInfo::TrackType aType, uint32_t aTrackNumber) override;
|
||||||
|
bool IsSeekable() const override;
|
||||||
|
void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override;
|
||||||
|
void NotifyDataRemoved() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Synchronous initialization.
|
||||||
|
bool InitInternal();
|
||||||
|
|
||||||
|
nsRefPtr<MediaResource> mSource;
|
||||||
|
nsRefPtr<MP3TrackDemuxer> mTrackDemuxer;
|
||||||
|
};
|
||||||
|
|
||||||
// ID3 header parser state machine used by FrameParser.
|
// ID3 header parser state machine used by FrameParser.
|
||||||
// The header contains the following format (one byte per term):
|
// The header contains the following format (one byte per term):
|
||||||
|
@ -282,88 +308,93 @@ private:
|
||||||
|
|
||||||
// The MP3 demuxer used to extract MPEG frames and side information out of
|
// The MP3 demuxer used to extract MPEG frames and side information out of
|
||||||
// MPEG streams.
|
// MPEG streams.
|
||||||
class MP3Demuxer : public mozilla::TrackDemuxer {
|
class MP3TrackDemuxer : public MediaTrackDemuxer {
|
||||||
public:
|
public:
|
||||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MP3Demuxer);
|
// Constructor, expecing a valid media resource.
|
||||||
|
explicit MP3TrackDemuxer(MediaResource* aSource);
|
||||||
|
|
||||||
// Constructor, expecing a valid stream source.
|
// Initializes the track demuxer by reading the first frame for meta data.
|
||||||
explicit MP3Demuxer(Stream* aSource);
|
// Returns initialization success state.
|
||||||
|
|
||||||
// Initializes the demuxer by reading the expected stream length, if
|
|
||||||
// available. Optional, but recommended.
|
|
||||||
// Currently always returns true.
|
|
||||||
bool Init();
|
bool Init();
|
||||||
|
|
||||||
// Returns the total stream length if known, -1 otherwise.
|
// Returns the total stream length if known, -1 otherwise.
|
||||||
int64_t StreamLength() const;
|
int64_t StreamLength() const;
|
||||||
|
|
||||||
// Returns the estimated stream duration in microseconds, or -1 if no
|
// Returns the estimated stream duration, or a 0-duration if unknown.
|
||||||
// estimation available.
|
media::TimeUnit Duration() const;
|
||||||
int64_t Duration() const;
|
|
||||||
|
|
||||||
// Returns the estimated duration up to the given frames number in microseconds,
|
// Returns the estimated duration up to the given frame number,
|
||||||
// or -1 if no estimation available.
|
// or a 0-duration if unknown.
|
||||||
int64_t Duration(int64_t aNumFrames) const;
|
media::TimeUnit Duration(int64_t aNumFrames) const;
|
||||||
|
|
||||||
#ifdef ENABLE_TESTS
|
#ifdef ENABLE_TESTS
|
||||||
const FrameParser::Frame& LastFrame() const;
|
const FrameParser::Frame& LastFrame() const;
|
||||||
|
nsRefPtr<MediaRawData> DemuxSample();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const ID3Parser::ID3Header& ID3Header() const;
|
const ID3Parser::ID3Header& ID3Header() const;
|
||||||
const FrameParser::VBRHeader& VBRInfo() const;
|
const FrameParser::VBRHeader& VBRInfo() const;
|
||||||
|
|
||||||
// TrackDemuxer interface.
|
// MediaTrackDemuxer interface.
|
||||||
virtual void Seek(Microseconds aTime) override;
|
UniquePtr<TrackInfo> GetInfo() const override;
|
||||||
virtual already_AddRefed<mozilla::MediaRawData> DemuxSample() override;
|
nsRefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
|
||||||
virtual Microseconds GetNextKeyframeTime() override;
|
nsRefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
|
||||||
|
void Reset() override;
|
||||||
void UpdateConfig(mozilla::AudioInfo& aConfig) {
|
nsRefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(
|
||||||
aConfig.mRate = mSamplesPerSecond;
|
media::TimeUnit aTimeThreshold) override;
|
||||||
aConfig.mChannels = mChannels;
|
int64_t GetResourceOffset() const override;
|
||||||
aConfig.mBitDepth = 16;
|
media::TimeIntervals GetBuffered() override;
|
||||||
aConfig.mMimeType = "audio/mpeg";
|
int64_t GetEvictionOffset(media::TimeUnit aTime) override;
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Destructor.
|
// Destructor.
|
||||||
~MP3Demuxer() {}
|
~MP3TrackDemuxer() {}
|
||||||
|
|
||||||
// Fast approximate seeking to given time.
|
// Fast approximate seeking to given time.
|
||||||
void FastSeek(Microseconds aTime);
|
media::TimeUnit FastSeek(media::TimeUnit aTime);
|
||||||
|
|
||||||
// Slow, more accurate approximate seeking to given time.
|
// Seeks by scanning the stream up to the given time for more accurate results.
|
||||||
void SlowSeek(Microseconds aTime);
|
media::TimeUnit ScanUntil(media::TimeUnit aTime);
|
||||||
|
|
||||||
|
// Finds the next valid frame and returns its byte range.
|
||||||
|
MediaByteRange FindNextFrame();
|
||||||
|
|
||||||
|
// Skips the next frame given the provided byte range.
|
||||||
|
bool SkipNextFrame(const MediaByteRange& aRange);
|
||||||
|
|
||||||
// Returns the next MPEG frame, if available.
|
// Returns the next MPEG frame, if available.
|
||||||
already_AddRefed<mozilla::MediaRawData> GetNext();
|
already_AddRefed<MediaRawData> GetNextFrame(const MediaByteRange& aRange);
|
||||||
|
|
||||||
|
// Updates post-read meta data.
|
||||||
|
void UpdateState(const MediaByteRange& aRange);
|
||||||
|
|
||||||
// Reads aSize bytes into aBuffer from the source starting at aOffset.
|
// Reads aSize bytes into aBuffer from the source starting at aOffset.
|
||||||
// Returns the actual size read.
|
// Returns the actual size read.
|
||||||
uint32_t Read(uint8_t* aBuffer, uint32_t aOffset, uint32_t aSize);
|
int32_t Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize);
|
||||||
|
|
||||||
// Returns the average frame length derived from the previously parsed frames.
|
// Returns the average frame length derived from the previously parsed frames.
|
||||||
double AverageFrameLength() const;
|
double AverageFrameLength() const;
|
||||||
|
|
||||||
// The (hopefully) MPEG source stream.
|
// The (hopefully) MPEG resource.
|
||||||
nsRefPtr<Stream> mSource;
|
nsRefPtr<MediaResource> mSource;
|
||||||
|
|
||||||
// MPEG frame parser used to detect frames and extract side info.
|
// MPEG frame parser used to detect frames and extract side info.
|
||||||
FrameParser mParser;
|
FrameParser mParser;
|
||||||
|
|
||||||
// Current byte offset in the source stream.
|
// Current byte offset in the source stream.
|
||||||
uint64_t mOffset;
|
int64_t mOffset;
|
||||||
|
|
||||||
// Byte offset of the begin of the first frame, or 0 if none parsed yet.
|
// Byte offset of the begin of the first frame, or 0 if none parsed yet.
|
||||||
uint64_t mFirstFrameOffset;
|
int64_t mFirstFrameOffset;
|
||||||
|
|
||||||
// Total expected stream length, if available, or -1 otherwise.
|
|
||||||
int64_t mStreamLength;
|
|
||||||
|
|
||||||
// Total parsed frames.
|
// Total parsed frames.
|
||||||
int64_t mNumParsedFrames;
|
uint64_t mNumParsedFrames;
|
||||||
|
|
||||||
|
// Current frame index.
|
||||||
int64_t mFrameIndex;
|
int64_t mFrameIndex;
|
||||||
|
|
||||||
// Sum of parsed frames' lengths in bytes.
|
// Sum of parsed frames' lengths in bytes.
|
||||||
int64_t mTotalFrameLen;
|
uint64_t mTotalFrameLen;
|
||||||
|
|
||||||
// Samples per frame metric derived from frame headers or 0 if none available.
|
// Samples per frame metric derived from frame headers or 0 if none available.
|
||||||
int32_t mSamplesPerFrame;
|
int32_t mSamplesPerFrame;
|
||||||
|
@ -373,8 +404,12 @@ private:
|
||||||
|
|
||||||
// Channel count derived from frame headers or 0 if none available.
|
// Channel count derived from frame headers or 0 if none available.
|
||||||
int32_t mChannels;
|
int32_t mChannels;
|
||||||
|
|
||||||
|
// Audio track config info.
|
||||||
|
UniquePtr<AudioInfo> mInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace mp3
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
#endif
|
#endif
|
|
@ -156,8 +156,7 @@ public:
|
||||||
return aByteRange.mStart >= mStart && aByteRange.mEnd <= mEnd;
|
return aByteRange.mStart >= mStart && aByteRange.mEnd <= mEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaByteRange Extents(const MediaByteRange& aByteRange) const
|
MediaByteRange Extents(const MediaByteRange& aByteRange) const {
|
||||||
{
|
|
||||||
if (IsNull()) {
|
if (IsNull()) {
|
||||||
return aByteRange;
|
return aByteRange;
|
||||||
}
|
}
|
||||||
|
@ -165,7 +164,9 @@ public:
|
||||||
std::max(mEnd, aByteRange.mEnd));
|
std::max(mEnd, aByteRange.mEnd));
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t Length() { return mEnd - mStart; }
|
int64_t Length() const {
|
||||||
|
return mEnd - mStart;
|
||||||
|
}
|
||||||
|
|
||||||
int64_t mStart, mEnd;
|
int64_t mStart, mEnd;
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,7 +35,7 @@ LogToConsole(const nsAString& aMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
DetailedPromise::MaybeReject(nsresult aArg, const nsCString& aReason)
|
DetailedPromise::MaybeReject(nsresult aArg, const nsACString& aReason)
|
||||||
{
|
{
|
||||||
mResponded = true;
|
mResponded = true;
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ DetailedPromise::MaybeReject(nsresult aArg, const nsCString& aReason)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
DetailedPromise::MaybeReject(ErrorResult&, const nsCString& aReason)
|
DetailedPromise::MaybeReject(ErrorResult&, const nsACString& aReason)
|
||||||
{
|
{
|
||||||
NS_NOTREACHED("nsresult expected in MaybeReject()");
|
NS_NOTREACHED("nsresult expected in MaybeReject()");
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,10 +31,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaybeReject(nsresult aArg) = delete;
|
void MaybeReject(nsresult aArg) = delete;
|
||||||
void MaybeReject(nsresult aArg, const nsCString& aReason);
|
void MaybeReject(nsresult aArg, const nsACString& aReason);
|
||||||
|
|
||||||
void MaybeReject(ErrorResult& aArg) = delete;
|
void MaybeReject(ErrorResult& aArg) = delete;
|
||||||
void MaybeReject(ErrorResult&, const nsCString& aReason);
|
void MaybeReject(ErrorResult&, const nsACString& aReason);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit DetailedPromise(nsIGlobalObject* aGlobal);
|
explicit DetailedPromise(nsIGlobalObject* aGlobal);
|
||||||
|
|
|
@ -0,0 +1,537 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include "mozilla/dom/GMPVideoDecoderTrialCreator.h"
|
||||||
|
#include "mozilla/Preferences.h"
|
||||||
|
#include "prsystem.h"
|
||||||
|
#include "GMPVideoHost.h"
|
||||||
|
#include "mozilla/EMEUtils.h"
|
||||||
|
#include "nsServiceManagerUtils.h"
|
||||||
|
#include "GMPService.h"
|
||||||
|
#include "VideoUtils.h"
|
||||||
|
#include "nsPrintfCString.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace dom {
|
||||||
|
|
||||||
|
static already_AddRefed<nsIThread>
|
||||||
|
GetGMPThread()
|
||||||
|
{
|
||||||
|
nsCOMPtr<mozIGeckoMediaPluginService> gmps =
|
||||||
|
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
|
||||||
|
if (!gmps) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
nsCOMPtr<nsIThread> gmpThread;
|
||||||
|
nsresult rv = gmps->GetThread(getter_AddRefs(gmpThread));
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return gmpThread.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
static bool
|
||||||
|
OnGMPThread()
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsIThread> currentThread;
|
||||||
|
NS_GetCurrentThread(getter_AddRefs(currentThread));
|
||||||
|
nsCOMPtr<nsIThread> gmpThread(GetGMPThread());
|
||||||
|
return !!gmpThread && !!currentThread && gmpThread == currentThread;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const char*
|
||||||
|
TrialCreatePrefName(const nsAString& aKeySystem)
|
||||||
|
{
|
||||||
|
if (aKeySystem.EqualsLiteral("com.adobe.primetime")) {
|
||||||
|
return "media.gmp-eme-adobe.trial-create";
|
||||||
|
}
|
||||||
|
if (aKeySystem.EqualsLiteral("org.w3.clearkey")) {
|
||||||
|
return "media.gmp-eme-clearkey.trial-create";
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* static */
|
||||||
|
GMPVideoDecoderTrialCreator::TrialCreateState
|
||||||
|
GMPVideoDecoderTrialCreator::GetCreateTrialState(const nsAString& aKeySystem)
|
||||||
|
{
|
||||||
|
const char* pref = TrialCreatePrefName(aKeySystem);
|
||||||
|
if (!pref) {
|
||||||
|
return Pending;
|
||||||
|
}
|
||||||
|
switch (Preferences::GetInt(pref, (int)Pending)) {
|
||||||
|
case 0: return Pending;
|
||||||
|
case 1: return Succeeded;
|
||||||
|
case 2: return Failed;
|
||||||
|
default: return Pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderFailed(const nsAString& aKeySystem,
|
||||||
|
const nsACString& aReason)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
|
EME_LOG("GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderFailed(%s)",
|
||||||
|
NS_ConvertUTF16toUTF8(aKeySystem).get());
|
||||||
|
|
||||||
|
TrialCreateData* data = mTestCreate.Get(aKeySystem);
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data->mStatus = Failed;
|
||||||
|
const char* pref = TrialCreatePrefName(aKeySystem);
|
||||||
|
if (pref) {
|
||||||
|
Preferences::SetInt(pref, (int)Failed);
|
||||||
|
}
|
||||||
|
for (nsRefPtr<AbstractPromiseLike>& promise: data->mPending) {
|
||||||
|
promise->Reject(NS_ERROR_DOM_NOT_SUPPORTED_ERR, aReason);
|
||||||
|
}
|
||||||
|
data->mPending.Clear();
|
||||||
|
data->mTest = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderSucceeded(const nsAString& aKeySystem)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
|
EME_LOG("GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderSucceeded(%s)",
|
||||||
|
NS_ConvertUTF16toUTF8(aKeySystem).get());
|
||||||
|
|
||||||
|
TrialCreateData* data = mTestCreate.Get(aKeySystem);
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data->mStatus = Succeeded;
|
||||||
|
const char* pref = TrialCreatePrefName(aKeySystem);
|
||||||
|
if (pref) {
|
||||||
|
Preferences::SetInt(pref, (int)Succeeded);
|
||||||
|
}
|
||||||
|
for (nsRefPtr<AbstractPromiseLike>& promise : data->mPending) {
|
||||||
|
promise->Resolve();
|
||||||
|
}
|
||||||
|
data->mPending.Clear();
|
||||||
|
data->mTest = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
TestGMPVideoDecoder::Start()
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
|
mGMPService = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
|
||||||
|
if (!mGMPService) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsIThread> thread(GetGMPThread());
|
||||||
|
if (!thread) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
nsRefPtr<nsIRunnable> task(NS_NewRunnableMethod(this, &TestGMPVideoDecoder::CreateGMPVideoDecoder));
|
||||||
|
return thread->Dispatch(task, NS_DISPATCH_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExpectedPlaneDecodePlane {
|
||||||
|
GMPPlaneType mPlane;
|
||||||
|
size_t mLength;
|
||||||
|
uint8_t mValue;
|
||||||
|
int32_t mSize; // width & height
|
||||||
|
};
|
||||||
|
|
||||||
|
static const ExpectedPlaneDecodePlane sExpectedPlanes[3] = {
|
||||||
|
{
|
||||||
|
kGMPYPlane,
|
||||||
|
112 * 112, // 12544
|
||||||
|
0x4c,
|
||||||
|
112
|
||||||
|
},
|
||||||
|
{ // U
|
||||||
|
kGMPUPlane,
|
||||||
|
56 * 56, // 3136
|
||||||
|
0x55,
|
||||||
|
56,
|
||||||
|
},
|
||||||
|
{ // V
|
||||||
|
kGMPVPlane,
|
||||||
|
56 * 56, // 3136
|
||||||
|
0xff,
|
||||||
|
56,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool TestDecodedFrame(GMPVideoi420Frame* aDecodedFrame)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(OnGMPThread());
|
||||||
|
|
||||||
|
if (aDecodedFrame->Width() != 112 || aDecodedFrame->Height() != 112) {
|
||||||
|
EME_LOG("TestDecodedFrame() - Invalid decoded frame dimensions");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const ExpectedPlaneDecodePlane& plane : sExpectedPlanes) {
|
||||||
|
int32_t stride = aDecodedFrame->Stride(plane.mPlane);
|
||||||
|
if (stride < plane.mSize) {
|
||||||
|
EME_LOG("TestDecodedFrame() - Insufficient decoded frame stride");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int32_t length = plane.mSize * plane.mSize;
|
||||||
|
if (aDecodedFrame->AllocatedSize(plane.mPlane) < length) {
|
||||||
|
EME_LOG("TestDecodedFrame() - Insufficient decoded frame allocated size");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const uint8_t* data = aDecodedFrame->Buffer(plane.mPlane);
|
||||||
|
for (int32_t row = 0; row < plane.mSize; row++) {
|
||||||
|
for (int32_t i = 0; i < plane.mSize; i++) {
|
||||||
|
size_t off = (stride * row) + i;
|
||||||
|
if (data[off] != plane.mValue) {
|
||||||
|
EME_LOG("TestDecodedFrame() - Invalid decoded frame contents");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestGMPVideoDecoder::Decoded(GMPVideoi420Frame* aDecodedFrame)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(OnGMPThread());
|
||||||
|
|
||||||
|
if (!mReceivedDecoded) {
|
||||||
|
mReceivedDecoded = true;
|
||||||
|
} else {
|
||||||
|
EME_LOG("Received multiple decoded frames");
|
||||||
|
ReportFailure(NS_LITERAL_CSTRING("TestGMPVideoDecoder received multiple decoded frames"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GMPUniquePtr<GMPVideoi420Frame> decodedFrame(aDecodedFrame);
|
||||||
|
if (!TestDecodedFrame(aDecodedFrame)) {
|
||||||
|
EME_LOG("decoded frame failed verification");
|
||||||
|
ReportFailure(NS_LITERAL_CSTRING("TestGMPVideoDecoder decoded frame failed verification"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestGMPVideoDecoder::DrainComplete()
|
||||||
|
{
|
||||||
|
EME_LOG("TestGMPVideoDecoder::DrainComplete()");
|
||||||
|
MOZ_ASSERT(OnGMPThread());
|
||||||
|
ReportSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestGMPVideoDecoder::Error(GMPErr aErr)
|
||||||
|
{
|
||||||
|
EME_LOG("TestGMPVideoDecoder::ReceivedDecodedFrame()");
|
||||||
|
MOZ_ASSERT(OnGMPThread());
|
||||||
|
ReportFailure(nsPrintfCString("TestGMPVideoDecoder error %d", aErr));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestGMPVideoDecoder::Terminated()
|
||||||
|
{
|
||||||
|
EME_LOG("TestGMPVideoDecoder::Terminated()");
|
||||||
|
MOZ_ASSERT(OnGMPThread());
|
||||||
|
ReportFailure(NS_LITERAL_CSTRING("TestGMPVideoDecoder GMP terminated"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestGMPVideoDecoder::ReportFailure(const nsACString& aReason)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(OnGMPThread());
|
||||||
|
|
||||||
|
if (mGMP) {
|
||||||
|
mGMP->Close();
|
||||||
|
mGMP = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsRefPtr<nsIRunnable> task;
|
||||||
|
task = NS_NewRunnableMethodWithArgs<nsString, nsCString>(mInstance,
|
||||||
|
&GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderFailed,
|
||||||
|
mKeySystem,
|
||||||
|
aReason);
|
||||||
|
NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestGMPVideoDecoder::ReportSuccess()
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(OnGMPThread());
|
||||||
|
|
||||||
|
if (mGMP) {
|
||||||
|
mGMP->Close();
|
||||||
|
mGMP = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsRefPtr<nsIRunnable> task;
|
||||||
|
task = NS_NewRunnableMethodWithArg<nsString>(mInstance,
|
||||||
|
&GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderSucceeded,
|
||||||
|
mKeySystem);
|
||||||
|
NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A solid red, 112x112 frame. Display size 100x100 pixels.
|
||||||
|
// Generated with ImageMagick/ffmpeg
|
||||||
|
// $ convert -size 100x100 xc:rgb\(255, 0, 0\) red.png
|
||||||
|
// $ ffmpeg -f image2 -i red.png -r 24 -b:v 200k -c:v libx264 -profile:v baseline -level 1 -v:r 24 red.mp4 -y
|
||||||
|
static const uint8_t sTestH264Frame[] = {
|
||||||
|
0x00, 0x00, 0x02, 0x81, 0x06, 0x05, 0xff, 0xff, 0x7d, 0xdc, 0x45, 0xe9, 0xbd, 0xe6, 0xd9,
|
||||||
|
0x48, 0xb7, 0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef, 0x78, 0x32, 0x36, 0x34, 0x20,
|
||||||
|
0x2d, 0x20, 0x63, 0x6f, 0x72, 0x65, 0x20, 0x31, 0x33, 0x35, 0x20, 0x72, 0x32, 0x33, 0x34,
|
||||||
|
0x35, 0x20, 0x66, 0x30, 0x63, 0x31, 0x63, 0x35, 0x33, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32,
|
||||||
|
0x36, 0x34, 0x2f, 0x4d, 0x50, 0x45, 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56, 0x43, 0x20, 0x63,
|
||||||
|
0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x6c, 0x65, 0x66, 0x74,
|
||||||
|
0x20, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x30, 0x31, 0x33, 0x20, 0x2d, 0x20, 0x68, 0x74,
|
||||||
|
0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c,
|
||||||
|
0x61, 0x6e, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x78, 0x32, 0x36, 0x34, 0x2e, 0x68, 0x74, 0x6d,
|
||||||
|
0x6c, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x20, 0x63, 0x61,
|
||||||
|
0x62, 0x61, 0x63, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x3d, 0x33, 0x20, 0x64, 0x65, 0x62,
|
||||||
|
0x6c, 0x6f, 0x63, 0x6b, 0x3d, 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, 0x61, 0x6e, 0x61, 0x6c,
|
||||||
|
0x79, 0x73, 0x65, 0x3d, 0x30, 0x78, 0x31, 0x3a, 0x30, 0x78, 0x31, 0x31, 0x31, 0x20, 0x6d,
|
||||||
|
0x65, 0x3d, 0x68, 0x65, 0x78, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, 0x3d, 0x37, 0x20, 0x70,
|
||||||
|
0x73, 0x79, 0x3d, 0x31, 0x20, 0x70, 0x73, 0x79, 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e, 0x30,
|
||||||
|
0x30, 0x3a, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x72, 0x65,
|
||||||
|
0x66, 0x3d, 0x31, 0x20, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x31, 0x36,
|
||||||
|
0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x6d, 0x65, 0x3d, 0x31, 0x20, 0x74, 0x72,
|
||||||
|
0x65, 0x6c, 0x6c, 0x69, 0x73, 0x3d, 0x31, 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d,
|
||||||
|
0x30, 0x20, 0x63, 0x71, 0x6d, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x61, 0x64, 0x7a, 0x6f, 0x6e,
|
||||||
|
0x65, 0x3d, 0x32, 0x31, 0x2c, 0x31, 0x31, 0x20, 0x66, 0x61, 0x73, 0x74, 0x5f, 0x70, 0x73,
|
||||||
|
0x6b, 0x69, 0x70, 0x3d, 0x31, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x71, 0x70,
|
||||||
|
0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x2d, 0x32, 0x20, 0x74, 0x68, 0x72, 0x65,
|
||||||
|
0x61, 0x64, 0x73, 0x3d, 0x31, 0x32, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61,
|
||||||
|
0x64, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x31, 0x20, 0x73, 0x6c, 0x69,
|
||||||
|
0x63, 0x65, 0x64, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x30, 0x20, 0x6e,
|
||||||
|
0x72, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20,
|
||||||
|
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x3d, 0x30, 0x20, 0x62, 0x6c,
|
||||||
|
0x75, 0x72, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x3d, 0x30, 0x20, 0x63,
|
||||||
|
0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x72,
|
||||||
|
0x61, 0x3d, 0x30, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x30, 0x20, 0x77,
|
||||||
|
0x65, 0x69, 0x67, 0x68, 0x74, 0x70, 0x3d, 0x30, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74,
|
||||||
|
0x3d, 0x32, 0x35, 0x30, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e,
|
||||||
|
0x3d, 0x32, 0x34, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d, 0x34, 0x30,
|
||||||
|
0x20, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x3d,
|
||||||
|
0x30, 0x20, 0x72, 0x63, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x3d,
|
||||||
|
0x34, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x61, 0x62, 0x72, 0x20, 0x6d, 0x62, 0x74, 0x72, 0x65,
|
||||||
|
0x65, 0x3d, 0x31, 0x20, 0x62, 0x69, 0x74, 0x72, 0x61, 0x74, 0x65, 0x3d, 0x32, 0x30, 0x30,
|
||||||
|
0x20, 0x72, 0x61, 0x74, 0x65, 0x74, 0x6f, 0x6c, 0x3d, 0x31, 0x2e, 0x30, 0x20, 0x71, 0x63,
|
||||||
|
0x6f, 0x6d, 0x70, 0x3d, 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x69, 0x6e, 0x3d,
|
||||||
|
0x30, 0x20, 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x36, 0x39, 0x20, 0x71, 0x70, 0x73, 0x74,
|
||||||
|
0x65, 0x70, 0x3d, 0x34, 0x20, 0x69, 0x70, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31,
|
||||||
|
0x2e, 0x34, 0x30, 0x20, 0x61, 0x71, 0x3d, 0x31, 0x3a, 0x31, 0x2e, 0x30, 0x30, 0x00, 0x80,
|
||||||
|
0x00, 0x00, 0x00, 0x39, 0x65, 0x88, 0x84, 0x0c, 0xf1, 0x18, 0xa0, 0x00, 0x23, 0xbf, 0x1c,
|
||||||
|
0x00, 0x04, 0x3c, 0x63, 0x80, 0x00, 0x98, 0x44, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9d, 0x75,
|
||||||
|
0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75,
|
||||||
|
0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75,
|
||||||
|
0xe0
|
||||||
|
};
|
||||||
|
|
||||||
|
static GMPUniquePtr<GMPVideoEncodedFrame>
|
||||||
|
CreateFrame(GMPVideoHost* aHost)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(OnGMPThread());
|
||||||
|
|
||||||
|
GMPVideoFrame* ftmp = nullptr;
|
||||||
|
GMPErr err = aHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
|
||||||
|
if (GMP_FAILED(err)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(ftmp));
|
||||||
|
err = frame->CreateEmptyFrame(MOZ_ARRAY_LENGTH(sTestH264Frame));
|
||||||
|
if (GMP_FAILED(err)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(frame->Buffer(), sTestH264Frame, MOZ_ARRAY_LENGTH(sTestH264Frame));
|
||||||
|
frame->SetBufferType(GMP_BufferLength32);
|
||||||
|
|
||||||
|
frame->SetEncodedWidth(100);
|
||||||
|
frame->SetEncodedHeight(100);
|
||||||
|
frame->SetTimeStamp(0);
|
||||||
|
frame->SetCompleteFrame(true);
|
||||||
|
frame->SetDuration(41666);
|
||||||
|
frame->SetFrameType(kGMPKeyFrame);
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint8_t sTestH264CodecSpecific[] = {
|
||||||
|
0x01, 0x42, 0xc0, 0x0a, 0xff, 0xe1, 0x00, 0x18, 0x67, 0x42, 0xc0, 0x0a, 0xd9, 0x07, 0x3f,
|
||||||
|
0x9e, 0x79, 0xb2, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x60, 0x1e, 0x24,
|
||||||
|
0x4c, 0x90, 0x01, 0x00, 0x04, 0x68, 0xcb, 0x8c, 0xb2
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
TestGMPVideoDecoder::Callback::Done(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(OnGMPThread());
|
||||||
|
|
||||||
|
if (!aHost || !aGMP) {
|
||||||
|
mInstance->ReportFailure(NS_LITERAL_CSTRING("TestGMPVideoDecoder null host or GMP on Get"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsRefPtr<nsIRunnable> task;
|
||||||
|
task = NS_NewRunnableMethodWithArgs<GMPVideoDecoderProxy*, GMPVideoHost*>(mInstance,
|
||||||
|
&TestGMPVideoDecoder::ActorCreated,
|
||||||
|
aGMP, aHost);
|
||||||
|
NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestGMPVideoDecoder::ActorCreated(GMPVideoDecoderProxy* aGMP,
|
||||||
|
GMPVideoHost* aHost)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
MOZ_ASSERT(aHost && aGMP);
|
||||||
|
|
||||||
|
// Add crash handler.
|
||||||
|
nsRefPtr<gmp::GeckoMediaPluginService> service =
|
||||||
|
gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
|
||||||
|
service->AddPluginCrashedEventTarget(aGMP->GetPluginId(), mWindow);
|
||||||
|
|
||||||
|
nsCOMPtr<nsIThread> thread(GetGMPThread());
|
||||||
|
if (!thread) {
|
||||||
|
mInstance->TrialCreateGMPVideoDecoderFailed(mKeySystem,
|
||||||
|
NS_LITERAL_CSTRING("Failed to get GMP thread in TestGMPVideoDecoder::ActorCreated"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsRefPtr<nsIRunnable> task;
|
||||||
|
task = NS_NewRunnableMethodWithArgs<GMPVideoDecoderProxy*, GMPVideoHost*>(this,
|
||||||
|
&TestGMPVideoDecoder::InitGMPDone,
|
||||||
|
aGMP, aHost);
|
||||||
|
thread->Dispatch(task, NS_DISPATCH_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestGMPVideoDecoder::InitGMPDone(GMPVideoDecoderProxy* aGMP,
|
||||||
|
GMPVideoHost* aHost)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(OnGMPThread());
|
||||||
|
MOZ_ASSERT(aHost && aGMP);
|
||||||
|
|
||||||
|
mGMP = aGMP;
|
||||||
|
mHost = aHost;
|
||||||
|
|
||||||
|
GMPVideoCodec codec;
|
||||||
|
memset(&codec, 0, sizeof(codec));
|
||||||
|
|
||||||
|
codec.mGMPApiVersion = kGMPVersion33;
|
||||||
|
|
||||||
|
codec.mCodecType = kGMPVideoCodecH264;
|
||||||
|
codec.mWidth = 100;
|
||||||
|
codec.mHeight = 100;
|
||||||
|
|
||||||
|
nsTArray<uint8_t> codecSpecific;
|
||||||
|
codecSpecific.AppendElement(0); // mPacketizationMode.
|
||||||
|
codecSpecific.AppendElements(sTestH264CodecSpecific,
|
||||||
|
MOZ_ARRAY_LENGTH(sTestH264CodecSpecific));
|
||||||
|
|
||||||
|
nsresult rv = mGMP->InitDecode(codec,
|
||||||
|
codecSpecific,
|
||||||
|
this,
|
||||||
|
PR_GetNumberOfProcessors());
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
EME_LOG("InitGMPDone() - InitDecode() failed!");
|
||||||
|
ReportFailure(NS_LITERAL_CSTRING("TestGMPVideoDecoder InitDecode() returned failure"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(aHost);
|
||||||
|
nsTArray<uint8_t> info; // No codec specific per-frame info to pass.
|
||||||
|
rv = mGMP->Decode(Move(frame), false, info, 0);
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
EME_LOG("InitGMPDone() - Decode() failed to send Decode message!");
|
||||||
|
ReportFailure(NS_LITERAL_CSTRING("TestGMPVideoDecoder Decode() returned failure"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = mGMP->Drain();
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
EME_LOG("InitGMPDone() - Drain() failed to send Drain message!");
|
||||||
|
ReportFailure(NS_LITERAL_CSTRING("TestGMPVideoDecoder Drain() returned failure"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestGMPVideoDecoder::CreateGMPVideoDecoder()
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(OnGMPThread());
|
||||||
|
|
||||||
|
nsTArray<nsCString> tags;
|
||||||
|
tags.AppendElement(NS_LITERAL_CSTRING("h264"));
|
||||||
|
tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem));
|
||||||
|
|
||||||
|
UniquePtr<GetGMPVideoDecoderCallback> callback(new Callback(this));
|
||||||
|
nsCString fakeNodeId;
|
||||||
|
if (NS_FAILED(GenerateRandomName(fakeNodeId, 32)) ||
|
||||||
|
NS_FAILED(mGMPService->GetGMPVideoDecoder(&tags, fakeNodeId, Move(callback)))) {
|
||||||
|
ReportFailure(NS_LITERAL_CSTRING("TestGMPVideoDecoder GMPService GetGMPVideoDecoder returned failure"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GMPVideoDecoderTrialCreator::MaybeAwaitTrialCreate(const nsAString& aKeySystem,
|
||||||
|
AbstractPromiseLike* aPromisey,
|
||||||
|
nsPIDOMWindow* aParent)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
|
if (!mTestCreate.Contains(aKeySystem)) {
|
||||||
|
mTestCreate.Put(aKeySystem, new TrialCreateData(aKeySystem));
|
||||||
|
}
|
||||||
|
TrialCreateData* data = mTestCreate.Get(aKeySystem);
|
||||||
|
MOZ_ASSERT(data);
|
||||||
|
|
||||||
|
switch (data->mStatus) {
|
||||||
|
case TrialCreateState::Succeeded: {
|
||||||
|
EME_LOG("GMPVideoDecoderTrialCreator::MaybeAwaitTrialCreate(%s) already succeeded",
|
||||||
|
NS_ConvertUTF16toUTF8(aKeySystem).get());
|
||||||
|
aPromisey->Resolve();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TrialCreateState::Failed: {
|
||||||
|
// Something is broken about this configuration. Report as unsupported.
|
||||||
|
EME_LOG("GMPVideoDecoderTrialCreator::MaybeAwaitTrialCreate(%s) already failed",
|
||||||
|
NS_ConvertUTF16toUTF8(aKeySystem).get());
|
||||||
|
aPromisey->Reject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
|
||||||
|
NS_LITERAL_CSTRING("navigator.requestMediaKeySystemAccess trial CDM creation failed"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TrialCreateState::Pending: {
|
||||||
|
EME_LOG("GMPVideoDecoderTrialCreator::MaybeAwaitTrialCreate(%s) pending",
|
||||||
|
NS_ConvertUTF16toUTF8(aKeySystem).get());
|
||||||
|
// Add request to the list of pending items waiting.
|
||||||
|
data->mPending.AppendElement(aPromisey);
|
||||||
|
if (!data->mTest) {
|
||||||
|
// Not already waiting for CDM to be created. Create and Init
|
||||||
|
// a CDM, to test whether it will work.
|
||||||
|
data->mTest = new TestGMPVideoDecoder(this, aKeySystem, aParent);
|
||||||
|
if (NS_FAILED(data->mTest->Start())) {
|
||||||
|
TrialCreateGMPVideoDecoderFailed(aKeySystem,
|
||||||
|
NS_LITERAL_CSTRING("TestGMPVideoDecoder::Start() failed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Promise will call InitMediaKeysPromiseHandler when Init()
|
||||||
|
// succeeds/fails.
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dom
|
||||||
|
} // namespace mozilla
|
|
@ -0,0 +1,175 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#ifndef mozilla_dom_GMPVideoDecoderTrialCreator_h
|
||||||
|
#define mozilla_dom_GMPVideoDecoderTrialCreator_h
|
||||||
|
|
||||||
|
#include "mozilla/dom/MediaKeySystemAccess.h"
|
||||||
|
#include "nsIObserver.h"
|
||||||
|
#include "nsCycleCollectionParticipant.h"
|
||||||
|
#include "nsISupportsImpl.h"
|
||||||
|
#include "nsITimer.h"
|
||||||
|
#include "nsClassHashtable.h"
|
||||||
|
#include "mozilla/dom/MediaKeys.h"
|
||||||
|
#include "GMPVideoDecoderProxy.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace dom {
|
||||||
|
|
||||||
|
class TestGMPVideoDecoder;
|
||||||
|
|
||||||
|
class GMPVideoDecoderTrialCreator {
|
||||||
|
public:
|
||||||
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPVideoDecoderTrialCreator);
|
||||||
|
|
||||||
|
void TrialCreateGMPVideoDecoderFailed(const nsAString& aKeySystem,
|
||||||
|
const nsACString& aReason);
|
||||||
|
void TrialCreateGMPVideoDecoderSucceeded(const nsAString& aKeySystem);
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void MaybeAwaitTrialCreate(const nsAString& aKeySystem,
|
||||||
|
MediaKeySystemAccess* aAccess,
|
||||||
|
T* aPromiseLike,
|
||||||
|
nsPIDOMWindow* aParent)
|
||||||
|
{
|
||||||
|
nsRefPtr<PromiseLike<T>> p(new PromiseLike<T>(aPromiseLike, aAccess));
|
||||||
|
MaybeAwaitTrialCreate(aKeySystem, p, aParent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
class AbstractPromiseLike {
|
||||||
|
public:
|
||||||
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractPromiseLike);
|
||||||
|
|
||||||
|
virtual void Resolve() = 0;
|
||||||
|
virtual void Reject(nsresult aResult, const nsACString& aMessage) = 0;
|
||||||
|
protected:
|
||||||
|
virtual ~AbstractPromiseLike() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class PromiseLike : public AbstractPromiseLike
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit PromiseLike(T* aPromiseLike, MediaKeySystemAccess* aAccess)
|
||||||
|
: mPromiseLike(aPromiseLike)
|
||||||
|
, mAccess(aAccess)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void Resolve() override {
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
mPromiseLike->MaybeResolve(mAccess);
|
||||||
|
}
|
||||||
|
void Reject(nsresult aResult, const nsACString& aMessage) override {
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
mPromiseLike->MaybeReject(aResult, aMessage);
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
~PromiseLike() {}
|
||||||
|
nsRefPtr<T> mPromiseLike;
|
||||||
|
nsRefPtr<MediaKeySystemAccess> mAccess;
|
||||||
|
};
|
||||||
|
|
||||||
|
void MaybeAwaitTrialCreate(const nsAString& aKeySystem,
|
||||||
|
AbstractPromiseLike* aPromisey,
|
||||||
|
nsPIDOMWindow* aParent);
|
||||||
|
|
||||||
|
~GMPVideoDecoderTrialCreator() {}
|
||||||
|
|
||||||
|
// Note: Keep this in sync with GetCreateTrialState.
|
||||||
|
enum TrialCreateState {
|
||||||
|
Pending = 0,
|
||||||
|
Succeeded = 1,
|
||||||
|
Failed = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
static TrialCreateState GetCreateTrialState(const nsAString& aKeySystem);
|
||||||
|
|
||||||
|
struct TrialCreateData {
|
||||||
|
TrialCreateData(const nsAString& aKeySystem)
|
||||||
|
: mKeySystem(aKeySystem)
|
||||||
|
, mStatus(GetCreateTrialState(aKeySystem))
|
||||||
|
{}
|
||||||
|
~TrialCreateData() {}
|
||||||
|
const nsString mKeySystem;
|
||||||
|
nsRefPtr<TestGMPVideoDecoder> mTest;
|
||||||
|
nsTArray<nsRefPtr<AbstractPromiseLike>> mPending;
|
||||||
|
TrialCreateState mStatus;
|
||||||
|
private:
|
||||||
|
TrialCreateData(const TrialCreateData& aOther) = delete;
|
||||||
|
TrialCreateData() = delete;
|
||||||
|
TrialCreateData& operator =(const TrialCreateData&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
nsClassHashtable<nsStringHashKey, TrialCreateData> mTestCreate;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestGMPVideoDecoder : public GMPVideoDecoderCallbackProxy {
|
||||||
|
public:
|
||||||
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestGMPVideoDecoder);
|
||||||
|
|
||||||
|
TestGMPVideoDecoder(GMPVideoDecoderTrialCreator* aInstance,
|
||||||
|
const nsAString& aKeySystem,
|
||||||
|
nsPIDOMWindow* aParent)
|
||||||
|
: mKeySystem(aKeySystem)
|
||||||
|
, mInstance(aInstance)
|
||||||
|
, mWindow(aParent)
|
||||||
|
, mGMP(nullptr)
|
||||||
|
, mHost(nullptr)
|
||||||
|
, mReceivedDecoded(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
nsresult Start();
|
||||||
|
|
||||||
|
// GMPVideoDecoderCallbackProxy
|
||||||
|
virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
|
||||||
|
virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override {}
|
||||||
|
virtual void ReceivedDecodedFrame(const uint64_t aPictureId) override {}
|
||||||
|
virtual void InputDataExhausted() override {}
|
||||||
|
virtual void DrainComplete() override;
|
||||||
|
virtual void ResetComplete() override {}
|
||||||
|
virtual void Error(GMPErr aErr) override;
|
||||||
|
virtual void Terminated() override;
|
||||||
|
|
||||||
|
void ActorCreated(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost); // Main thread.
|
||||||
|
|
||||||
|
class Callback : public GetGMPVideoDecoderCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Callback(TestGMPVideoDecoder* aInstance)
|
||||||
|
: mInstance(aInstance)
|
||||||
|
{}
|
||||||
|
~Callback() {}
|
||||||
|
void Done(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost) override;
|
||||||
|
private:
|
||||||
|
nsRefPtr<TestGMPVideoDecoder> mInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void InitGMPDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost); // GMP thread.
|
||||||
|
void CreateGMPVideoDecoder();
|
||||||
|
~TestGMPVideoDecoder() {}
|
||||||
|
|
||||||
|
void ReportFailure(const nsACString& aReason);
|
||||||
|
void ReportSuccess();
|
||||||
|
|
||||||
|
const nsString mKeySystem;
|
||||||
|
nsCOMPtr<mozIGeckoMediaPluginService> mGMPService;
|
||||||
|
|
||||||
|
nsRefPtr<GMPVideoDecoderTrialCreator> mInstance;
|
||||||
|
nsCOMPtr<nsPIDOMWindow> mWindow;
|
||||||
|
GMPVideoDecoderProxy* mGMP;
|
||||||
|
GMPVideoHost* mHost;
|
||||||
|
bool mReceivedDecoded;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dom
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
|
#endif
|
|
@ -10,6 +10,9 @@
|
||||||
#include "nsIObserverService.h"
|
#include "nsIObserverService.h"
|
||||||
#include "mozilla/Services.h"
|
#include "mozilla/Services.h"
|
||||||
#include "mozilla/DetailedPromise.h"
|
#include "mozilla/DetailedPromise.h"
|
||||||
|
#ifdef XP_WIN
|
||||||
|
#include "mozilla/WindowsVersion.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace dom {
|
namespace dom {
|
||||||
|
@ -44,6 +47,9 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||||
MediaKeySystemAccessManager::MediaKeySystemAccessManager(nsPIDOMWindow* aWindow)
|
MediaKeySystemAccessManager::MediaKeySystemAccessManager(nsPIDOMWindow* aWindow)
|
||||||
: mWindow(aWindow)
|
: mWindow(aWindow)
|
||||||
, mAddedObservers(false)
|
, mAddedObservers(false)
|
||||||
|
#ifdef XP_WIN
|
||||||
|
, mTrialCreator(new GMPVideoDecoderTrialCreator())
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +150,16 @@ MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
|
||||||
if (aOptions.IsEmpty() ||
|
if (aOptions.IsEmpty() ||
|
||||||
MediaKeySystemAccess::IsSupported(keySystem, aOptions)) {
|
MediaKeySystemAccess::IsSupported(keySystem, aOptions)) {
|
||||||
nsRefPtr<MediaKeySystemAccess> access(new MediaKeySystemAccess(mWindow, keySystem));
|
nsRefPtr<MediaKeySystemAccess> access(new MediaKeySystemAccess(mWindow, keySystem));
|
||||||
|
#ifdef XP_WIN
|
||||||
|
if (IsVistaOrLater()) {
|
||||||
|
// On Windows, ensure we have tried creating a GMPVideoDecoder for this
|
||||||
|
// keySystem, and that we can use it to decode. This ensures that we only
|
||||||
|
// report that we support this keySystem when the CDM us usable (i.e.
|
||||||
|
// all system libraries required are installed).
|
||||||
|
mTrialCreator->MaybeAwaitTrialCreate(keySystem, access, aPromise, mWindow);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
aPromise->MaybeResolve(access);
|
aPromise->MaybeResolve(access);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
#define mozilla_dom_MediaKeySystemAccessManager_h
|
#define mozilla_dom_MediaKeySystemAccessManager_h
|
||||||
|
|
||||||
#include "mozilla/dom/MediaKeySystemAccess.h"
|
#include "mozilla/dom/MediaKeySystemAccess.h"
|
||||||
|
#ifdef XP_WIN
|
||||||
|
#include "mozilla/dom/GMPVideoDecoderTrialCreator.h"
|
||||||
|
#endif
|
||||||
#include "nsIObserver.h"
|
#include "nsIObserver.h"
|
||||||
#include "nsCycleCollectionParticipant.h"
|
#include "nsCycleCollectionParticipant.h"
|
||||||
#include "nsISupportsImpl.h"
|
#include "nsISupportsImpl.h"
|
||||||
|
@ -15,6 +18,7 @@ namespace mozilla {
|
||||||
namespace dom {
|
namespace dom {
|
||||||
|
|
||||||
class DetailedPromise;
|
class DetailedPromise;
|
||||||
|
class TestGMPVideoDecoder;
|
||||||
|
|
||||||
class MediaKeySystemAccessManager final : public nsIObserver
|
class MediaKeySystemAccessManager final : public nsIObserver
|
||||||
{
|
{
|
||||||
|
@ -34,9 +38,9 @@ public:
|
||||||
|
|
||||||
struct PendingRequest {
|
struct PendingRequest {
|
||||||
PendingRequest(DetailedPromise* aPromise,
|
PendingRequest(DetailedPromise* aPromise,
|
||||||
const nsAString& aKeySystem,
|
const nsAString& aKeySystem,
|
||||||
const Sequence<MediaKeySystemOptions>& aOptions,
|
const Sequence<MediaKeySystemOptions>& aOptions,
|
||||||
nsITimer* aTimer);
|
nsITimer* aTimer);
|
||||||
PendingRequest(const PendingRequest& aOther);
|
PendingRequest(const PendingRequest& aOther);
|
||||||
~PendingRequest();
|
~PendingRequest();
|
||||||
void CancelTimer();
|
void CancelTimer();
|
||||||
|
@ -74,6 +78,10 @@ private:
|
||||||
|
|
||||||
nsCOMPtr<nsPIDOMWindow> mWindow;
|
nsCOMPtr<nsPIDOMWindow> mWindow;
|
||||||
bool mAddedObservers;
|
bool mAddedObservers;
|
||||||
|
|
||||||
|
#ifdef XP_WIN
|
||||||
|
nsRefPtr<GMPVideoDecoderTrialCreator> mTrialCreator;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
|
|
|
@ -6,14 +6,12 @@
|
||||||
|
|
||||||
#include "mozilla/dom/MediaKeys.h"
|
#include "mozilla/dom/MediaKeys.h"
|
||||||
#include "GMPService.h"
|
#include "GMPService.h"
|
||||||
#include "mozilla/EventDispatcher.h"
|
|
||||||
#include "mozilla/dom/HTMLMediaElement.h"
|
#include "mozilla/dom/HTMLMediaElement.h"
|
||||||
#include "mozilla/dom/MediaKeysBinding.h"
|
#include "mozilla/dom/MediaKeysBinding.h"
|
||||||
#include "mozilla/dom/MediaKeyMessageEvent.h"
|
#include "mozilla/dom/MediaKeyMessageEvent.h"
|
||||||
#include "mozilla/dom/MediaKeyError.h"
|
#include "mozilla/dom/MediaKeyError.h"
|
||||||
#include "mozilla/dom/MediaKeySession.h"
|
#include "mozilla/dom/MediaKeySession.h"
|
||||||
#include "mozilla/dom/DOMException.h"
|
#include "mozilla/dom/DOMException.h"
|
||||||
#include "mozilla/dom/PluginCrashedEvent.h"
|
|
||||||
#include "mozilla/dom/UnionTypes.h"
|
#include "mozilla/dom/UnionTypes.h"
|
||||||
#include "mozilla/CDMProxy.h"
|
#include "mozilla/CDMProxy.h"
|
||||||
#include "mozilla/EMEUtils.h"
|
#include "mozilla/EMEUtils.h"
|
||||||
|
@ -383,83 +381,6 @@ MediaKeys::Init(ErrorResult& aRv)
|
||||||
return promise.forget();
|
return promise.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
class CrashHandler : public gmp::GeckoMediaPluginService::PluginCrashCallback
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CrashHandler(const uint32_t aPluginId,
|
|
||||||
nsPIDOMWindow* aParentWindow,
|
|
||||||
nsIDocument* aDocument)
|
|
||||||
: gmp::GeckoMediaPluginService::PluginCrashCallback(aPluginId)
|
|
||||||
, mPluginId(aPluginId)
|
|
||||||
, mParentWindowWeakPtr(do_GetWeakReference(aParentWindow))
|
|
||||||
, mDocumentWeakPtr(do_GetWeakReference(aDocument))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Run(const nsACString& aPluginName) override
|
|
||||||
{
|
|
||||||
PluginCrashedEventInit init;
|
|
||||||
init.mPluginID = mPluginId;
|
|
||||||
init.mBubbles = true;
|
|
||||||
init.mCancelable = true;
|
|
||||||
init.mGmpPlugin = true;
|
|
||||||
CopyUTF8toUTF16(aPluginName, init.mPluginName);
|
|
||||||
init.mSubmittedCrashReport = false;
|
|
||||||
|
|
||||||
// The following PluginCrashedEvent fields stay empty:
|
|
||||||
// init.mBrowserDumpID
|
|
||||||
// init.mPluginFilename
|
|
||||||
// TODO: Can/should we fill them?
|
|
||||||
|
|
||||||
nsCOMPtr<nsPIDOMWindow> parentWindow;
|
|
||||||
nsCOMPtr<nsIDocument> document;
|
|
||||||
if (!GetParentWindowAndDocumentIfValid(parentWindow, document)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsRefPtr<PluginCrashedEvent> event =
|
|
||||||
PluginCrashedEvent::Constructor(document, NS_LITERAL_STRING("PluginCrashed"), init);
|
|
||||||
event->SetTrusted(true);
|
|
||||||
event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
|
|
||||||
|
|
||||||
EventDispatcher::DispatchDOMEvent(parentWindow, nullptr, event, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool IsStillValid() override
|
|
||||||
{
|
|
||||||
nsCOMPtr<nsPIDOMWindow> parentWindow;
|
|
||||||
nsCOMPtr<nsIDocument> document;
|
|
||||||
return GetParentWindowAndDocumentIfValid(parentWindow, document);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
virtual ~CrashHandler()
|
|
||||||
{ }
|
|
||||||
|
|
||||||
bool
|
|
||||||
GetParentWindowAndDocumentIfValid(nsCOMPtr<nsPIDOMWindow>& parentWindow,
|
|
||||||
nsCOMPtr<nsIDocument>& document)
|
|
||||||
{
|
|
||||||
parentWindow = do_QueryReferent(mParentWindowWeakPtr);
|
|
||||||
if (!parentWindow) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
document = do_QueryReferent(mDocumentWeakPtr);
|
|
||||||
if (!document) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
nsCOMPtr<nsIDocument> parentWindowDocument = parentWindow->GetExtantDoc();
|
|
||||||
if (!parentWindowDocument || document.get() != parentWindowDocument.get()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t mPluginId;
|
|
||||||
nsWeakPtr mParentWindowWeakPtr;
|
|
||||||
nsWeakPtr mDocumentWeakPtr;
|
|
||||||
};
|
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId, const uint32_t aPluginId)
|
MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId, const uint32_t aPluginId)
|
||||||
{
|
{
|
||||||
|
@ -489,11 +410,7 @@ MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId, const uint32_t
|
||||||
if (NS_WARN_IF(!mParent)) {
|
if (NS_WARN_IF(!mParent)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
nsCOMPtr<nsIDocument> doc = mParent->GetExtantDoc();
|
service->AddPluginCrashedEventTarget(aPluginId, mParent);
|
||||||
if (NS_WARN_IF(!doc)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
service->AddPluginCrashCallback(new CrashHandler(aPluginId, mParent, doc));
|
|
||||||
EME_LOG("MediaKeys[%p]::OnCDMCreated() registered crash handler for pluginId '%i'",
|
EME_LOG("MediaKeys[%p]::OnCDMCreated() registered crash handler for pluginId '%i'",
|
||||||
this, aPluginId);
|
this, aPluginId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,14 @@ UNIFIED_SOURCES += [
|
||||||
'MediaKeySystemAccessManager.cpp',
|
'MediaKeySystemAccessManager.cpp',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||||
|
UNIFIED_SOURCES += [
|
||||||
|
'GMPVideoDecoderTrialCreator.cpp',
|
||||||
|
]
|
||||||
|
EXPORTS.mozilla.dom += [
|
||||||
|
'GMPVideoDecoderTrialCreator.h',
|
||||||
|
]
|
||||||
|
|
||||||
FINAL_LIBRARY = 'xul'
|
FINAL_LIBRARY = 'xul'
|
||||||
|
|
||||||
FAIL_ON_WARNINGS = True
|
FAIL_ON_WARNINGS = True
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Name: fakeopenh264
|
Name: fakeopenh264
|
||||||
Description: Fake GMP Plugin
|
Description: Fake GMP Plugin
|
||||||
Version: 1.0
|
Version: 1.0
|
||||||
APIs: encode-video[h264], decode-video[h264]
|
APIs: encode-video[h264:fake], decode-video[h264:fake]
|
||||||
|
|
|
@ -0,0 +1,429 @@
|
||||||
|
/*!
|
||||||
|
* \copy
|
||||||
|
* Copyright (c) 2009-2014, Cisco Systems
|
||||||
|
* Copyright (c) 2014, Mozilla
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in
|
||||||
|
* the documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*************************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include "gmp-platform.h"
|
||||||
|
#include "gmp-video-host.h"
|
||||||
|
#include "gmp-video-encode.h"
|
||||||
|
#include "gmp-video-decode.h"
|
||||||
|
#include "gmp-video-frame-i420.h"
|
||||||
|
#include "gmp-video-frame-encoded.h"
|
||||||
|
|
||||||
|
#if defined(GMP_FAKE_SUPPORT_DECRYPT)
|
||||||
|
#include "gmp-decryption.h"
|
||||||
|
#include "gmp-test-decryptor.h"
|
||||||
|
#include "gmp-test-storage.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
#define PUBLIC_FUNC __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define PUBLIC_FUNC
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define BIG_FRAME 10000
|
||||||
|
|
||||||
|
static int g_log_level = 0;
|
||||||
|
|
||||||
|
#define GMPLOG(l, x) do { \
|
||||||
|
if (l <= g_log_level) { \
|
||||||
|
const char *log_string = "unknown"; \
|
||||||
|
if ((l >= 0) && (l <= 3)) { \
|
||||||
|
log_string = kLogStrings[l]; \
|
||||||
|
} \
|
||||||
|
std::cerr << log_string << ": " << x << std::endl; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define GL_CRIT 0
|
||||||
|
#define GL_ERROR 1
|
||||||
|
#define GL_INFO 2
|
||||||
|
#define GL_DEBUG 3
|
||||||
|
|
||||||
|
const char* kLogStrings[] = {
|
||||||
|
"Critical",
|
||||||
|
"Error",
|
||||||
|
"Info",
|
||||||
|
"Debug"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
GMPPlatformAPI* g_platform_api = NULL;
|
||||||
|
|
||||||
|
class FakeVideoEncoder;
|
||||||
|
class FakeVideoDecoder;
|
||||||
|
|
||||||
|
struct EncodedFrame {
|
||||||
|
uint32_t length_;
|
||||||
|
uint8_t h264_compat_;
|
||||||
|
uint32_t magic_;
|
||||||
|
uint32_t width_;
|
||||||
|
uint32_t height_;
|
||||||
|
uint8_t y_;
|
||||||
|
uint8_t u_;
|
||||||
|
uint8_t v_;
|
||||||
|
uint32_t timestamp_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ENCODED_FRAME_MAGIC 0x4652414d
|
||||||
|
|
||||||
|
class FakeEncoderTask : public GMPTask {
|
||||||
|
public:
|
||||||
|
FakeEncoderTask(FakeVideoEncoder* encoder,
|
||||||
|
GMPVideoi420Frame* frame,
|
||||||
|
GMPVideoFrameType type)
|
||||||
|
: encoder_(encoder), frame_(frame), type_(type) {}
|
||||||
|
|
||||||
|
virtual void Run();
|
||||||
|
virtual void Destroy() { delete this; }
|
||||||
|
|
||||||
|
FakeVideoEncoder* encoder_;
|
||||||
|
GMPVideoi420Frame* frame_;
|
||||||
|
GMPVideoFrameType type_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FakeVideoEncoder : public GMPVideoEncoder {
|
||||||
|
public:
|
||||||
|
explicit FakeVideoEncoder (GMPVideoHost* hostAPI) :
|
||||||
|
host_ (hostAPI),
|
||||||
|
callback_ (NULL) {}
|
||||||
|
|
||||||
|
virtual void InitEncode (const GMPVideoCodec& codecSettings,
|
||||||
|
const uint8_t* aCodecSpecific,
|
||||||
|
uint32_t aCodecSpecificSize,
|
||||||
|
GMPVideoEncoderCallback* callback,
|
||||||
|
int32_t numberOfCores,
|
||||||
|
uint32_t maxPayloadSize) {
|
||||||
|
callback_ = callback;
|
||||||
|
|
||||||
|
GMPLOG (GL_INFO, "Initialized encoder");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Encode (GMPVideoi420Frame* inputImage,
|
||||||
|
const uint8_t* aCodecSpecificInfo,
|
||||||
|
uint32_t aCodecSpecificInfoLength,
|
||||||
|
const GMPVideoFrameType* aFrameTypes,
|
||||||
|
uint32_t aFrameTypesLength) {
|
||||||
|
GMPLOG (GL_DEBUG,
|
||||||
|
__FUNCTION__
|
||||||
|
<< " size="
|
||||||
|
<< inputImage->Width() << "x" << inputImage->Height());
|
||||||
|
|
||||||
|
assert (aFrameTypesLength != 0);
|
||||||
|
|
||||||
|
g_platform_api->runonmainthread(new FakeEncoderTask(this,
|
||||||
|
inputImage,
|
||||||
|
aFrameTypes[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Encode_m (GMPVideoi420Frame* inputImage,
|
||||||
|
GMPVideoFrameType frame_type) {
|
||||||
|
if (frame_type == kGMPKeyFrame) {
|
||||||
|
if (!inputImage)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!inputImage) {
|
||||||
|
GMPLOG (GL_ERROR, "no input image");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now return the encoded data back to the parent.
|
||||||
|
GMPVideoFrame* ftmp;
|
||||||
|
GMPErr err = host_->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
|
||||||
|
if (err != GMPNoErr) {
|
||||||
|
GMPLOG (GL_ERROR, "Error creating encoded frame");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GMPVideoEncodedFrame* f = static_cast<GMPVideoEncodedFrame*> (ftmp);
|
||||||
|
|
||||||
|
// Encode this in a frame that looks a little bit like H.264.
|
||||||
|
// Note that we don't do PPS or SPS.
|
||||||
|
// Copy the data. This really should convert this to network byte order.
|
||||||
|
EncodedFrame eframe;
|
||||||
|
eframe.length_ = sizeof(eframe) - sizeof(uint32_t);
|
||||||
|
eframe.h264_compat_ = 5; // Emulate a H.264 IDR NAL.
|
||||||
|
eframe.magic_ = ENCODED_FRAME_MAGIC;
|
||||||
|
eframe.width_ = inputImage->Width();
|
||||||
|
eframe.height_ = inputImage->Height();
|
||||||
|
eframe.y_ = AveragePlane(inputImage->Buffer(kGMPYPlane),
|
||||||
|
inputImage->AllocatedSize(kGMPYPlane));
|
||||||
|
eframe.u_ = AveragePlane(inputImage->Buffer(kGMPUPlane),
|
||||||
|
inputImage->AllocatedSize(kGMPUPlane));
|
||||||
|
eframe.v_ = AveragePlane(inputImage->Buffer(kGMPVPlane),
|
||||||
|
inputImage->AllocatedSize(kGMPVPlane));
|
||||||
|
|
||||||
|
eframe.timestamp_ = inputImage->Timestamp();
|
||||||
|
|
||||||
|
err = f->CreateEmptyFrame (sizeof(eframe) +
|
||||||
|
(frame_type == kGMPKeyFrame ? sizeof(uint32_t) + BIG_FRAME : 0));
|
||||||
|
if (err != GMPNoErr) {
|
||||||
|
GMPLOG (GL_ERROR, "Error allocating frame data");
|
||||||
|
f->Destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(f->Buffer(), &eframe, sizeof(eframe));
|
||||||
|
if (frame_type == kGMPKeyFrame) {
|
||||||
|
*((uint32_t*) f->Buffer() + sizeof(eframe)) = BIG_FRAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
f->SetEncodedWidth (inputImage->Width());
|
||||||
|
f->SetEncodedHeight (inputImage->Height());
|
||||||
|
f->SetTimeStamp (inputImage->Timestamp());
|
||||||
|
f->SetFrameType (frame_type);
|
||||||
|
f->SetCompleteFrame (true);
|
||||||
|
f->SetBufferType(GMP_BufferLength32);
|
||||||
|
|
||||||
|
GMPLOG (GL_DEBUG, "Encoding complete. type= "
|
||||||
|
<< f->FrameType()
|
||||||
|
<< " length="
|
||||||
|
<< f->Size()
|
||||||
|
<< " timestamp="
|
||||||
|
<< f->TimeStamp());
|
||||||
|
|
||||||
|
// Return the encoded frame.
|
||||||
|
GMPCodecSpecificInfo info;
|
||||||
|
memset (&info, 0, sizeof (info));
|
||||||
|
info.mCodecType = kGMPVideoCodecH264;
|
||||||
|
info.mBufferType = GMP_BufferLength32;
|
||||||
|
info.mCodecSpecific.mH264.mSimulcastIdx = 0;
|
||||||
|
GMPLOG (GL_DEBUG, "Calling callback");
|
||||||
|
callback_->Encoded (f, reinterpret_cast<uint8_t*> (&info), sizeof(info));
|
||||||
|
GMPLOG (GL_DEBUG, "Callback called");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void SetChannelParameters (uint32_t aPacketLoss, uint32_t aRTT) {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void SetRates (uint32_t aNewBitRate, uint32_t aFrameRate) {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void SetPeriodicKeyFrames (bool aEnable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void EncodingComplete() {
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t AveragePlane(uint8_t* ptr, size_t len) {
|
||||||
|
uint64_t val = 0;
|
||||||
|
|
||||||
|
for (size_t i=0; i<len; ++i) {
|
||||||
|
val += ptr[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (val / len) % 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
GMPVideoHost* host_;
|
||||||
|
GMPVideoEncoderCallback* callback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
void FakeEncoderTask::Run() {
|
||||||
|
encoder_->Encode_m(frame_, type_);
|
||||||
|
frame_->Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeDecoderTask : public GMPTask {
|
||||||
|
public:
|
||||||
|
FakeDecoderTask(FakeVideoDecoder* decoder,
|
||||||
|
GMPVideoEncodedFrame* frame,
|
||||||
|
int64_t time)
|
||||||
|
: decoder_(decoder), frame_(frame), time_(time) {}
|
||||||
|
|
||||||
|
virtual void Run();
|
||||||
|
virtual void Destroy() { delete this; }
|
||||||
|
|
||||||
|
FakeVideoDecoder* decoder_;
|
||||||
|
GMPVideoEncodedFrame* frame_;
|
||||||
|
int64_t time_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FakeVideoDecoder : public GMPVideoDecoder {
|
||||||
|
public:
|
||||||
|
explicit FakeVideoDecoder (GMPVideoHost* hostAPI) :
|
||||||
|
host_ (hostAPI),
|
||||||
|
callback_ (NULL) {}
|
||||||
|
|
||||||
|
virtual ~FakeVideoDecoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void InitDecode (const GMPVideoCodec& codecSettings,
|
||||||
|
const uint8_t* aCodecSpecific,
|
||||||
|
uint32_t aCodecSpecificSize,
|
||||||
|
GMPVideoDecoderCallback* callback,
|
||||||
|
int32_t coreCount) {
|
||||||
|
GMPLOG (GL_INFO, "InitDecode");
|
||||||
|
|
||||||
|
callback_ = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Decode (GMPVideoEncodedFrame* inputFrame,
|
||||||
|
bool missingFrames,
|
||||||
|
const uint8_t* aCodecSpecificInfo,
|
||||||
|
uint32_t aCodecSpecificInfoLength,
|
||||||
|
int64_t renderTimeMs = -1) {
|
||||||
|
GMPLOG (GL_DEBUG, __FUNCTION__
|
||||||
|
<< "Decoding frame size=" << inputFrame->Size()
|
||||||
|
<< " timestamp=" << inputFrame->TimeStamp());
|
||||||
|
g_platform_api->runonmainthread(new FakeDecoderTask(this, inputFrame, renderTimeMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Drain() {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void DecodingComplete() {
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the decoded data back to the parent.
|
||||||
|
void Decode_m (GMPVideoEncodedFrame* inputFrame,
|
||||||
|
int64_t renderTimeMs) {
|
||||||
|
EncodedFrame *eframe;
|
||||||
|
if (inputFrame->Size() != (sizeof(*eframe))) {
|
||||||
|
GMPLOG (GL_ERROR, "Couldn't decode frame. Size=" << inputFrame->Size());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
eframe = reinterpret_cast<EncodedFrame*>(inputFrame->Buffer());
|
||||||
|
|
||||||
|
if (eframe->magic_ != ENCODED_FRAME_MAGIC) {
|
||||||
|
GMPLOG (GL_ERROR, "Couldn't decode frame. Magic=" << eframe->magic_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int width = eframe->width_;
|
||||||
|
int height = eframe->height_;
|
||||||
|
int ystride = eframe->width_;
|
||||||
|
int uvstride = eframe->width_/2;
|
||||||
|
|
||||||
|
GMPLOG (GL_DEBUG, "Video frame ready for display "
|
||||||
|
<< width
|
||||||
|
<< "x"
|
||||||
|
<< height
|
||||||
|
<< " timestamp="
|
||||||
|
<< inputFrame->TimeStamp());
|
||||||
|
|
||||||
|
GMPVideoFrame* ftmp = NULL;
|
||||||
|
|
||||||
|
// Translate the image.
|
||||||
|
GMPErr err = host_->CreateFrame (kGMPI420VideoFrame, &ftmp);
|
||||||
|
if (err != GMPNoErr) {
|
||||||
|
GMPLOG (GL_ERROR, "Couldn't allocate empty I420 frame");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GMPVideoi420Frame* frame = static_cast<GMPVideoi420Frame*> (ftmp);
|
||||||
|
err = frame->CreateEmptyFrame (
|
||||||
|
width, height,
|
||||||
|
ystride, uvstride, uvstride);
|
||||||
|
if (err != GMPNoErr) {
|
||||||
|
GMPLOG (GL_ERROR, "Couldn't make decoded frame");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(frame->Buffer(kGMPYPlane),
|
||||||
|
eframe->y_,
|
||||||
|
frame->AllocatedSize(kGMPYPlane));
|
||||||
|
memset(frame->Buffer(kGMPUPlane),
|
||||||
|
eframe->u_,
|
||||||
|
frame->AllocatedSize(kGMPUPlane));
|
||||||
|
memset(frame->Buffer(kGMPVPlane),
|
||||||
|
eframe->v_,
|
||||||
|
frame->AllocatedSize(kGMPVPlane));
|
||||||
|
|
||||||
|
GMPLOG (GL_DEBUG, "Allocated size = "
|
||||||
|
<< frame->AllocatedSize (kGMPYPlane));
|
||||||
|
frame->SetTimestamp (inputFrame->TimeStamp());
|
||||||
|
frame->SetDuration (inputFrame->Duration());
|
||||||
|
callback_->Decoded (frame);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
GMPVideoHost* host_;
|
||||||
|
GMPVideoDecoderCallback* callback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
void FakeDecoderTask::Run() {
|
||||||
|
decoder_->Decode_m(frame_, time_);
|
||||||
|
frame_->Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
PUBLIC_FUNC GMPErr
|
||||||
|
GMPInit (GMPPlatformAPI* aPlatformAPI) {
|
||||||
|
g_platform_api = aPlatformAPI;
|
||||||
|
return GMPNoErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PUBLIC_FUNC GMPErr
|
||||||
|
GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) {
|
||||||
|
if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) {
|
||||||
|
*aPluginApi = new FakeVideoDecoder (static_cast<GMPVideoHost*> (aHostAPI));
|
||||||
|
return GMPNoErr;
|
||||||
|
} else if (!strcmp (aApiName, GMP_API_VIDEO_ENCODER)) {
|
||||||
|
*aPluginApi = new FakeVideoEncoder (static_cast<GMPVideoHost*> (aHostAPI));
|
||||||
|
return GMPNoErr;
|
||||||
|
#if defined(GMP_FAKE_SUPPORT_DECRYPT)
|
||||||
|
} else if (!strcmp (aApiName, GMP_API_DECRYPTOR)) {
|
||||||
|
*aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI));
|
||||||
|
return GMPNoErr;
|
||||||
|
} else if (!strcmp (aApiName, GMP_API_ASYNC_SHUTDOWN)) {
|
||||||
|
*aPluginApi = new TestAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI));
|
||||||
|
return GMPNoErr;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return GMPGenericErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PUBLIC_FUNC void
|
||||||
|
GMPShutdown (void) {
|
||||||
|
g_platform_api = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
FINAL_TARGET = 'dist/bin/gmp-fakeopenh264/1.0'
|
FINAL_TARGET = 'dist/bin/gmp-fakeopenh264/1.0'
|
||||||
SOURCES += [
|
SOURCES += [
|
||||||
'../gmp-plugin/gmp-fake.cpp',
|
'gmp-fake-openh264.cpp',
|
||||||
]
|
]
|
||||||
|
|
||||||
SharedLibrary("fakeopenh264")
|
SharedLibrary("fakeopenh264")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
Name: fake
|
Name: fake
|
||||||
Description: Fake GMP Plugin
|
Description: Fake GMP Plugin
|
||||||
Version: 1.0
|
Version: 1.0
|
||||||
APIs: encode-video[h264:fake], decode-video[h264:fake], eme-decrypt-v7[fake]
|
APIs: decode-video[h264:broken], eme-decrypt-v7[fake]
|
||||||
Libraries: dxva2.dll
|
Libraries: dxva2.dll
|
||||||
|
|
|
@ -34,21 +34,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <time.h>
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <assert.h>
|
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
#include "gmp-platform.h"
|
#include "gmp-platform.h"
|
||||||
#include "gmp-video-host.h"
|
|
||||||
#include "gmp-video-encode.h"
|
|
||||||
#include "gmp-video-decode.h"
|
#include "gmp-video-decode.h"
|
||||||
#include "gmp-video-frame-i420.h"
|
|
||||||
#include "gmp-video-frame-encoded.h"
|
|
||||||
|
|
||||||
#if defined(GMP_FAKE_SUPPORT_DECRYPT)
|
#if defined(GMP_FAKE_SUPPORT_DECRYPT)
|
||||||
#include "gmp-decryption.h"
|
#include "gmp-decryption.h"
|
||||||
|
@ -62,337 +54,8 @@
|
||||||
#define PUBLIC_FUNC
|
#define PUBLIC_FUNC
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define BIG_FRAME 10000
|
|
||||||
|
|
||||||
static int g_log_level = 0;
|
|
||||||
|
|
||||||
#define GMPLOG(l, x) do { \
|
|
||||||
if (l <= g_log_level) { \
|
|
||||||
const char *log_string = "unknown"; \
|
|
||||||
if ((l >= 0) && (l <= 3)) { \
|
|
||||||
log_string = kLogStrings[l]; \
|
|
||||||
} \
|
|
||||||
std::cerr << log_string << ": " << x << std::endl; \
|
|
||||||
} \
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
#define GL_CRIT 0
|
|
||||||
#define GL_ERROR 1
|
|
||||||
#define GL_INFO 2
|
|
||||||
#define GL_DEBUG 3
|
|
||||||
|
|
||||||
const char* kLogStrings[] = {
|
|
||||||
"Critical",
|
|
||||||
"Error",
|
|
||||||
"Info",
|
|
||||||
"Debug"
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
GMPPlatformAPI* g_platform_api = NULL;
|
GMPPlatformAPI* g_platform_api = NULL;
|
||||||
|
|
||||||
class FakeVideoEncoder;
|
|
||||||
class FakeVideoDecoder;
|
|
||||||
|
|
||||||
struct EncodedFrame {
|
|
||||||
uint32_t length_;
|
|
||||||
uint8_t h264_compat_;
|
|
||||||
uint32_t magic_;
|
|
||||||
uint32_t width_;
|
|
||||||
uint32_t height_;
|
|
||||||
uint8_t y_;
|
|
||||||
uint8_t u_;
|
|
||||||
uint8_t v_;
|
|
||||||
uint32_t timestamp_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define ENCODED_FRAME_MAGIC 0x4652414d
|
|
||||||
|
|
||||||
class FakeEncoderTask : public GMPTask {
|
|
||||||
public:
|
|
||||||
FakeEncoderTask(FakeVideoEncoder* encoder,
|
|
||||||
GMPVideoi420Frame* frame,
|
|
||||||
GMPVideoFrameType type)
|
|
||||||
: encoder_(encoder), frame_(frame), type_(type) {}
|
|
||||||
|
|
||||||
virtual void Run();
|
|
||||||
virtual void Destroy() { delete this; }
|
|
||||||
|
|
||||||
FakeVideoEncoder* encoder_;
|
|
||||||
GMPVideoi420Frame* frame_;
|
|
||||||
GMPVideoFrameType type_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class FakeVideoEncoder : public GMPVideoEncoder {
|
|
||||||
public:
|
|
||||||
explicit FakeVideoEncoder (GMPVideoHost* hostAPI) :
|
|
||||||
host_ (hostAPI),
|
|
||||||
callback_ (NULL) {}
|
|
||||||
|
|
||||||
virtual void InitEncode (const GMPVideoCodec& codecSettings,
|
|
||||||
const uint8_t* aCodecSpecific,
|
|
||||||
uint32_t aCodecSpecificSize,
|
|
||||||
GMPVideoEncoderCallback* callback,
|
|
||||||
int32_t numberOfCores,
|
|
||||||
uint32_t maxPayloadSize) {
|
|
||||||
callback_ = callback;
|
|
||||||
|
|
||||||
GMPLOG (GL_INFO, "Initialized encoder");
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Encode (GMPVideoi420Frame* inputImage,
|
|
||||||
const uint8_t* aCodecSpecificInfo,
|
|
||||||
uint32_t aCodecSpecificInfoLength,
|
|
||||||
const GMPVideoFrameType* aFrameTypes,
|
|
||||||
uint32_t aFrameTypesLength) {
|
|
||||||
GMPLOG (GL_DEBUG,
|
|
||||||
__FUNCTION__
|
|
||||||
<< " size="
|
|
||||||
<< inputImage->Width() << "x" << inputImage->Height());
|
|
||||||
|
|
||||||
assert (aFrameTypesLength != 0);
|
|
||||||
|
|
||||||
g_platform_api->runonmainthread(new FakeEncoderTask(this,
|
|
||||||
inputImage,
|
|
||||||
aFrameTypes[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Encode_m (GMPVideoi420Frame* inputImage,
|
|
||||||
GMPVideoFrameType frame_type) {
|
|
||||||
if (frame_type == kGMPKeyFrame) {
|
|
||||||
if (!inputImage)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!inputImage) {
|
|
||||||
GMPLOG (GL_ERROR, "no input image");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now return the encoded data back to the parent.
|
|
||||||
GMPVideoFrame* ftmp;
|
|
||||||
GMPErr err = host_->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
|
|
||||||
if (err != GMPNoErr) {
|
|
||||||
GMPLOG (GL_ERROR, "Error creating encoded frame");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GMPVideoEncodedFrame* f = static_cast<GMPVideoEncodedFrame*> (ftmp);
|
|
||||||
|
|
||||||
// Encode this in a frame that looks a little bit like H.264.
|
|
||||||
// Note that we don't do PPS or SPS.
|
|
||||||
// Copy the data. This really should convert this to network byte order.
|
|
||||||
EncodedFrame eframe;
|
|
||||||
eframe.length_ = sizeof(eframe) - sizeof(uint32_t);
|
|
||||||
eframe.h264_compat_ = 5; // Emulate a H.264 IDR NAL.
|
|
||||||
eframe.magic_ = ENCODED_FRAME_MAGIC;
|
|
||||||
eframe.width_ = inputImage->Width();
|
|
||||||
eframe.height_ = inputImage->Height();
|
|
||||||
eframe.y_ = AveragePlane(inputImage->Buffer(kGMPYPlane),
|
|
||||||
inputImage->AllocatedSize(kGMPYPlane));
|
|
||||||
eframe.u_ = AveragePlane(inputImage->Buffer(kGMPUPlane),
|
|
||||||
inputImage->AllocatedSize(kGMPUPlane));
|
|
||||||
eframe.v_ = AveragePlane(inputImage->Buffer(kGMPVPlane),
|
|
||||||
inputImage->AllocatedSize(kGMPVPlane));
|
|
||||||
|
|
||||||
eframe.timestamp_ = inputImage->Timestamp();
|
|
||||||
|
|
||||||
err = f->CreateEmptyFrame (sizeof(eframe) +
|
|
||||||
(frame_type == kGMPKeyFrame ? sizeof(uint32_t) + BIG_FRAME : 0));
|
|
||||||
if (err != GMPNoErr) {
|
|
||||||
GMPLOG (GL_ERROR, "Error allocating frame data");
|
|
||||||
f->Destroy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
memcpy(f->Buffer(), &eframe, sizeof(eframe));
|
|
||||||
if (frame_type == kGMPKeyFrame) {
|
|
||||||
*((uint32_t*) f->Buffer() + sizeof(eframe)) = BIG_FRAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
f->SetEncodedWidth (inputImage->Width());
|
|
||||||
f->SetEncodedHeight (inputImage->Height());
|
|
||||||
f->SetTimeStamp (inputImage->Timestamp());
|
|
||||||
f->SetFrameType (frame_type);
|
|
||||||
f->SetCompleteFrame (true);
|
|
||||||
f->SetBufferType(GMP_BufferLength32);
|
|
||||||
|
|
||||||
GMPLOG (GL_DEBUG, "Encoding complete. type= "
|
|
||||||
<< f->FrameType()
|
|
||||||
<< " length="
|
|
||||||
<< f->Size()
|
|
||||||
<< " timestamp="
|
|
||||||
<< f->TimeStamp());
|
|
||||||
|
|
||||||
// Return the encoded frame.
|
|
||||||
GMPCodecSpecificInfo info;
|
|
||||||
memset (&info, 0, sizeof (info));
|
|
||||||
info.mCodecType = kGMPVideoCodecH264;
|
|
||||||
info.mBufferType = GMP_BufferLength32;
|
|
||||||
info.mCodecSpecific.mH264.mSimulcastIdx = 0;
|
|
||||||
GMPLOG (GL_DEBUG, "Calling callback");
|
|
||||||
callback_->Encoded (f, reinterpret_cast<uint8_t*> (&info), sizeof(info));
|
|
||||||
GMPLOG (GL_DEBUG, "Callback called");
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void SetChannelParameters (uint32_t aPacketLoss, uint32_t aRTT) {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void SetRates (uint32_t aNewBitRate, uint32_t aFrameRate) {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void SetPeriodicKeyFrames (bool aEnable) {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void EncodingComplete() {
|
|
||||||
delete this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t AveragePlane(uint8_t* ptr, size_t len) {
|
|
||||||
uint64_t val = 0;
|
|
||||||
|
|
||||||
for (size_t i=0; i<len; ++i) {
|
|
||||||
val += ptr[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return (val / len) % 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
GMPVideoHost* host_;
|
|
||||||
GMPVideoEncoderCallback* callback_;
|
|
||||||
};
|
|
||||||
|
|
||||||
void FakeEncoderTask::Run() {
|
|
||||||
encoder_->Encode_m(frame_, type_);
|
|
||||||
frame_->Destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
class FakeDecoderTask : public GMPTask {
|
|
||||||
public:
|
|
||||||
FakeDecoderTask(FakeVideoDecoder* decoder,
|
|
||||||
GMPVideoEncodedFrame* frame,
|
|
||||||
int64_t time)
|
|
||||||
: decoder_(decoder), frame_(frame), time_(time) {}
|
|
||||||
|
|
||||||
virtual void Run();
|
|
||||||
virtual void Destroy() { delete this; }
|
|
||||||
|
|
||||||
FakeVideoDecoder* decoder_;
|
|
||||||
GMPVideoEncodedFrame* frame_;
|
|
||||||
int64_t time_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class FakeVideoDecoder : public GMPVideoDecoder {
|
|
||||||
public:
|
|
||||||
explicit FakeVideoDecoder (GMPVideoHost* hostAPI) :
|
|
||||||
host_ (hostAPI),
|
|
||||||
callback_ (NULL) {}
|
|
||||||
|
|
||||||
virtual ~FakeVideoDecoder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void InitDecode (const GMPVideoCodec& codecSettings,
|
|
||||||
const uint8_t* aCodecSpecific,
|
|
||||||
uint32_t aCodecSpecificSize,
|
|
||||||
GMPVideoDecoderCallback* callback,
|
|
||||||
int32_t coreCount) {
|
|
||||||
GMPLOG (GL_INFO, "InitDecode");
|
|
||||||
|
|
||||||
callback_ = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Decode (GMPVideoEncodedFrame* inputFrame,
|
|
||||||
bool missingFrames,
|
|
||||||
const uint8_t* aCodecSpecificInfo,
|
|
||||||
uint32_t aCodecSpecificInfoLength,
|
|
||||||
int64_t renderTimeMs = -1) {
|
|
||||||
GMPLOG (GL_DEBUG, __FUNCTION__
|
|
||||||
<< "Decoding frame size=" << inputFrame->Size()
|
|
||||||
<< " timestamp=" << inputFrame->TimeStamp());
|
|
||||||
g_platform_api->runonmainthread(new FakeDecoderTask(this, inputFrame, renderTimeMs));
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Reset() {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Drain() {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void DecodingComplete() {
|
|
||||||
delete this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the decoded data back to the parent.
|
|
||||||
void Decode_m (GMPVideoEncodedFrame* inputFrame,
|
|
||||||
int64_t renderTimeMs) {
|
|
||||||
EncodedFrame *eframe;
|
|
||||||
if (inputFrame->Size() != (sizeof(*eframe))) {
|
|
||||||
GMPLOG (GL_ERROR, "Couldn't decode frame. Size=" << inputFrame->Size());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
eframe = reinterpret_cast<EncodedFrame*>(inputFrame->Buffer());
|
|
||||||
|
|
||||||
if (eframe->magic_ != ENCODED_FRAME_MAGIC) {
|
|
||||||
GMPLOG (GL_ERROR, "Couldn't decode frame. Magic=" << eframe->magic_);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = eframe->width_;
|
|
||||||
int height = eframe->height_;
|
|
||||||
int ystride = eframe->width_;
|
|
||||||
int uvstride = eframe->width_/2;
|
|
||||||
|
|
||||||
GMPLOG (GL_DEBUG, "Video frame ready for display "
|
|
||||||
<< width
|
|
||||||
<< "x"
|
|
||||||
<< height
|
|
||||||
<< " timestamp="
|
|
||||||
<< inputFrame->TimeStamp());
|
|
||||||
|
|
||||||
GMPVideoFrame* ftmp = NULL;
|
|
||||||
|
|
||||||
// Translate the image.
|
|
||||||
GMPErr err = host_->CreateFrame (kGMPI420VideoFrame, &ftmp);
|
|
||||||
if (err != GMPNoErr) {
|
|
||||||
GMPLOG (GL_ERROR, "Couldn't allocate empty I420 frame");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GMPVideoi420Frame* frame = static_cast<GMPVideoi420Frame*> (ftmp);
|
|
||||||
err = frame->CreateEmptyFrame (
|
|
||||||
width, height,
|
|
||||||
ystride, uvstride, uvstride);
|
|
||||||
if (err != GMPNoErr) {
|
|
||||||
GMPLOG (GL_ERROR, "Couldn't make decoded frame");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(frame->Buffer(kGMPYPlane),
|
|
||||||
eframe->y_,
|
|
||||||
frame->AllocatedSize(kGMPYPlane));
|
|
||||||
memset(frame->Buffer(kGMPUPlane),
|
|
||||||
eframe->u_,
|
|
||||||
frame->AllocatedSize(kGMPUPlane));
|
|
||||||
memset(frame->Buffer(kGMPVPlane),
|
|
||||||
eframe->v_,
|
|
||||||
frame->AllocatedSize(kGMPVPlane));
|
|
||||||
|
|
||||||
GMPLOG (GL_DEBUG, "Allocated size = "
|
|
||||||
<< frame->AllocatedSize (kGMPYPlane));
|
|
||||||
frame->SetTimestamp (inputFrame->TimeStamp());
|
|
||||||
frame->SetDuration (inputFrame->Duration());
|
|
||||||
callback_->Decoded (frame);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
GMPVideoHost* host_;
|
|
||||||
GMPVideoDecoderCallback* callback_;
|
|
||||||
};
|
|
||||||
|
|
||||||
void FakeDecoderTask::Run() {
|
|
||||||
decoder_->Decode_m(frame_, time_);
|
|
||||||
frame_->Destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
PUBLIC_FUNC GMPErr
|
PUBLIC_FUNC GMPErr
|
||||||
|
@ -404,11 +67,10 @@ extern "C" {
|
||||||
PUBLIC_FUNC GMPErr
|
PUBLIC_FUNC GMPErr
|
||||||
GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) {
|
GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) {
|
||||||
if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) {
|
if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) {
|
||||||
*aPluginApi = new FakeVideoDecoder (static_cast<GMPVideoHost*> (aHostAPI));
|
// Note: Deliberately advertise in our .info file that we support
|
||||||
return GMPNoErr;
|
// video-decode, but we fail the "get" call here to simulate what
|
||||||
} else if (!strcmp (aApiName, GMP_API_VIDEO_ENCODER)) {
|
// happens when decoder init fails.
|
||||||
*aPluginApi = new FakeVideoEncoder (static_cast<GMPVideoHost*> (aHostAPI));
|
return GMPGenericErr;
|
||||||
return GMPNoErr;
|
|
||||||
#if defined(GMP_FAKE_SUPPORT_DECRYPT)
|
#if defined(GMP_FAKE_SUPPORT_DECRYPT)
|
||||||
} else if (!strcmp (aApiName, GMP_API_DECRYPTOR)) {
|
} else if (!strcmp (aApiName, GMP_API_DECRYPTOR)) {
|
||||||
*aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI));
|
*aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI));
|
||||||
|
|
|
@ -16,25 +16,19 @@ GMPDecryptorParent::GMPDecryptorParent(GMPContentParent* aPlugin)
|
||||||
, mShuttingDown(false)
|
, mShuttingDown(false)
|
||||||
, mActorDestroyed(false)
|
, mActorDestroyed(false)
|
||||||
, mPlugin(aPlugin)
|
, mPlugin(aPlugin)
|
||||||
|
, mPluginId(aPlugin->GetPluginId())
|
||||||
, mCallback(nullptr)
|
, mCallback(nullptr)
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
, mGMPThread(aPlugin->GMPThread())
|
, mGMPThread(aPlugin->GMPThread())
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(mPlugin && mGMPThread);
|
MOZ_ASSERT(mPlugin && mGMPThread);
|
||||||
mPluginId = aPlugin->GetPluginId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GMPDecryptorParent::~GMPDecryptorParent()
|
GMPDecryptorParent::~GMPDecryptorParent()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t
|
|
||||||
GMPDecryptorParent::GetPluginId() const
|
|
||||||
{
|
|
||||||
return mPluginId;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback)
|
GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback)
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,7 +29,7 @@ public:
|
||||||
|
|
||||||
// GMPDecryptorProxy
|
// GMPDecryptorProxy
|
||||||
|
|
||||||
virtual const uint32_t GetPluginId() const override;
|
virtual const uint32_t GetPluginId() const override { return mPluginId; }
|
||||||
|
|
||||||
virtual nsresult Init(GMPDecryptorProxyCallback* aCallback) override;
|
virtual nsresult Init(GMPDecryptorProxyCallback* aCallback) override;
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ GMPParent::GMPParent()
|
||||||
, mAsyncShutdownRequired(false)
|
, mAsyncShutdownRequired(false)
|
||||||
, mAsyncShutdownInProgress(false)
|
, mAsyncShutdownInProgress(false)
|
||||||
, mChildPid(0)
|
, mChildPid(0)
|
||||||
|
, mHoldingSelfRef(false)
|
||||||
{
|
{
|
||||||
LOGD("GMPParent ctor");
|
LOGD("GMPParent ctor");
|
||||||
mPluginId = GeckoChildProcessHost::GetUniqueID();
|
mPluginId = GeckoChildProcessHost::GetUniqueID();
|
||||||
|
@ -74,6 +75,8 @@ GMPParent::~GMPParent()
|
||||||
// Can't Close or Destroy the process here, since destruction is MainThread only
|
// Can't Close or Destroy the process here, since destruction is MainThread only
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
LOGD("GMPParent dtor");
|
LOGD("GMPParent dtor");
|
||||||
|
|
||||||
|
MOZ_ASSERT(!mProcess);
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
|
@ -178,6 +181,13 @@ GMPParent::LoadProcess()
|
||||||
|
|
||||||
mState = GMPStateLoaded;
|
mState = GMPStateLoaded;
|
||||||
|
|
||||||
|
// Hold a self ref while the child process is alive. This ensures that
|
||||||
|
// during shutdown the GMPParent stays we stay alive long enough to
|
||||||
|
// terminate the child process.
|
||||||
|
MOZ_ASSERT(!mHoldingSelfRef);
|
||||||
|
mHoldingSelfRef = true;
|
||||||
|
AddRef();
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,6 +418,10 @@ GMPParent::DeleteProcess()
|
||||||
new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId)),
|
new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId)),
|
||||||
NS_DISPATCH_NORMAL);
|
NS_DISPATCH_NORMAL);
|
||||||
|
|
||||||
|
if (mHoldingSelfRef) {
|
||||||
|
Release();
|
||||||
|
mHoldingSelfRef = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GMPState
|
GMPState
|
||||||
|
|
|
@ -218,6 +218,12 @@ private:
|
||||||
bool mAsyncShutdownInProgress;
|
bool mAsyncShutdownInProgress;
|
||||||
|
|
||||||
int mChildPid;
|
int mChildPid;
|
||||||
|
|
||||||
|
// We hold a self reference to ourself while the child process is alive.
|
||||||
|
// This ensures that if the GMPService tries to shut us down and drops
|
||||||
|
// its reference to us, we stay alive long enough for the child process
|
||||||
|
// to terminate gracefully.
|
||||||
|
bool mHoldingSelfRef;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gmp
|
} // namespace gmp
|
||||||
|
|
|
@ -37,6 +37,9 @@
|
||||||
#include "nsIFile.h"
|
#include "nsIFile.h"
|
||||||
#include "nsISimpleEnumerator.h"
|
#include "nsISimpleEnumerator.h"
|
||||||
|
|
||||||
|
#include "mozilla/dom/PluginCrashedEvent.h"
|
||||||
|
#include "mozilla/EventDispatcher.h"
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
|
|
||||||
#ifdef LOG
|
#ifdef LOG
|
||||||
|
@ -160,33 +163,115 @@ GeckoMediaPluginService::RemoveObsoletePluginCrashCallbacks()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
|
for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
|
||||||
nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
|
nsRefPtr<GMPCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
|
||||||
if (!callback->IsStillValid()) {
|
if (!callback->IsStillValid()) {
|
||||||
LOGD(("%s::%s - Removing obsolete callback for pluginId %i",
|
LOGD(("%s::%s - Removing obsolete callback for pluginId %i",
|
||||||
__CLASS__, __FUNCTION__, callback->PluginId()));
|
__CLASS__, __FUNCTION__, callback->GetPluginId()));
|
||||||
mPluginCrashCallbacks.RemoveElementAt(i - 1);
|
mPluginCrashCallbacks.RemoveElementAt(i - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
GeckoMediaPluginService::GMPCrashCallback::GMPCrashCallback(const uint32_t aPluginId,
|
||||||
GeckoMediaPluginService::AddPluginCrashCallback(
|
nsPIDOMWindow* aParentWindow,
|
||||||
nsRefPtr<PluginCrashCallback> aPluginCrashCallback)
|
nsIDocument* aDocument)
|
||||||
|
: mPluginId(aPluginId)
|
||||||
|
, mParentWindowWeakPtr(do_GetWeakReference(aParentWindow))
|
||||||
|
, mDocumentWeakPtr(do_GetWeakReference(aDocument))
|
||||||
{
|
{
|
||||||
RemoveObsoletePluginCrashCallbacks();
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
mPluginCrashCallbacks.AppendElement(aPluginCrashCallback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
GeckoMediaPluginService::RemovePluginCrashCallbacks(const uint32_t aPluginId)
|
GeckoMediaPluginService::GMPCrashCallback::Run(const nsACString& aPluginName)
|
||||||
{
|
{
|
||||||
|
dom::PluginCrashedEventInit init;
|
||||||
|
init.mPluginID = mPluginId;
|
||||||
|
init.mBubbles = true;
|
||||||
|
init.mCancelable = true;
|
||||||
|
init.mGmpPlugin = true;
|
||||||
|
CopyUTF8toUTF16(aPluginName, init.mPluginName);
|
||||||
|
init.mSubmittedCrashReport = false;
|
||||||
|
|
||||||
|
// The following PluginCrashedEvent fields stay empty:
|
||||||
|
// init.mBrowserDumpID
|
||||||
|
// init.mPluginFilename
|
||||||
|
// TODO: Can/should we fill them?
|
||||||
|
|
||||||
|
nsCOMPtr<nsPIDOMWindow> parentWindow;
|
||||||
|
nsCOMPtr<nsIDocument> document;
|
||||||
|
if (!GetParentWindowAndDocumentIfValid(parentWindow, document)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsRefPtr<dom::PluginCrashedEvent> event =
|
||||||
|
dom::PluginCrashedEvent::Constructor(document, NS_LITERAL_STRING("PluginCrashed"), init);
|
||||||
|
event->SetTrusted(true);
|
||||||
|
event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
|
||||||
|
|
||||||
|
EventDispatcher::DispatchDOMEvent(parentWindow, nullptr, event, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
GeckoMediaPluginService::GMPCrashCallback::IsStillValid()
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsPIDOMWindow> parentWindow;
|
||||||
|
nsCOMPtr<nsIDocument> document;
|
||||||
|
return GetParentWindowAndDocumentIfValid(parentWindow, document);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
GeckoMediaPluginService::GMPCrashCallback::GetParentWindowAndDocumentIfValid(
|
||||||
|
nsCOMPtr<nsPIDOMWindow>& parentWindow,
|
||||||
|
nsCOMPtr<nsIDocument>& document)
|
||||||
|
{
|
||||||
|
parentWindow = do_QueryReferent(mParentWindowWeakPtr);
|
||||||
|
if (!parentWindow) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
document = do_QueryReferent(mDocumentWeakPtr);
|
||||||
|
if (!document) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
nsCOMPtr<nsIDocument> parentWindowDocument = parentWindow->GetExtantDoc();
|
||||||
|
if (!parentWindowDocument || document.get() != parentWindowDocument.get()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GeckoMediaPluginService::AddPluginCrashedEventTarget(const uint32_t aPluginId,
|
||||||
|
nsPIDOMWindow* aParentWindow)
|
||||||
|
{
|
||||||
|
LOGD(("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId));
|
||||||
|
|
||||||
|
if (NS_WARN_IF(!aParentWindow)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nsCOMPtr<nsIDocument> doc = aParentWindow->GetExtantDoc();
|
||||||
|
if (NS_WARN_IF(!doc)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nsRefPtr<GMPCrashCallback> callback(new GMPCrashCallback(aPluginId, aParentWindow, doc));
|
||||||
RemoveObsoletePluginCrashCallbacks();
|
RemoveObsoletePluginCrashCallbacks();
|
||||||
for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
|
|
||||||
nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
|
// If the plugin with that ID has already crashed without being handled,
|
||||||
if (callback->PluginId() == aPluginId) {
|
// just run the handler now.
|
||||||
mPluginCrashCallbacks.RemoveElementAt(i - 1);
|
for (size_t i = mPluginCrashes.Length(); i != 0; --i) {
|
||||||
|
size_t index = i - 1;
|
||||||
|
const PluginCrash& crash = mPluginCrashes[index];
|
||||||
|
if (crash.mPluginId == aPluginId) {
|
||||||
|
LOGD(("%s::%s(%i) - added crash handler for crashed plugin, running handler #%u",
|
||||||
|
__CLASS__, __FUNCTION__, aPluginId, index));
|
||||||
|
callback->Run(crash.mPluginName);
|
||||||
|
mPluginCrashes.RemoveElementAt(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remember crash, so if a handler is added for it later, we report the
|
||||||
|
// crash to that window too.
|
||||||
|
mPluginCrashCallbacks.AppendElement(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -195,20 +280,21 @@ GeckoMediaPluginService::RunPluginCrashCallbacks(const uint32_t aPluginId,
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
LOGD(("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId));
|
LOGD(("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId));
|
||||||
|
RemoveObsoletePluginCrashCallbacks();
|
||||||
|
|
||||||
for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
|
for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
|
||||||
nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
|
nsRefPtr<GMPCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
|
||||||
const uint32_t callbackPluginId = callback->PluginId();
|
if (callback->GetPluginId() == aPluginId) {
|
||||||
if (!callback->IsStillValid()) {
|
|
||||||
LOGD(("%s::%s(%i) - Removing obsolete callback for pluginId %i",
|
|
||||||
__CLASS__, __FUNCTION__, aPluginId, callback->PluginId()));
|
|
||||||
mPluginCrashCallbacks.RemoveElementAt(i - 1);
|
|
||||||
} else if (callbackPluginId == aPluginId) {
|
|
||||||
LOGD(("%s::%s(%i) - Running #%u",
|
LOGD(("%s::%s(%i) - Running #%u",
|
||||||
__CLASS__, __FUNCTION__, aPluginId, i - 1));
|
__CLASS__, __FUNCTION__, aPluginId, i - 1));
|
||||||
callback->Run(aPluginName);
|
callback->Run(aPluginName);
|
||||||
mPluginCrashCallbacks.RemoveElementAt(i - 1);
|
mPluginCrashCallbacks.RemoveElementAt(i - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mPluginCrashes.AppendElement(PluginCrash(aPluginId, aPluginName));
|
||||||
|
if (mPluginCrashes.Length() > MAX_PLUGIN_CRASHES) {
|
||||||
|
mPluginCrashes.RemoveElementAt(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
#include "nsCOMPtr.h"
|
#include "nsCOMPtr.h"
|
||||||
#include "nsIThread.h"
|
#include "nsIThread.h"
|
||||||
#include "nsThreadUtils.h"
|
#include "nsThreadUtils.h"
|
||||||
|
#include "nsPIDOMWindow.h"
|
||||||
|
#include "nsIDocument.h"
|
||||||
|
#include "nsIWeakReference.h"
|
||||||
|
|
||||||
template <class> struct already_AddRefed;
|
template <class> struct already_AddRefed;
|
||||||
|
|
||||||
|
@ -62,37 +65,21 @@ public:
|
||||||
|
|
||||||
int32_t AsyncShutdownTimeoutMs();
|
int32_t AsyncShutdownTimeoutMs();
|
||||||
|
|
||||||
class PluginCrashCallback
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
NS_INLINE_DECL_REFCOUNTING(PluginCrashCallback)
|
|
||||||
|
|
||||||
PluginCrashCallback(const uint32_t aPluginId)
|
|
||||||
: mPluginId(aPluginId)
|
|
||||||
{
|
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
|
||||||
}
|
|
||||||
const uint32_t PluginId() const { return mPluginId; }
|
|
||||||
virtual void Run(const nsACString& aPluginName) = 0;
|
|
||||||
virtual bool IsStillValid() = 0; // False if callback has become useless.
|
|
||||||
protected:
|
|
||||||
virtual ~PluginCrashCallback()
|
|
||||||
{
|
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
const uint32_t mPluginId;
|
|
||||||
};
|
|
||||||
void RemoveObsoletePluginCrashCallbacks(); // Called from add/remove/run.
|
|
||||||
void AddPluginCrashCallback(nsRefPtr<PluginCrashCallback> aPluginCrashCallback);
|
|
||||||
void RemovePluginCrashCallbacks(const uint32_t aPluginId);
|
|
||||||
void RunPluginCrashCallbacks(const uint32_t aPluginId,
|
void RunPluginCrashCallbacks(const uint32_t aPluginId,
|
||||||
const nsACString& aPluginName);
|
const nsACString& aPluginName);
|
||||||
|
|
||||||
|
// Sets the window to which 'PluginCrashed' chromeonly event is dispatched.
|
||||||
|
// Note: if the plugin has crashed before the target window has been set,
|
||||||
|
// the 'PluginCrashed' event is dispatched as soon as a target window is set.
|
||||||
|
void AddPluginCrashedEventTarget(const uint32_t aPluginId,
|
||||||
|
nsPIDOMWindow* aParentWindow);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
GeckoMediaPluginService();
|
GeckoMediaPluginService();
|
||||||
virtual ~GeckoMediaPluginService();
|
virtual ~GeckoMediaPluginService();
|
||||||
|
|
||||||
|
void RemoveObsoletePluginCrashCallbacks(); // Called from add/run.
|
||||||
|
|
||||||
virtual void InitializePlugins() = 0;
|
virtual void InitializePlugins() = 0;
|
||||||
virtual bool GetContentParentFrom(const nsACString& aNodeId,
|
virtual bool GetContentParentFrom(const nsACString& aNodeId,
|
||||||
const nsCString& aAPI,
|
const nsCString& aAPI,
|
||||||
|
@ -102,14 +89,54 @@ protected:
|
||||||
nsresult GMPDispatch(nsIRunnable* event, uint32_t flags = NS_DISPATCH_NORMAL);
|
nsresult GMPDispatch(nsIRunnable* event, uint32_t flags = NS_DISPATCH_NORMAL);
|
||||||
void ShutdownGMPThread();
|
void ShutdownGMPThread();
|
||||||
|
|
||||||
protected:
|
|
||||||
Mutex mMutex; // Protects mGMPThread and mGMPThreadShutdown and some members
|
Mutex mMutex; // Protects mGMPThread and mGMPThreadShutdown and some members
|
||||||
// in derived classes.
|
// in derived classes.
|
||||||
nsCOMPtr<nsIThread> mGMPThread;
|
nsCOMPtr<nsIThread> mGMPThread;
|
||||||
bool mGMPThreadShutdown;
|
bool mGMPThreadShutdown;
|
||||||
bool mShuttingDownOnGMPThread;
|
bool mShuttingDownOnGMPThread;
|
||||||
|
|
||||||
nsTArray<nsRefPtr<PluginCrashCallback>> mPluginCrashCallbacks;
|
class GMPCrashCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NS_INLINE_DECL_REFCOUNTING(GMPCrashCallback)
|
||||||
|
|
||||||
|
GMPCrashCallback(const uint32_t aPluginId,
|
||||||
|
nsPIDOMWindow* aParentWindow,
|
||||||
|
nsIDocument* aDocument);
|
||||||
|
void Run(const nsACString& aPluginName);
|
||||||
|
bool IsStillValid();
|
||||||
|
const uint32_t GetPluginId() const { return mPluginId; }
|
||||||
|
private:
|
||||||
|
virtual ~GMPCrashCallback() { MOZ_ASSERT(NS_IsMainThread()); }
|
||||||
|
|
||||||
|
bool GetParentWindowAndDocumentIfValid(nsCOMPtr<nsPIDOMWindow>& parentWindow,
|
||||||
|
nsCOMPtr<nsIDocument>& document);
|
||||||
|
const uint32_t mPluginId;
|
||||||
|
nsWeakPtr mParentWindowWeakPtr;
|
||||||
|
nsWeakPtr mDocumentWeakPtr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PluginCrash
|
||||||
|
{
|
||||||
|
PluginCrash(uint32_t aPluginId,
|
||||||
|
const nsACString& aPluginName)
|
||||||
|
: mPluginId(aPluginId)
|
||||||
|
, mPluginName(aPluginName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
uint32_t mPluginId;
|
||||||
|
nsCString mPluginName;
|
||||||
|
|
||||||
|
bool operator==(const PluginCrash& aOther) const {
|
||||||
|
return mPluginId == aOther.mPluginId &&
|
||||||
|
mPluginName == aOther.mPluginName;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const size_t MAX_PLUGIN_CRASHES = 100;
|
||||||
|
nsTArray<PluginCrash> mPluginCrashes;
|
||||||
|
|
||||||
|
nsTArray<nsRefPtr<GMPCrashCallback>> mPluginCrashCallbacks;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gmp
|
} // namespace gmp
|
||||||
|
|
|
@ -46,6 +46,7 @@ GMPVideoDecoderParent::GMPVideoDecoderParent(GMPContentParent* aPlugin)
|
||||||
, mPlugin(aPlugin)
|
, mPlugin(aPlugin)
|
||||||
, mCallback(nullptr)
|
, mCallback(nullptr)
|
||||||
, mVideoHost(this)
|
, mVideoHost(this)
|
||||||
|
, mPluginId(aPlugin->GetPluginId())
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(mPlugin);
|
MOZ_ASSERT(mPlugin);
|
||||||
}
|
}
|
||||||
|
@ -83,6 +84,10 @@ GMPVideoDecoderParent::InitDecode(const GMPVideoCodec& aCodecSettings,
|
||||||
GMPVideoDecoderCallbackProxy* aCallback,
|
GMPVideoDecoderCallbackProxy* aCallback,
|
||||||
int32_t aCoreCount)
|
int32_t aCoreCount)
|
||||||
{
|
{
|
||||||
|
if (mActorDestroyed) {
|
||||||
|
NS_WARNING("Trying to use a destroyed GMP video decoder!");
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
if (mIsOpen) {
|
if (mIsOpen) {
|
||||||
NS_WARNING("Trying to re-init an in-use GMP video decoder!");
|
NS_WARNING("Trying to re-init an in-use GMP video decoder!");
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
|
|
|
@ -44,7 +44,7 @@ public:
|
||||||
int64_t aRenderTimeMs = -1) override;
|
int64_t aRenderTimeMs = -1) override;
|
||||||
virtual nsresult Reset() override;
|
virtual nsresult Reset() override;
|
||||||
virtual nsresult Drain() override;
|
virtual nsresult Drain() override;
|
||||||
virtual const uint64_t ParentID() override { return reinterpret_cast<uint64_t>(mPlugin.get()); }
|
virtual const uint32_t GetPluginId() const override { return mPluginId; }
|
||||||
virtual const nsCString& GetDisplayName() const override;
|
virtual const nsCString& GetDisplayName() const override;
|
||||||
|
|
||||||
// GMPSharedMemManager
|
// GMPSharedMemManager
|
||||||
|
@ -85,6 +85,7 @@ private:
|
||||||
nsRefPtr<GMPContentParent> mPlugin;
|
nsRefPtr<GMPContentParent> mPlugin;
|
||||||
GMPVideoDecoderCallbackProxy* mCallback;
|
GMPVideoDecoderCallbackProxy* mCallback;
|
||||||
GMPVideoHostImpl mVideoHost;
|
GMPVideoHostImpl mVideoHost;
|
||||||
|
const uint32_t mPluginId;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gmp
|
} // namespace gmp
|
||||||
|
|
|
@ -44,7 +44,7 @@ public:
|
||||||
int64_t aRenderTimeMs = -1) = 0;
|
int64_t aRenderTimeMs = -1) = 0;
|
||||||
virtual nsresult Reset() = 0;
|
virtual nsresult Reset() = 0;
|
||||||
virtual nsresult Drain() = 0;
|
virtual nsresult Drain() = 0;
|
||||||
virtual const uint64_t ParentID() = 0;
|
virtual const uint32_t GetPluginId() const = 0;
|
||||||
|
|
||||||
// Call to tell GMP/plugin the consumer will no longer use this
|
// Call to tell GMP/plugin the consumer will no longer use this
|
||||||
// interface/codec.
|
// interface/codec.
|
||||||
|
|
|
@ -52,7 +52,8 @@ GMPVideoEncoderParent::GMPVideoEncoderParent(GMPContentParent *aPlugin)
|
||||||
mActorDestroyed(false),
|
mActorDestroyed(false),
|
||||||
mPlugin(aPlugin),
|
mPlugin(aPlugin),
|
||||||
mCallback(nullptr),
|
mCallback(nullptr),
|
||||||
mVideoHost(this)
|
mVideoHost(this),
|
||||||
|
mPluginId(aPlugin->GetPluginId())
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(mPlugin);
|
MOZ_ASSERT(mPlugin);
|
||||||
|
|
||||||
|
@ -211,12 +212,6 @@ GMPVideoEncoderParent::SetPeriodicKeyFrames(bool aEnable)
|
||||||
return GMPNoErr;
|
return GMPNoErr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t
|
|
||||||
GMPVideoEncoderParent::ParentID()
|
|
||||||
{
|
|
||||||
return mPlugin ? mPlugin->GetPluginId() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: Consider keeping ActorDestroy sync'd up when making changes here.
|
// Note: Consider keeping ActorDestroy sync'd up when making changes here.
|
||||||
void
|
void
|
||||||
GMPVideoEncoderParent::Shutdown()
|
GMPVideoEncoderParent::Shutdown()
|
||||||
|
|
|
@ -45,7 +45,7 @@ public:
|
||||||
virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) override;
|
virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) override;
|
||||||
virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) override;
|
virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) override;
|
||||||
virtual GMPErr SetPeriodicKeyFrames(bool aEnable) override;
|
virtual GMPErr SetPeriodicKeyFrames(bool aEnable) override;
|
||||||
virtual const uint32_t ParentID() override;
|
virtual const uint32_t GetPluginId() const override { return mPluginId; }
|
||||||
|
|
||||||
// GMPSharedMemManager
|
// GMPSharedMemManager
|
||||||
virtual bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override
|
virtual bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override
|
||||||
|
@ -82,6 +82,7 @@ private:
|
||||||
GMPVideoEncoderCallbackProxy* mCallback;
|
GMPVideoEncoderCallbackProxy* mCallback;
|
||||||
GMPVideoHostImpl mVideoHost;
|
GMPVideoHostImpl mVideoHost;
|
||||||
nsCOMPtr<nsIThread> mEncodedThread;
|
nsCOMPtr<nsIThread> mEncodedThread;
|
||||||
|
const uint32_t mPluginId;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gmp
|
} // namespace gmp
|
||||||
|
|
|
@ -46,7 +46,7 @@ public:
|
||||||
virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0;
|
virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0;
|
||||||
virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 0;
|
virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 0;
|
||||||
virtual GMPErr SetPeriodicKeyFrames(bool aEnable) = 0;
|
virtual GMPErr SetPeriodicKeyFrames(bool aEnable) = 0;
|
||||||
virtual const uint32_t ParentID() = 0;
|
virtual const uint32_t GetPluginId() const = 0;
|
||||||
|
|
||||||
// Call to tell GMP/plugin the consumer will no longer use this
|
// Call to tell GMP/plugin the consumer will no longer use this
|
||||||
// interface/codec.
|
// interface/codec.
|
||||||
|
|
|
@ -13,6 +13,11 @@
|
||||||
#include "GMPVideoEncoderProxy.h"
|
#include "GMPVideoEncoderProxy.h"
|
||||||
#include "GMPDecryptorProxy.h"
|
#include "GMPDecryptorProxy.h"
|
||||||
#include "GMPServiceParent.h"
|
#include "GMPServiceParent.h"
|
||||||
|
#ifdef XP_WIN
|
||||||
|
#include "GMPVideoDecoderTrialCreator.h"
|
||||||
|
#include "mozilla/dom/MediaKeySystemAccess.h"
|
||||||
|
#include "mozilla/Monitor.h"
|
||||||
|
#endif
|
||||||
#include "nsAppDirectoryServiceDefs.h"
|
#include "nsAppDirectoryServiceDefs.h"
|
||||||
#include "nsIFile.h"
|
#include "nsIFile.h"
|
||||||
#include "nsISimpleEnumerator.h"
|
#include "nsISimpleEnumerator.h"
|
||||||
|
@ -82,6 +87,7 @@ protected:
|
||||||
{
|
{
|
||||||
nsTArray<nsCString> tags;
|
nsTArray<nsCString> tags;
|
||||||
tags.AppendElement(NS_LITERAL_CSTRING("h264"));
|
tags.AppendElement(NS_LITERAL_CSTRING("h264"));
|
||||||
|
tags.AppendElement(NS_LITERAL_CSTRING("fake"));
|
||||||
|
|
||||||
nsRefPtr<GeckoMediaPluginService> service =
|
nsRefPtr<GeckoMediaPluginService> service =
|
||||||
GeckoMediaPluginService::GetGeckoMediaPluginService();
|
GeckoMediaPluginService::GetGeckoMediaPluginService();
|
||||||
|
@ -174,7 +180,7 @@ private:
|
||||||
EXPECT_TRUE(aGMP);
|
EXPECT_TRUE(aGMP);
|
||||||
if (aGMP) {
|
if (aGMP) {
|
||||||
EXPECT_TRUE(mGMP &&
|
EXPECT_TRUE(mGMP &&
|
||||||
(mGMP->ParentID() == aGMP->ParentID()) == mShouldBeEqual);
|
(mGMP->GetPluginId() == aGMP->GetPluginId()) == mShouldBeEqual);
|
||||||
}
|
}
|
||||||
if (mGMP) {
|
if (mGMP) {
|
||||||
mGMP->Close();
|
mGMP->Close();
|
||||||
|
@ -1344,7 +1350,12 @@ class GMPStorageTest : public GMPDecryptorProxyCallback
|
||||||
virtual void Decrypted(uint32_t aId,
|
virtual void Decrypted(uint32_t aId,
|
||||||
GMPErr aResult,
|
GMPErr aResult,
|
||||||
const nsTArray<uint8_t>& aDecryptedData) override { }
|
const nsTArray<uint8_t>& aDecryptedData) override { }
|
||||||
virtual void Terminated() override { }
|
virtual void Terminated() override {
|
||||||
|
if (mDecryptor) {
|
||||||
|
mDecryptor->Close();
|
||||||
|
mDecryptor = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~GMPStorageTest() { }
|
~GMPStorageTest() { }
|
||||||
|
@ -1475,3 +1486,65 @@ TEST(GeckoMediaPlugins, GMPStorageLongRecordNames) {
|
||||||
nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
|
nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
|
||||||
runner->DoTest(&GMPStorageTest::TestLongRecordNames);
|
runner->DoTest(&GMPStorageTest::TestLongRecordNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef XP_WIN
|
||||||
|
class GMPTrialCreateTest
|
||||||
|
{
|
||||||
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPStorageTest)
|
||||||
|
|
||||||
|
void DoTest() {
|
||||||
|
EnsureNSSInitializedChromeOrContent();
|
||||||
|
mCreator = new mozilla::dom::GMPVideoDecoderTrialCreator();
|
||||||
|
mCreator->MaybeAwaitTrialCreate(NS_LITERAL_STRING("broken"), nullptr, this, nullptr);
|
||||||
|
AwaitFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
GMPTrialCreateTest()
|
||||||
|
: mMonitor("GMPTrialCreateTest")
|
||||||
|
, mFinished(false)
|
||||||
|
, mPassed(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaybeResolve(mozilla::dom::MediaKeySystemAccess* aAccess) {
|
||||||
|
mPassed = false;
|
||||||
|
SetFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaybeReject(nsresult aResult, const nsACString& aUnusedMessage) {
|
||||||
|
mPassed = true;
|
||||||
|
SetFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
~GMPTrialCreateTest() { }
|
||||||
|
|
||||||
|
void Dummy() {
|
||||||
|
// Intentionally left blank.
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFinished() {
|
||||||
|
mFinished = true;
|
||||||
|
NS_DispatchToMainThread(NS_NewRunnableMethod(this, &GMPTrialCreateTest::Dummy));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AwaitFinished() {
|
||||||
|
while (!mFinished) {
|
||||||
|
NS_ProcessNextEvent(nullptr, true);
|
||||||
|
}
|
||||||
|
mFinished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsRefPtr<mozilla::dom::GMPVideoDecoderTrialCreator> mCreator;
|
||||||
|
|
||||||
|
Monitor mMonitor;
|
||||||
|
Atomic<bool> mFinished;
|
||||||
|
bool mPassed;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(GeckoMediaPlugins, GMPTrialCreateFail) {
|
||||||
|
nsRefPtr<GMPTrialCreateTest> runner = new GMPTrialCreateTest();
|
||||||
|
runner->DoTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // XP_WIN
|
|
@ -11,8 +11,9 @@
|
||||||
#include "mozilla/Services.h"
|
#include "mozilla/Services.h"
|
||||||
#include "nsDirectoryServiceDefs.h"
|
#include "nsDirectoryServiceDefs.h"
|
||||||
#include "nsIObserverService.h"
|
#include "nsIObserverService.h"
|
||||||
|
#include "GMPVideoDecoderProxy.h"
|
||||||
|
|
||||||
#define GMP_DIR_NAME NS_LITERAL_STRING("gmp-fake")
|
#define GMP_DIR_NAME NS_LITERAL_STRING("gmp-fakeopenh264")
|
||||||
#define GMP_OLD_VERSION NS_LITERAL_STRING("1.0")
|
#define GMP_OLD_VERSION NS_LITERAL_STRING("1.0")
|
||||||
#define GMP_NEW_VERSION NS_LITERAL_STRING("1.1")
|
#define GMP_NEW_VERSION NS_LITERAL_STRING("1.1")
|
||||||
|
|
||||||
|
@ -414,6 +415,10 @@ void
|
||||||
GMPRemoveTest::Terminated()
|
GMPRemoveTest::Terminated()
|
||||||
{
|
{
|
||||||
mIsTerminated = true;
|
mIsTerminated = true;
|
||||||
|
if (mDecoder) {
|
||||||
|
mDecoder->Close();
|
||||||
|
mDecoder = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -435,6 +440,17 @@ GMPRemoveTest::GeneratePlugin()
|
||||||
rv = origDir->Append(GMP_OLD_VERSION);
|
rv = origDir->Append(GMP_OLD_VERSION);
|
||||||
EXPECT_OK(rv);
|
EXPECT_OK(rv);
|
||||||
|
|
||||||
|
rv = gmpDir->Clone(getter_AddRefs(tmpDir));
|
||||||
|
EXPECT_OK(rv);
|
||||||
|
rv = tmpDir->Append(GMP_NEW_VERSION);
|
||||||
|
EXPECT_OK(rv);
|
||||||
|
bool exists = false;
|
||||||
|
rv = tmpDir->Exists(&exists);
|
||||||
|
EXPECT_OK(rv);
|
||||||
|
if (exists) {
|
||||||
|
rv = tmpDir->Remove(true);
|
||||||
|
EXPECT_OK(rv);
|
||||||
|
}
|
||||||
rv = origDir->CopyTo(gmpDir, GMP_NEW_VERSION);
|
rv = origDir->CopyTo(gmpDir, GMP_NEW_VERSION);
|
||||||
EXPECT_OK(rv);
|
EXPECT_OK(rv);
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,11 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "mp4_demuxer/mp4_demuxer.h"
|
#include "MP3Demuxer.h"
|
||||||
#include "mp4_demuxer/MP3TrackDemuxer.h"
|
|
||||||
#include "MP4Stream.h"
|
|
||||||
#include "mozilla/ArrayUtils.h"
|
#include "mozilla/ArrayUtils.h"
|
||||||
#include "MockMediaResource.h"
|
#include "MockMediaResource.h"
|
||||||
|
|
||||||
using namespace mp4_demuxer;
|
using namespace mp3;
|
||||||
|
|
||||||
struct MP3Resource {
|
struct MP3Resource {
|
||||||
const char* mFilePath;
|
const char* mFilePath;
|
||||||
|
@ -40,7 +38,7 @@ struct MP3Resource {
|
||||||
// The first n frame offsets.
|
// The first n frame offsets.
|
||||||
std::vector<int32_t> mSyncOffsets;
|
std::vector<int32_t> mSyncOffsets;
|
||||||
nsRefPtr<MockMediaResource> mResource;
|
nsRefPtr<MockMediaResource> mResource;
|
||||||
nsRefPtr<MP3Demuxer> mDemuxer;
|
nsRefPtr<MP3TrackDemuxer> mDemuxer;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MP3DemuxerTest : public ::testing::Test {
|
class MP3DemuxerTest : public ::testing::Test {
|
||||||
|
@ -98,7 +96,7 @@ protected:
|
||||||
|
|
||||||
for (auto& target: mTargets) {
|
for (auto& target: mTargets) {
|
||||||
target.mResource = new MockMediaResource(target.mFilePath),
|
target.mResource = new MockMediaResource(target.mFilePath),
|
||||||
target.mDemuxer = new MP3Demuxer(new MP4Stream(target.mResource));
|
target.mDemuxer = new MP3TrackDemuxer(target.mResource);
|
||||||
|
|
||||||
ASSERT_EQ(NS_OK, target.mResource->Open(nullptr));
|
ASSERT_EQ(NS_OK, target.mResource->Open(nullptr));
|
||||||
ASSERT_TRUE(target.mDemuxer->Init());
|
ASSERT_TRUE(target.mDemuxer->Init());
|
||||||
|
@ -208,7 +206,7 @@ TEST_F(MP3DemuxerTest, Duration) {
|
||||||
EXPECT_EQ(static_cast<int64_t>(target.mFileSize), target.mDemuxer->StreamLength());
|
EXPECT_EQ(static_cast<int64_t>(target.mFileSize), target.mDemuxer->StreamLength());
|
||||||
|
|
||||||
while (frameData) {
|
while (frameData) {
|
||||||
EXPECT_NEAR(target.mDuration, target.mDemuxer->Duration(),
|
EXPECT_NEAR(target.mDuration, target.mDemuxer->Duration().ToMicroseconds(),
|
||||||
target.mDurationError * target.mDuration);
|
target.mDurationError * target.mDuration);
|
||||||
|
|
||||||
frameData = target.mDemuxer->DemuxSample();
|
frameData = target.mDemuxer->DemuxSample();
|
||||||
|
|
|
@ -101,7 +101,7 @@ public:
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(DEBUG)
|
#if defined(DEBUG)
|
||||||
void Dump(const char* aPath);
|
void Dump(const char* aPath) override;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -137,6 +137,8 @@ EXPORTS += [
|
||||||
'MediaTimer.h',
|
'MediaTimer.h',
|
||||||
'MediaTrack.h',
|
'MediaTrack.h',
|
||||||
'MediaTrackList.h',
|
'MediaTrackList.h',
|
||||||
|
'MP3Decoder.h',
|
||||||
|
'MP3Demuxer.h',
|
||||||
'MP3FrameParser.h',
|
'MP3FrameParser.h',
|
||||||
'nsIDocumentActivity.h',
|
'nsIDocumentActivity.h',
|
||||||
'RtspMediaResource.h',
|
'RtspMediaResource.h',
|
||||||
|
@ -234,6 +236,8 @@ UNIFIED_SOURCES += [
|
||||||
'MediaTimer.cpp',
|
'MediaTimer.cpp',
|
||||||
'MediaTrack.cpp',
|
'MediaTrack.cpp',
|
||||||
'MediaTrackList.cpp',
|
'MediaTrackList.cpp',
|
||||||
|
'MP3Decoder.cpp',
|
||||||
|
'MP3Demuxer.cpp',
|
||||||
'MP3FrameParser.cpp',
|
'MP3FrameParser.cpp',
|
||||||
'RTCIdentityProviderRegistrar.cpp',
|
'RTCIdentityProviderRegistrar.cpp',
|
||||||
'RtspMediaResource.cpp',
|
'RtspMediaResource.cpp',
|
||||||
|
|
|
@ -458,6 +458,8 @@ MediaCodecReader::DecodeAudioDataTask()
|
||||||
}
|
}
|
||||||
} else if (AudioQueue().AtEndOfStream()) {
|
} else if (AudioQueue().AtEndOfStream()) {
|
||||||
mAudioTrack.mAudioPromise.Reject(END_OF_STREAM, __func__);
|
mAudioTrack.mAudioPromise.Reject(END_OF_STREAM, __func__);
|
||||||
|
} else if (AudioQueue().GetSize() == 0) {
|
||||||
|
DispatchAudioTask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,6 +468,9 @@ MediaCodecReader::DecodeVideoFrameTask(int64_t aTimeThreshold)
|
||||||
{
|
{
|
||||||
DecodeVideoFrameSync(aTimeThreshold);
|
DecodeVideoFrameSync(aTimeThreshold);
|
||||||
MonitorAutoLock al(mVideoTrack.mTrackMonitor);
|
MonitorAutoLock al(mVideoTrack.mTrackMonitor);
|
||||||
|
if (mVideoTrack.mVideoPromise.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (VideoQueue().GetSize() > 0) {
|
if (VideoQueue().GetSize() > 0) {
|
||||||
nsRefPtr<VideoData> v = VideoQueue().PopFront();
|
nsRefPtr<VideoData> v = VideoQueue().PopFront();
|
||||||
if (v) {
|
if (v) {
|
||||||
|
@ -477,6 +482,8 @@ MediaCodecReader::DecodeVideoFrameTask(int64_t aTimeThreshold)
|
||||||
}
|
}
|
||||||
} else if (VideoQueue().AtEndOfStream()) {
|
} else if (VideoQueue().AtEndOfStream()) {
|
||||||
mVideoTrack.mVideoPromise.Reject(END_OF_STREAM, __func__);
|
mVideoTrack.mVideoPromise.Reject(END_OF_STREAM, __func__);
|
||||||
|
} else if (VideoQueue().GetSize() == 0) {
|
||||||
|
DispatchVideoTask(aTimeThreshold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1830,7 +1837,7 @@ MediaCodecReader::GetCodecOutputData(Track& aTrack,
|
||||||
|
|
||||||
if (status == OK) {
|
if (status == OK) {
|
||||||
// Notify mDecoder that we have parsed a video frame.
|
// Notify mDecoder that we have parsed a video frame.
|
||||||
if (&aTrack == &mVideoTrack) {
|
if (aTrack.mType == Track::kVideo) {
|
||||||
mDecoder->NotifyDecodedFrames(1, 0, 0);
|
mDecoder->NotifyDecodedFrames(1, 0, 0);
|
||||||
}
|
}
|
||||||
if (!IsValidTimestampUs(aThreshold) || info.mTimeUs >= aThreshold) {
|
if (!IsValidTimestampUs(aThreshold) || info.mTimeUs >= aThreshold) {
|
||||||
|
@ -1905,6 +1912,7 @@ MediaCodecReader::EnsureCodecFormatParsed(Track& aTrack)
|
||||||
}
|
}
|
||||||
FillCodecInputData(aTrack);
|
FillCodecInputData(aTrack);
|
||||||
}
|
}
|
||||||
|
aTrack.mCodec->releaseOutputBuffer(index);
|
||||||
return aTrack.mCodec->getOutputFormat(&format) == OK;
|
return aTrack.mCodec->getOutputFormat(&format) == OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,79 +28,6 @@ const signalingStateTransitions = {
|
||||||
"closed": []
|
"closed": []
|
||||||
}
|
}
|
||||||
|
|
||||||
var wait = (time) => new Promise(r => setTimeout(r, time));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class provides a state checker for media elements which store
|
|
||||||
* a media stream to check for media attribute state and events fired.
|
|
||||||
* When constructed by a caller, an object instance is created with
|
|
||||||
* a media element, event state checkers for canplaythrough, timeupdate, and
|
|
||||||
* time changing on the media element and stream.
|
|
||||||
*
|
|
||||||
* @param {HTMLMediaElement} element the media element being analyzed
|
|
||||||
*/
|
|
||||||
function MediaElementChecker(element) {
|
|
||||||
this.element = element;
|
|
||||||
this.canPlayThroughFired = false;
|
|
||||||
this.timeUpdateFired = false;
|
|
||||||
this.timePassed = false;
|
|
||||||
|
|
||||||
var elementId = this.element.getAttribute('id');
|
|
||||||
|
|
||||||
// When canplaythrough fires, we track that it's fired and remove the
|
|
||||||
// event listener.
|
|
||||||
var canPlayThroughCallback = () => {
|
|
||||||
info('canplaythrough fired for media element ' + elementId);
|
|
||||||
this.canPlayThroughFired = true;
|
|
||||||
this.element.removeEventListener('canplaythrough', canPlayThroughCallback,
|
|
||||||
false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// When timeupdate fires, we track that it's fired and check if time
|
|
||||||
// has passed on the media stream and media element.
|
|
||||||
var timeUpdateCallback = () => {
|
|
||||||
this.timeUpdateFired = true;
|
|
||||||
info('timeupdate fired for media element ' + elementId);
|
|
||||||
|
|
||||||
// If time has passed, then track that and remove the timeupdate event
|
|
||||||
// listener.
|
|
||||||
if (element.mozSrcObject && element.mozSrcObject.currentTime > 0 &&
|
|
||||||
element.currentTime > 0) {
|
|
||||||
info('time passed for media element ' + elementId);
|
|
||||||
this.timePassed = true;
|
|
||||||
this.element.removeEventListener('timeupdate', timeUpdateCallback,
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
element.addEventListener('canplaythrough', canPlayThroughCallback, false);
|
|
||||||
element.addEventListener('timeupdate', timeUpdateCallback, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaElementChecker.prototype = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits until the canplaythrough & timeupdate events to fire along with
|
|
||||||
* ensuring time has passed on the stream and media element.
|
|
||||||
*/
|
|
||||||
waitForMediaFlow: function() {
|
|
||||||
var elementId = this.element.getAttribute('id');
|
|
||||||
info('Analyzing element: ' + elementId);
|
|
||||||
|
|
||||||
return waitUntil(() => this.canPlayThroughFired && this.timeUpdateFired && this.timePassed)
|
|
||||||
.then(() => ok(true, 'Media flowing for ' + elementId));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if there is no media flow present by checking that the ready
|
|
||||||
* state of the media element is HAVE_METADATA.
|
|
||||||
*/
|
|
||||||
checkForNoMediaFlow: function() {
|
|
||||||
ok(this.element.readyState === HTMLMediaElement.HAVE_METADATA,
|
|
||||||
'Media element has a ready state of HAVE_METADATA');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Also remove mode 0 if it's offered
|
// Also remove mode 0 if it's offered
|
||||||
// Note, we don't bother removing the fmtp lines, which makes a good test
|
// Note, we don't bother removing the fmtp lines, which makes a good test
|
||||||
// for some SDP parsing issues.
|
// for some SDP parsing issues.
|
||||||
|
@ -768,7 +695,7 @@ function PeerConnectionWrapper(label, configuration, h264) {
|
||||||
this.constraints = [ ];
|
this.constraints = [ ];
|
||||||
this.offerOptions = {};
|
this.offerOptions = {};
|
||||||
this.streams = [ ];
|
this.streams = [ ];
|
||||||
this.mediaCheckers = [ ];
|
this.mediaElements = [ ];
|
||||||
|
|
||||||
this.dataChannels = [ ];
|
this.dataChannels = [ ];
|
||||||
|
|
||||||
|
@ -918,7 +845,7 @@ PeerConnectionWrapper.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
var element = createMediaElement(type, this.label + '_' + side + this.streams.length);
|
var element = createMediaElement(type, this.label + '_' + side + this.streams.length);
|
||||||
this.mediaCheckers.push(new MediaElementChecker(element));
|
this.mediaElements.push(element);
|
||||||
element.mozSrcObject = stream;
|
element.mozSrcObject = stream;
|
||||||
element.play();
|
element.play();
|
||||||
|
|
||||||
|
@ -1528,11 +1455,93 @@ PeerConnectionWrapper.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that media flow is present on all media elements involved in this
|
* Check that media flow is present on the given media element by waiting for
|
||||||
* test by waiting for confirmation that media flow is present.
|
* it to reach ready state HAVE_ENOUGH_DATA and progress time further than
|
||||||
|
* the start of the check.
|
||||||
|
*
|
||||||
|
* This ensures, that the stream being played is producing
|
||||||
|
* data and that at least one video frame has been displayed.
|
||||||
|
*
|
||||||
|
* @param {object} element
|
||||||
|
* A media element to wait for data flow on.
|
||||||
|
* @returns {Promise}
|
||||||
|
* A promise that resolves when media is flowing.
|
||||||
*/
|
*/
|
||||||
checkMediaFlowPresent : function() {
|
waitForMediaElementFlow : function(element) {
|
||||||
return Promise.all(this.mediaCheckers.map(checker => checker.waitForMediaFlow()));
|
return new Promise(resolve => {
|
||||||
|
info("Checking data flow to element: " + element.id);
|
||||||
|
var haveEnoughData = false;
|
||||||
|
var oncanplay = () => {
|
||||||
|
info("Element " + element.id + " saw 'canplay', " +
|
||||||
|
"meaning HAVE_ENOUGH_DATA was just reached.");
|
||||||
|
haveEnoughData = true;
|
||||||
|
element.removeEventListener("canplay", oncanplay);
|
||||||
|
};
|
||||||
|
var ontimeupdate = () => {
|
||||||
|
info("Element " + element.id + " saw 'timeupdate'" +
|
||||||
|
", currentTime=" + element.currentTime +
|
||||||
|
"s, readyState=" + element.readyState);
|
||||||
|
if (haveEnoughData || element.readyState == element.HAVE_ENOUGH_DATA) {
|
||||||
|
element.removeEventListener("timeupdate", ontimeupdate);
|
||||||
|
ok(true, "Media flowing for element: " + element.id);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
element.addEventListener("canplay", oncanplay);
|
||||||
|
element.addEventListener("timeupdate", ontimeupdate);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for RTP packet flow for the given MediaStreamTrack.
|
||||||
|
*
|
||||||
|
* @param {object} track
|
||||||
|
* A MediaStreamTrack to wait for data flow on.
|
||||||
|
* @returns {Promise}
|
||||||
|
* A promise that resolves when media is flowing.
|
||||||
|
*/
|
||||||
|
waitForRtpFlow(track) {
|
||||||
|
var hasFlow = stats => {
|
||||||
|
var rtpStatsKey = Object.keys(stats)
|
||||||
|
.find(key => !stats[key].isRemote && stats[key].type.endsWith("boundrtp"));
|
||||||
|
ok(rtpStatsKey, "Should have RTP stats for track " + track.id);
|
||||||
|
var rtp = stats[rtpStatsKey];
|
||||||
|
var nrPackets = rtp[rtp.type == "outboundrtp" ? "packetsSent"
|
||||||
|
: "packetsReceived"];
|
||||||
|
info("Track " + track.id + " has " + nrPackets + " " +
|
||||||
|
rtp.type + " RTP packets.");
|
||||||
|
return nrPackets > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
info("Checking RTP packet flow for track " + track.id);
|
||||||
|
|
||||||
|
var waitForFlow = () => {
|
||||||
|
this._pc.getStats(track).then(stats => {
|
||||||
|
if (hasFlow(stats)) {
|
||||||
|
ok(true, "RTP flowing for track " + track.id);
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
wait(200).then(waitForFlow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
waitForFlow();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for presence of video flow on all media elements and rtp flow on
|
||||||
|
* all sending and receiving track involved in this test.
|
||||||
|
*
|
||||||
|
* @returns {Promise}
|
||||||
|
* A promise that resolves when media flows for all elements and tracks
|
||||||
|
*/
|
||||||
|
waitForMediaFlow : function() {
|
||||||
|
return Promise.all([].concat(
|
||||||
|
this.mediaElements.map(element => this.waitForMediaElementFlow(element)),
|
||||||
|
this._pc.getSenders().map(sender => this.waitForRtpFlow(sender.track)),
|
||||||
|
this._pc.getReceivers().map(receiver => this.waitForRtpFlow(receiver.track))));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -446,13 +446,14 @@ var commandsPeerConnectionOfferAnswer = [
|
||||||
return test.pcRemote.checkMediaTracks();
|
return test.pcRemote.checkMediaTracks();
|
||||||
},
|
},
|
||||||
|
|
||||||
function PC_LOCAL_CHECK_MEDIA_FLOW_PRESENT(test) {
|
function PC_LOCAL_WAIT_FOR_MEDIA_FLOW(test) {
|
||||||
return test.pcLocal.checkMediaFlowPresent();
|
return test.pcLocal.waitForMediaFlow();
|
||||||
},
|
},
|
||||||
|
|
||||||
function PC_REMOTE_CHECK_MEDIA_FLOW_PRESENT(test) {
|
function PC_REMOTE_WAIT_FOR_MEDIA_FLOW(test) {
|
||||||
return test.pcRemote.checkMediaFlowPresent();
|
return test.pcRemote.waitForMediaFlow();
|
||||||
},
|
},
|
||||||
|
|
||||||
function PC_LOCAL_CHECK_STATS(test) {
|
function PC_LOCAL_CHECK_STATS(test) {
|
||||||
return test.pcLocal.getStats(null).then(stats => {
|
return test.pcLocal.getStats(null).then(stats => {
|
||||||
test.pcLocal.checkStats(stats, test.steeplechase);
|
test.pcLocal.checkStats(stats, test.steeplechase);
|
||||||
|
|
|
@ -25,22 +25,14 @@ runNetworkTest(function() {
|
||||||
var test = new PeerConnectionTest();
|
var test = new PeerConnectionTest();
|
||||||
test.setOfferOptions({ offerToReceiveVideo: false,
|
test.setOfferOptions({ offerToReceiveVideo: false,
|
||||||
offerToReceiveAudio: false });
|
offerToReceiveAudio: false });
|
||||||
test.chain.insertAfter("PC_LOCAL_GUM", [
|
test.setMediaConstraints([{video: true, audio: true}], []);
|
||||||
|
test.chain.replace("PC_LOCAL_GUM", [
|
||||||
function PC_LOCAL_CAPTUREVIDEO(test) {
|
function PC_LOCAL_CAPTUREVIDEO(test) {
|
||||||
return metadataLoaded
|
return metadataLoaded
|
||||||
.then(() => {
|
.then(() => {
|
||||||
var stream = v1.mozCaptureStreamUntilEnded();
|
var stream = v1.mozCaptureStreamUntilEnded();
|
||||||
is(stream.getTracks().length, 2, "Captured stream has 2 tracks");
|
is(stream.getTracks().length, 2, "Captured stream has 2 tracks");
|
||||||
stream.getTracks().forEach(track => {
|
test.pcLocal.attachMedia(stream, "audiovideo", "local");
|
||||||
test.pcLocal.expectNegotiationNeeded();
|
|
||||||
test.pcLocal._pc.addTrack(track, stream);
|
|
||||||
test.pcLocal.expectedLocalTrackInfoById[track.id] = {
|
|
||||||
type: track.kind,
|
|
||||||
streamId: stream.id
|
|
||||||
};
|
|
||||||
});
|
|
||||||
test.pcLocal.constraints = [{ video: true, audio:true }]; // fool tests
|
|
||||||
return test.pcLocal.observedNegotiationNeeded;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -66,8 +66,8 @@
|
||||||
function PC_REMOTE_VIDEOONLY_REPLACE_VIDEOTRACK(test) {
|
function PC_REMOTE_VIDEOONLY_REPLACE_VIDEOTRACK(test) {
|
||||||
return replacetest(test.pcRemote);
|
return replacetest(test.pcRemote);
|
||||||
},
|
},
|
||||||
function PC_LOCAL_NEW_VIDEOTRACK_CHECK_MEDIA_FLOW_PRESENT(test) {
|
function PC_LOCAL_NEW_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW(test) {
|
||||||
return test.pcLocal.checkMediaFlowPresent();
|
return test.pcLocal.waitForMediaFlow();
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -77,14 +77,14 @@
|
||||||
function PC_LOCAL_AUDIOVIDEO_REPLACE_VIDEOTRACK_1(test) {
|
function PC_LOCAL_AUDIOVIDEO_REPLACE_VIDEOTRACK_1(test) {
|
||||||
return replacetest(test.pcLocal);
|
return replacetest(test.pcLocal);
|
||||||
},
|
},
|
||||||
function PC_REMOTE_NEW_VIDEOTRACK_CHECK_MEDIA_FLOW_PRESENT_1(test) {
|
function PC_REMOTE_NEW_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW_1(test) {
|
||||||
return test.pcRemote.checkMediaFlowPresent();
|
return test.pcRemote.waitForMediaFlow();
|
||||||
},
|
},
|
||||||
function PC_LOCAL_AUDIOVIDEO_REPLACE_VIDEOTRACK_2(test) {
|
function PC_LOCAL_AUDIOVIDEO_REPLACE_VIDEOTRACK_2(test) {
|
||||||
return replacetest(test.pcLocal);
|
return replacetest(test.pcLocal);
|
||||||
},
|
},
|
||||||
function PC_REMOTE_NEW_VIDEOTRACK_CHECK_MEDIA_FLOW_PRESENT_2(test) {
|
function PC_REMOTE_NEW_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW_2(test) {
|
||||||
return test.pcRemote.checkMediaFlowPresent();
|
return test.pcRemote.waitForMediaFlow();
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -96,8 +96,8 @@
|
||||||
return sender.replaceTrack(sender.track)
|
return sender.replaceTrack(sender.track)
|
||||||
.then(() => ok(true, "replacing with itself should succeed"));
|
.then(() => ok(true, "replacing with itself should succeed"));
|
||||||
},
|
},
|
||||||
function PC_REMOTE_NEW_SAME_VIDEOTRACK_CHECK_MEDIA_FLOW_PRESENT(test) {
|
function PC_REMOTE_NEW_SAME_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW(test) {
|
||||||
return test.pcRemote.checkMediaFlowPresent();
|
return test.pcRemote.waitForMediaFlow();
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
SimpleTest.waitForExplicitFinish();
|
||||||
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
|
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
|
||||||
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
|
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_DISABLED, "Second Test Plug-in");
|
||||||
|
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Java Test Plug-in");
|
||||||
|
|
||||||
function findPlugin(pluginName) {
|
function findPlugin(pluginName) {
|
||||||
for (var i = 0; i < navigator.plugins.length; i++) {
|
for (var i = 0; i < navigator.plugins.length; i++) {
|
||||||
|
@ -36,38 +37,37 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function run() {
|
function run() {
|
||||||
// Add "Test Plug-in" (but not "Second Test Plug-in") to the list of
|
|
||||||
// unhidden plugins. This test must modify the "plugins.enumerable_names"
|
|
||||||
// pref BEFORE accessing the navigator.plugins or navigator.mimeTypes
|
|
||||||
// arrays because they only read the pref when they first initialize
|
|
||||||
// their internal arrays!
|
|
||||||
var prefs = SpecialPowers.Cc["@mozilla.org/preferences-service;1"].getService(SpecialPowers.Ci.nsIPrefBranch);
|
|
||||||
var defaultEnumerableNamesPref = prefs.getCharPref("plugins.enumerable_names");
|
|
||||||
SpecialPowers.pushPrefEnv(
|
|
||||||
{'set': [["plugins.enumerable_names", defaultEnumerableNamesPref + ",Test Plug-in"]]},
|
|
||||||
finishRun
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function finishRun() {
|
|
||||||
var pluginElement = document.getElementById("plugin");
|
var pluginElement = document.getElementById("plugin");
|
||||||
is(pluginElement.identifierToStringTest("foo"), "foo", "Should be able to call a function provided by the plugin");
|
is(pluginElement.identifierToStringTest("foo"), "foo", "Should be able to call a function provided by the plugin");
|
||||||
|
|
||||||
ok(navigator.plugins["Test Plug-in"], "Should have queried a non-hidden plugin named 'Test Plug-in'");
|
pluginElement = document.getElementById("disabledPlugin");
|
||||||
ok(navigator.plugins["Second Test Plug-in"], "Should have queried a hidden plugin named 'Test Plug-in'");
|
is(typeof pluginElement.identifierToStringTest, "undefined", "Should NOT be able to call a function on a disabled plugin");
|
||||||
|
|
||||||
ok(findPlugin("Test Plug-in"), "Should have found a non-hidden plugin named 'Test Plug-in'");
|
pluginElement = document.getElementById("clickToPlayPlugin");
|
||||||
ok(!findPlugin("Second Test Plug-in"), "Should NOT found a hidden plugin named 'Test Plug-in'");
|
is(typeof pluginElement.identifierToStringTest, "undefined", "Should NOT be able to call a function on a click-to-play plugin");
|
||||||
|
|
||||||
ok(navigator.mimeTypes["application/x-test"], "Should have queried a non-hidden MIME type named 'application/x-test'");
|
ok(navigator.plugins["Test Plug-in"], "Should have queried a plugin named 'Test Plug-in'");
|
||||||
ok(navigator.mimeTypes["application/x-second-test"], "Should have queried a MIME type named 'application/x-second-test'");
|
ok(!navigator.plugins["Second Test Plug-in"], "Should NOT have queried a disabled plugin named 'Second Test Plug-in'");
|
||||||
|
ok(navigator.plugins["Java Test Plug-in"], "Should have queried a click-to-play plugin named 'Java Test Plug-in'");
|
||||||
|
|
||||||
ok(findMimeType("application/x-test"), "Should have found a non-hidden MIME type named 'application/x-test'");
|
ok(findPlugin("Test Plug-in"), "Should have found a plugin named 'Test Plug-in'");
|
||||||
ok(!findMimeType("application/x-second-test"), "Should NOT have found a MIME type named 'application/x-second-test'");
|
ok(!findPlugin("Second Test Plug-in"), "Should NOT found a disabled plugin named 'Second Test Plug-in'");
|
||||||
|
ok(findPlugin("Java Test Plug-in"), "Should have found a click-to-play plugin named 'Java Test Plug-in'");
|
||||||
|
|
||||||
|
ok(navigator.mimeTypes["application/x-test"], "Should have queried a MIME type named 'application/x-test'");
|
||||||
|
ok(!navigator.mimeTypes["application/x-second-test"], "Should NOT have queried a disabled type named 'application/x-second-test'");
|
||||||
|
ok(navigator.mimeTypes["application/x-java-test"], "Should have queried a click-to-play MIME type named 'application/x-java-test'");
|
||||||
|
|
||||||
|
ok(findMimeType("application/x-test"), "Should have found a MIME type named 'application/x-test'");
|
||||||
|
ok(!findMimeType("application/x-second-test"), "Should NOT have found a disabled MIME type named 'application/x-second-test'");
|
||||||
|
ok(findMimeType("application/x-java-test"), "Should have found a click-to-play MIME type named 'application/x-java-test'");
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<object id="plugin" type="application/x-second-test" width=200 height=200></object>
|
<object id="plugin" type="application/x-test" width=200 height=200></object>
|
||||||
|
<object id="disabledPlugin" type="application/x-second-test" width=200 height=200></object>
|
||||||
|
<object id="clickToPlayPlugin" type="application/x-java-test" width=200 height=200></object>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -27,11 +27,11 @@ skip-if = buildapp == 'b2g' # Bug 1137683
|
||||||
skip-if = buildapp == 'b2g' # Bug 1137683
|
skip-if = buildapp == 'b2g' # Bug 1137683
|
||||||
[test_fetch_basic_http.html]
|
[test_fetch_basic_http.html]
|
||||||
[test_fetch_basic_http_sw_reroute.html]
|
[test_fetch_basic_http_sw_reroute.html]
|
||||||
skip-if = true # Bug 1122161, need proper support for redirects
|
skip-if = true # Bug 1170937, need fully support for redirects
|
||||||
#skip-if = buildapp == 'b2g' # Bug 1137683
|
#skip-if = buildapp == 'b2g' # Bug 1137683
|
||||||
[test_fetch_cors.html]
|
[test_fetch_cors.html]
|
||||||
[test_fetch_cors_sw_reroute.html]
|
[test_fetch_cors_sw_reroute.html]
|
||||||
skip-if = true # Bug 1122161, need proper support for redirects
|
skip-if = true # Bug 1170937, need fully support for redirects
|
||||||
#skip-if = buildapp == 'b2g' # Bug 1137683
|
#skip-if = buildapp == 'b2g' # Bug 1137683
|
||||||
[test_formdataparsing.html]
|
[test_formdataparsing.html]
|
||||||
[test_formdataparsing_sw_reroute.html]
|
[test_formdataparsing_sw_reroute.html]
|
||||||
|
|
|
@ -1667,6 +1667,12 @@ RuntimeService::UnregisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (aWorkerPrivate->IsServiceWorker()) {
|
||||||
|
AssertIsOnMainThread();
|
||||||
|
Telemetry::AccumulateTimeDelta(Telemetry::SERVICE_WORKER_LIFE_TIME,
|
||||||
|
aWorkerPrivate->CreationTimeStamp());
|
||||||
|
}
|
||||||
|
|
||||||
if (aWorkerPrivate->IsSharedWorker()) {
|
if (aWorkerPrivate->IsSharedWorker()) {
|
||||||
AssertIsOnMainThread();
|
AssertIsOnMainThread();
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "mozilla/ClearOnShutdown.h"
|
#include "mozilla/ClearOnShutdown.h"
|
||||||
#include "mozilla/ErrorNames.h"
|
#include "mozilla/ErrorNames.h"
|
||||||
#include "mozilla/LoadContext.h"
|
#include "mozilla/LoadContext.h"
|
||||||
|
#include "mozilla/Telemetry.h"
|
||||||
#include "mozilla/dom/BindingUtils.h"
|
#include "mozilla/dom/BindingUtils.h"
|
||||||
#include "mozilla/dom/ContentParent.h"
|
#include "mozilla/dom/ContentParent.h"
|
||||||
#include "mozilla/dom/DOMError.h"
|
#include "mozilla/dom/DOMError.h"
|
||||||
|
@ -814,6 +815,9 @@ public:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AssertIsOnMainThread();
|
||||||
|
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_UPDATED, 1);
|
||||||
|
|
||||||
nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
||||||
|
|
||||||
nsCOMPtr<nsIURI> scriptURI;
|
nsCOMPtr<nsIURI> scriptURI;
|
||||||
|
@ -1322,6 +1326,9 @@ ServiceWorkerManager::Register(nsIDOMWindow* aWindow,
|
||||||
new ServiceWorkerRegisterJob(queue, cleanedScope, spec, cb, documentPrincipal);
|
new ServiceWorkerRegisterJob(queue, cleanedScope, spec, cb, documentPrincipal);
|
||||||
queue->Append(job);
|
queue->Append(job);
|
||||||
|
|
||||||
|
AssertIsOnMainThread();
|
||||||
|
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_REGISTRATIONS, 1);
|
||||||
|
|
||||||
promise.forget(aPromise);
|
promise.forget(aPromise);
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
@ -2295,12 +2302,13 @@ ServiceWorkerManager::GetInstance()
|
||||||
// this can resurrect the ServiceWorkerManager pretty late during shutdown.
|
// this can resurrect the ServiceWorkerManager pretty late during shutdown.
|
||||||
static bool firstTime = true;
|
static bool firstTime = true;
|
||||||
if (firstTime) {
|
if (firstTime) {
|
||||||
|
firstTime = false;
|
||||||
|
|
||||||
AssertIsOnMainThread();
|
AssertIsOnMainThread();
|
||||||
|
|
||||||
gInstance = new ServiceWorkerManager();
|
gInstance = new ServiceWorkerManager();
|
||||||
gInstance->Init();
|
gInstance->Init();
|
||||||
ClearOnShutdown(&gInstance);
|
ClearOnShutdown(&gInstance);
|
||||||
firstTime = false;
|
|
||||||
}
|
}
|
||||||
nsRefPtr<ServiceWorkerManager> copy = gInstance.get();
|
nsRefPtr<ServiceWorkerManager> copy = gInstance.get();
|
||||||
return copy.forget();
|
return copy.forget();
|
||||||
|
@ -2789,6 +2797,7 @@ ServiceWorkerManager::StartControllingADocument(ServiceWorkerRegistrationInfo* a
|
||||||
|
|
||||||
aRegistration->StartControllingADocument();
|
aRegistration->StartControllingADocument();
|
||||||
mControlledDocuments.Put(aDoc, aRegistration);
|
mControlledDocuments.Put(aDoc, aRegistration);
|
||||||
|
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
Двоичные данные
dom/workers/test/serviceworkers/app-protocol/application.zip
Двоичные данные
dom/workers/test/serviceworkers/app-protocol/application.zip
Двоичный файл не отображается.
|
@ -9,7 +9,14 @@
|
||||||
function runTests() {
|
function runTests() {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(navigator.serviceWorker.ready)
|
.then(navigator.serviceWorker.ready)
|
||||||
.then(() => { return testFetchAppResource('swresponse'); })
|
.then(() => {
|
||||||
|
return testFetchAppResource('foo.txt',
|
||||||
|
'swresponse', 'text/plain');
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return testFetchAppResource('test_custom_content_type',
|
||||||
|
'customContentType', 'text/html');
|
||||||
|
})
|
||||||
.then(done);
|
.then(done);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -19,7 +19,8 @@ function registerServiceWorker() {
|
||||||
|
|
||||||
function runTests() {
|
function runTests() {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => { return testFetchAppResource('networkresponse'); })
|
.then(() => { return testFetchAppResource('foo.txt',
|
||||||
|
'networkresponse'); })
|
||||||
.then(registerServiceWorker)
|
.then(registerServiceWorker)
|
||||||
.then(ready);
|
.then(ready);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,16 @@ self.addEventListener('fetch', (event) => {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.request.url.indexOf('test_doc_load_interception.js') >=0 ) {
|
if (event.request.url.indexOf('test_doc_load_interception.js') >= 0 ) {
|
||||||
var scriptContent = 'alert("OK: Script modified by service worker")';
|
var scriptContent = 'alert("OK: Script modified by service worker")';
|
||||||
event.respondWith(new Response(scriptContent, {
|
event.respondWith(new Response(scriptContent, {
|
||||||
headers: {'Content-Type': 'application/javascript'}
|
headers: {'Content-Type': 'application/javascript'}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.request.url.indexOf('test_custom_content_type') >= 0) {
|
||||||
|
event.respondWith(new Response('customContentType', {
|
||||||
|
headers: {'Content-Type': 'text/html'}
|
||||||
|
}));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,16 +14,26 @@ function done() {
|
||||||
alert('DONE');
|
alert('DONE');
|
||||||
}
|
}
|
||||||
|
|
||||||
function testFetchAppResource(aExpectedResponse) {
|
function testFetchAppResource(aUrl,
|
||||||
return fetch('foo.txt').then(res => {
|
aExpectedResponse,
|
||||||
|
aExpectedContentType) {
|
||||||
|
return fetch(aUrl).then(res => {
|
||||||
ok(true, 'fetch should resolve');
|
ok(true, 'fetch should resolve');
|
||||||
if (res.type == 'error') {
|
if (res.type == 'error') {
|
||||||
ok(false, 'fetch failed');
|
ok(false, 'fetch failed');
|
||||||
}
|
}
|
||||||
ok(res.status == 200, 'status should be 200');
|
ok(res.status == 200, 'status should be 200');
|
||||||
ok(res.statusText == 'OK', 'statusText should be OK');
|
ok(res.statusText == 'OK', 'statusText should be OK');
|
||||||
|
if (aExpectedContentType) {
|
||||||
|
var headers = res.headers.getAll('Content-Type');
|
||||||
|
ok(headers.length, "Headers length");
|
||||||
|
var contentType = res.headers.get('Content-Type');
|
||||||
|
ok(contentType == aExpectedContentType, ('content type ' +
|
||||||
|
contentType + ' should match with ' + aExpectedContentType));
|
||||||
|
}
|
||||||
return res.text().then(body => {
|
return res.text().then(body => {
|
||||||
ok(body == aExpectedResponse, 'body should match');
|
ok(body == aExpectedResponse, 'body ' + body +
|
||||||
|
' should match with ' + aExpectedResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,6 +140,25 @@ fetchXHR('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?st
|
||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Test that when the page fetches a url the controlling SW forces a redirect to
|
||||||
|
// another location. This other location fetch should also be intercepted by
|
||||||
|
// the SW.
|
||||||
|
fetchXHR('something.txt', function(xhr) {
|
||||||
|
my_ok(xhr.status == 200, "load should be successful");
|
||||||
|
my_ok(xhr.responseText == "something else response body", "load should have something else");
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test fetch will internally get it's SkipServiceWorker flag set. The request is
|
||||||
|
// made from the SW through fetch(). fetch() fetches a server-side JavaScript
|
||||||
|
// file that force a redirect. The redirect location fetch does not go through
|
||||||
|
// the SW.
|
||||||
|
fetchXHR('redirect_serviceworker.sjs', function(xhr) {
|
||||||
|
my_ok(xhr.status == 200, "load should be successful");
|
||||||
|
my_ok(xhr.responseText == "// empty worker, always succeed!\n", "load should have redirection content");
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
|
||||||
expectAsyncResult();
|
expectAsyncResult();
|
||||||
fetch('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*')
|
fetch('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*')
|
||||||
.then(function(res) {
|
.then(function(res) {
|
||||||
|
|
|
@ -190,4 +190,20 @@ onfetch = function(ev) {
|
||||||
return new Response(body + body);
|
return new Response(body + body);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
else if (ev.request.url.includes('something.txt')) {
|
||||||
|
ev.respondWith(Response.redirect('fetch/somethingelse.txt'));
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.includes('somethingelse.txt')) {
|
||||||
|
ev.respondWith(new Response('something else response body', {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.includes('redirect_serviceworker.sjs')) {
|
||||||
|
// The redirect_serviceworker.sjs server-side JavaScript file redirects to
|
||||||
|
// 'http://mochi.test:8888/tests/dom/workers/test/serviceworkers/worker.js'
|
||||||
|
// The redirected fetch should not go through the SW since the original
|
||||||
|
// fetch was initiated from a SW.
|
||||||
|
ev.respondWith(fetch('redirect_serviceworker.sjs'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
// This test is full of dummy debug messages. This is because I need to follow
|
// This test is full of dummy debug messages. This is because I need to follow
|
||||||
// an hard-to-reproduce timeout failure.
|
// an hard-to-reproduce timeout failure.
|
||||||
|
|
||||||
info("test started");
|
|
||||||
SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]] }, function() {
|
SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]] }, function() {
|
||||||
info("test started");
|
info("test started");
|
||||||
var sw = new SharedWorker('bug1132395_sharedWorker.js');
|
var sw = new SharedWorker('bug1132395_sharedWorker.js');
|
||||||
|
@ -26,6 +25,11 @@ SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]] }
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sw.onerror = function(event) {
|
||||||
|
ok(false, "Failed to create a ServiceWorker");
|
||||||
|
SimpleTest.finish();
|
||||||
|
}
|
||||||
|
|
||||||
info("sw.postmessage called");
|
info("sw.postmessage called");
|
||||||
sw.port.postMessage('go');
|
sw.port.postMessage('go');
|
||||||
});
|
});
|
||||||
|
|
|
@ -260,11 +260,18 @@ SetCairoStrokeOptions(cairo_t* aCtx, const StrokeOptions& aStrokeOptions)
|
||||||
if (aStrokeOptions.mDashPattern) {
|
if (aStrokeOptions.mDashPattern) {
|
||||||
// Convert array of floats to array of doubles
|
// Convert array of floats to array of doubles
|
||||||
std::vector<double> dashes(aStrokeOptions.mDashLength);
|
std::vector<double> dashes(aStrokeOptions.mDashLength);
|
||||||
|
bool nonZero = false;
|
||||||
for (size_t i = 0; i < aStrokeOptions.mDashLength; ++i) {
|
for (size_t i = 0; i < aStrokeOptions.mDashLength; ++i) {
|
||||||
|
if (aStrokeOptions.mDashPattern[i] != 0) {
|
||||||
|
nonZero = true;
|
||||||
|
}
|
||||||
dashes[i] = aStrokeOptions.mDashPattern[i];
|
dashes[i] = aStrokeOptions.mDashPattern[i];
|
||||||
}
|
}
|
||||||
cairo_set_dash(aCtx, &dashes[0], aStrokeOptions.mDashLength,
|
// Avoid all-zero patterns that would trigger the CAIRO_STATUS_INVALID_DASH context error state.
|
||||||
aStrokeOptions.mDashOffset);
|
if (nonZero) {
|
||||||
|
cairo_set_dash(aCtx, &dashes[0], aStrokeOptions.mDashLength,
|
||||||
|
aStrokeOptions.mDashOffset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cairo_set_line_join(aCtx, GfxLineJoinToCairoLineJoin(aStrokeOptions.mLineJoin));
|
cairo_set_line_join(aCtx, GfxLineJoinToCairoLineJoin(aStrokeOptions.mLineJoin));
|
||||||
|
|
|
@ -98,19 +98,31 @@ InitTextures(IDirect3DDevice9* aDevice,
|
||||||
return result.forget();
|
return result.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
FinishTextures(IDirect3DDevice9* aDevice,
|
FinishTextures(IDirect3DDevice9* aDevice,
|
||||||
IDirect3DTexture9* aTexture,
|
IDirect3DTexture9* aTexture,
|
||||||
IDirect3DSurface9* aSurface)
|
IDirect3DSurface9* aSurface)
|
||||||
{
|
{
|
||||||
if (!aDevice) {
|
if (!aDevice) {
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT hr = aSurface->UnlockRect();
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
aSurface->UnlockRect();
|
|
||||||
nsRefPtr<IDirect3DSurface9> dstSurface;
|
nsRefPtr<IDirect3DSurface9> dstSurface;
|
||||||
aTexture->GetSurfaceLevel(0, getter_AddRefs(dstSurface));
|
hr = aTexture->GetSurfaceLevel(0, getter_AddRefs(dstSurface));
|
||||||
aDevice->UpdateSurface(aSurface, nullptr, dstSurface, nullptr);
|
if (FAILED(hr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = aDevice->UpdateSurface(aSurface, nullptr, dstSurface, nullptr);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool UploadData(IDirect3DDevice9* aDevice,
|
static bool UploadData(IDirect3DDevice9* aDevice,
|
||||||
|
@ -137,8 +149,7 @@ static bool UploadData(IDirect3DDevice9* aDevice,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FinishTextures(aDevice, aTexture, surf);
|
return FinishTextures(aDevice, aTexture, surf);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureClient*
|
TextureClient*
|
||||||
|
|
|
@ -1254,10 +1254,9 @@ APZCTreeManager::DispatchFling(AsyncPanZoomController* aPrev,
|
||||||
|
|
||||||
transformedVelocity = endPoint - startPoint;
|
transformedVelocity = endPoint - startPoint;
|
||||||
|
|
||||||
bool handoff = (startIndex < 1) ? aHandoff : true;
|
|
||||||
if (current->AttemptFling(transformedVelocity,
|
if (current->AttemptFling(transformedVelocity,
|
||||||
aOverscrollHandoffChain,
|
aOverscrollHandoffChain,
|
||||||
handoff)) {
|
aHandoff)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -545,6 +545,7 @@ public:
|
||||||
// HandleFlingOverscroll() (which acquires the tree lock) would violate
|
// HandleFlingOverscroll() (which acquires the tree lock) would violate
|
||||||
// the lock ordering. Instead we schedule HandleFlingOverscroll() to be
|
// the lock ordering. Instead we schedule HandleFlingOverscroll() to be
|
||||||
// called after mMonitor is released.
|
// called after mMonitor is released.
|
||||||
|
APZC_LOG("%p fling went into overscroll, handing off with velocity %s\n", &mApzc, Stringify(velocity).c_str());
|
||||||
mDeferredTasks.append(NewRunnableMethod(&mApzc,
|
mDeferredTasks.append(NewRunnableMethod(&mApzc,
|
||||||
&AsyncPanZoomController::HandleFlingOverscroll,
|
&AsyncPanZoomController::HandleFlingOverscroll,
|
||||||
velocity,
|
velocity,
|
||||||
|
@ -2131,6 +2132,7 @@ void AsyncPanZoomController::AcceptFling(const ParentLayerPoint& aVelocity,
|
||||||
bool aHandoff) {
|
bool aHandoff) {
|
||||||
// We may have a pre-existing velocity for whatever reason (for example,
|
// We may have a pre-existing velocity for whatever reason (for example,
|
||||||
// a previously handed off fling). We don't want to clobber that.
|
// a previously handed off fling). We don't want to clobber that.
|
||||||
|
APZC_LOG("%p accepting fling with velocity %s\n", this, Stringify(aVelocity).c_str());
|
||||||
mX.SetVelocity(mX.GetVelocity() + aVelocity.x);
|
mX.SetVelocity(mX.GetVelocity() + aVelocity.x);
|
||||||
mY.SetVelocity(mY.GetVelocity() + aVelocity.y);
|
mY.SetVelocity(mY.GetVelocity() + aVelocity.y);
|
||||||
SetState(FLING);
|
SetState(FLING);
|
||||||
|
|
|
@ -564,6 +564,11 @@ DXGIYCbCrTextureClient::Create(ISurfaceAllocator* aAllocator,
|
||||||
const gfx::IntSize& aSizeY,
|
const gfx::IntSize& aSizeY,
|
||||||
const gfx::IntSize& aSizeCbCr)
|
const gfx::IntSize& aSizeCbCr)
|
||||||
{
|
{
|
||||||
|
if (!aHandleY || !aHandleCb || !aHandleCr ||
|
||||||
|
!aTextureY || !aTextureCb || !aTextureCr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
RefPtr<DXGIYCbCrTextureClient> texture =
|
RefPtr<DXGIYCbCrTextureClient> texture =
|
||||||
new DXGIYCbCrTextureClient(aAllocator, aFlags);
|
new DXGIYCbCrTextureClient(aAllocator, aFlags);
|
||||||
texture->mHandles[0] = aHandleY;
|
texture->mHandles[0] = aHandleY;
|
||||||
|
|
|
@ -206,6 +206,8 @@ public:
|
||||||
return mFrameMetrics;
|
return mFrameMetrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using AsyncPanZoomController::GetVelocityVector;
|
||||||
|
|
||||||
void AssertStateIsReset() const {
|
void AssertStateIsReset() const {
|
||||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||||
EXPECT_EQ(NOTHING, mState);
|
EXPECT_EQ(NOTHING, mState);
|
||||||
|
@ -1857,6 +1859,22 @@ protected:
|
||||||
manager->ClearTree();
|
manager->ClearTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample animations once for all APZCs, 1 ms later than the last sample.
|
||||||
|
*/
|
||||||
|
void SampleAnimationsOnce() {
|
||||||
|
const TimeDuration increment = TimeDuration::FromMilliseconds(1);
|
||||||
|
ParentLayerPoint pointOut;
|
||||||
|
ViewTransform viewTransformOut;
|
||||||
|
mcc->AdvanceBy(increment);
|
||||||
|
|
||||||
|
for (const nsRefPtr<Layer>& layer : layers) {
|
||||||
|
if (TestAsyncPanZoomController* apzc = ApzcOf(layer)) {
|
||||||
|
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nsRefPtr<MockContentControllerDelayed> mcc;
|
nsRefPtr<MockContentControllerDelayed> mcc;
|
||||||
|
|
||||||
nsTArray<nsRefPtr<Layer> > layers;
|
nsTArray<nsRefPtr<Layer> > layers;
|
||||||
|
@ -2528,14 +2546,15 @@ protected:
|
||||||
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateScrollgrabLayerTree() {
|
void CreateScrollgrabLayerTree(bool makeParentScrollable = true) {
|
||||||
const char* layerTreeSyntax = "c(t)";
|
const char* layerTreeSyntax = "c(t)";
|
||||||
nsIntRegion layerVisibleRegion[] = {
|
nsIntRegion layerVisibleRegion[] = {
|
||||||
nsIntRegion(IntRect(0, 0, 100, 100)), // scroll-grabbing parent
|
nsIntRegion(IntRect(0, 0, 100, 100)), // scroll-grabbing parent
|
||||||
nsIntRegion(IntRect(0, 20, 100, 80)) // child
|
nsIntRegion(IntRect(0, 20, 100, 80)) // child
|
||||||
};
|
};
|
||||||
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
|
||||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 120));
|
float parentHeight = makeParentScrollable ? 120 : 100;
|
||||||
|
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, parentHeight));
|
||||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200));
|
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200));
|
||||||
SetScrollHandoff(layers[1], root);
|
SetScrollHandoff(layers[1], root);
|
||||||
registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
|
registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
|
||||||
|
@ -2543,6 +2562,43 @@ protected:
|
||||||
rootApzc = ApzcOf(root);
|
rootApzc = ApzcOf(root);
|
||||||
rootApzc->GetFrameMetrics().SetHasScrollgrab(true);
|
rootApzc->GetFrameMetrics().SetHasScrollgrab(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestFlingAcceleration() {
|
||||||
|
// Jack up the fling acceleration multiplier so we can easily determine
|
||||||
|
// whether acceleration occured.
|
||||||
|
const float kAcceleration = 100.0f;
|
||||||
|
SCOPED_GFX_PREF(APZFlingAccelBaseMultiplier, float, kAcceleration);
|
||||||
|
|
||||||
|
nsRefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
|
||||||
|
|
||||||
|
// Pan once, enough to fully scroll the scrollgrab parent and then scroll
|
||||||
|
// and fling the child.
|
||||||
|
Pan(manager, mcc, 70, 40);
|
||||||
|
|
||||||
|
// Give the fling animation a chance to start.
|
||||||
|
SampleAnimationsOnce();
|
||||||
|
|
||||||
|
float childVelocityAfterFling1 = childApzc->GetVelocityVector().y;
|
||||||
|
|
||||||
|
// Pan again.
|
||||||
|
Pan(manager, mcc, 70, 40);
|
||||||
|
|
||||||
|
// Give the fling animation a chance to start.
|
||||||
|
// This time it should be accelerated.
|
||||||
|
SampleAnimationsOnce();
|
||||||
|
|
||||||
|
float childVelocityAfterFling2 = childApzc->GetVelocityVector().y;
|
||||||
|
|
||||||
|
// We should have accelerated once.
|
||||||
|
// The division by 2 is to account for friction.
|
||||||
|
EXPECT_GT(childVelocityAfterFling2,
|
||||||
|
childVelocityAfterFling1 * kAcceleration / 2);
|
||||||
|
|
||||||
|
// We should not have accelerated twice.
|
||||||
|
// The division by 4 is to account for friction.
|
||||||
|
EXPECT_LE(childVelocityAfterFling2,
|
||||||
|
childVelocityAfterFling1 * kAcceleration * kAcceleration / 4);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Here we test that if the processing of a touch block is deferred while we
|
// Here we test that if the processing of a touch block is deferred while we
|
||||||
|
@ -2744,6 +2800,16 @@ TEST_F(APZOverscrollHandoffTester, ScrollgrabFling) {
|
||||||
childApzc->AssertStateIsReset();
|
childApzc->AssertStateIsReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration1) {
|
||||||
|
CreateScrollgrabLayerTree(true /* make parent scrollable */);
|
||||||
|
TestFlingAcceleration();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration2) {
|
||||||
|
CreateScrollgrabLayerTree(false /* do not make parent scrollable */);
|
||||||
|
TestFlingAcceleration();
|
||||||
|
}
|
||||||
|
|
||||||
class APZEventRegionsTester : public APZCTreeManagerTester {
|
class APZEventRegionsTester : public APZCTreeManagerTester {
|
||||||
protected:
|
protected:
|
||||||
UniquePtr<ScopedLayerTreeRegistration> registration;
|
UniquePtr<ScopedLayerTreeRegistration> registration;
|
||||||
|
|
|
@ -86,8 +86,10 @@ bool
|
||||||
ImageCacheKey::operator==(const ImageCacheKey& aOther) const
|
ImageCacheKey::operator==(const ImageCacheKey& aOther) const
|
||||||
{
|
{
|
||||||
if (mBlobSerial || aOther.mBlobSerial) {
|
if (mBlobSerial || aOther.mBlobSerial) {
|
||||||
// If at least one of us has a blob serial, just compare those.
|
// If at least one of us has a blob serial, just compare the blob serial and
|
||||||
return mBlobSerial == aOther.mBlobSerial;
|
// the ref portion of the URIs.
|
||||||
|
return mBlobSerial == aOther.mBlobSerial &&
|
||||||
|
mURI->HasSameRef(*aOther.mURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For non-blob URIs, compare the URIs.
|
// For non-blob URIs, compare the URIs.
|
||||||
|
@ -109,8 +111,13 @@ ImageCacheKey::ComputeHash(ImageURL* aURI,
|
||||||
|
|
||||||
if (aBlobSerial) {
|
if (aBlobSerial) {
|
||||||
// For blob URIs, we hash the serial number of the underlying blob, so that
|
// For blob URIs, we hash the serial number of the underlying blob, so that
|
||||||
// different blob URIs which point to the same blob share a cache entry.
|
// different blob URIs which point to the same blob share a cache entry. We
|
||||||
return HashGeneric(*aBlobSerial);
|
// also include the ref portion of the URI to support -moz-samplesize and
|
||||||
|
// -moz-resolution, which require us to create different Image objects even
|
||||||
|
// if the source data is the same.
|
||||||
|
nsAutoCString ref;
|
||||||
|
aURI->GetRef(ref);
|
||||||
|
return HashGeneric(*aBlobSerial, HashString(ref));
|
||||||
}
|
}
|
||||||
|
|
||||||
// For non-blob URIs, we hash the URI spec.
|
// For non-blob URIs, we hash the URI spec.
|
||||||
|
|
|
@ -101,6 +101,11 @@ public:
|
||||||
return mSpec == aOther.mSpec;
|
return mSpec == aOther.mSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HasSameRef(const ImageURL& aOther) const
|
||||||
|
{
|
||||||
|
return mRef == aOther.mRef;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Since this is a basic storage class, no duplication of spec parsing is
|
// Since this is a basic storage class, no duplication of spec parsing is
|
||||||
// included in the functionality. Instead, the class depends upon the
|
// included in the functionality. Instead, the class depends upon the
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="reftest-wait">
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<img id="test">
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var image = new Image;
|
||||||
|
|
||||||
|
image.onload = function() {
|
||||||
|
// Create a canvas.
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 100;
|
||||||
|
canvas.height = 100;
|
||||||
|
|
||||||
|
// Draw the image into the canvas.
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.drawImage(image, 0, 0);
|
||||||
|
|
||||||
|
// Convert the image into a blob URI and use it as #test's src.
|
||||||
|
canvas.toBlob(function(blob) {
|
||||||
|
var uri = window.URL.createObjectURL(blob);
|
||||||
|
uri += '#-moz-samplesize=8';
|
||||||
|
document.getElementById('test').src = uri;
|
||||||
|
|
||||||
|
// Take the snapshot.
|
||||||
|
document.documentElement.removeAttribute('class');
|
||||||
|
}, 'image/jpeg', 0.99);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start loading the image.
|
||||||
|
image.src = 'image.png';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="reftest-wait">
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<img id="test">
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var image = new Image;
|
||||||
|
|
||||||
|
image.onload = function() {
|
||||||
|
// Create a canvas.
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 100;
|
||||||
|
canvas.height = 100;
|
||||||
|
|
||||||
|
// Draw the image into the canvas.
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.drawImage(image, 0, 0);
|
||||||
|
|
||||||
|
// Convert the image into a blob URI and use it as #test's src.
|
||||||
|
canvas.toBlob(function(blob) {
|
||||||
|
var uri = window.URL.createObjectURL(blob);
|
||||||
|
document.getElementById('test').src = uri;
|
||||||
|
|
||||||
|
// Take the snapshot.
|
||||||
|
document.documentElement.removeAttribute('class');
|
||||||
|
}, 'image/jpeg', 0.99);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start loading the image.
|
||||||
|
image.src = 'image.png';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 840 B |
|
@ -0,0 +1,7 @@
|
||||||
|
# Blob URI tests
|
||||||
|
|
||||||
|
# Test that blob URIs don't get merged if they have different ref params.
|
||||||
|
# (We run the test twice to check both cached and non-cached cases.)
|
||||||
|
default-preferences pref(image.mozsamplesize.enabled,true)
|
||||||
|
!= blob-uri-with-ref-param.html blob-uri-with-ref-param-notref.html
|
||||||
|
!= blob-uri-with-ref-param.html blob-uri-with-ref-param-notref.html
|
|
@ -46,5 +46,8 @@ include color-management/reftest.list
|
||||||
# Downscaling tests
|
# Downscaling tests
|
||||||
include downscaling/reftest.list
|
include downscaling/reftest.list
|
||||||
|
|
||||||
|
# Blob URI tests
|
||||||
|
include blob/reftest.list
|
||||||
|
|
||||||
# Lossless encoders
|
# Lossless encoders
|
||||||
skip-if(Android||B2G) include encoders-lossless/reftest.list # bug 783621
|
skip-if(Android||B2G) include encoders-lossless/reftest.list # bug 783621
|
||||||
|
|
|
@ -710,14 +710,20 @@ class NameResolver
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Import/export spec lists contain only import/export specs
|
// Import/export spec lists contain import/export specs containing
|
||||||
// containing only pairs of names.
|
// only pairs of names. Alternatively, an export spec lists may
|
||||||
|
// contain a single export batch specifier.
|
||||||
case PNK_IMPORT_SPEC_LIST: {
|
case PNK_IMPORT_SPEC_LIST: {
|
||||||
case PNK_EXPORT_SPEC_LIST:
|
case PNK_EXPORT_SPEC_LIST:
|
||||||
MOZ_ASSERT(cur->isArity(PN_LIST));
|
MOZ_ASSERT(cur->isArity(PN_LIST));
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
bool isImport = cur->isKind(PNK_IMPORT_SPEC_LIST);
|
bool isImport = cur->isKind(PNK_IMPORT_SPEC_LIST);
|
||||||
for (ParseNode* item = cur->pn_head; item; item = item->pn_next) {
|
ParseNode* item = cur->pn_head;
|
||||||
|
if (!isImport && item && item->isKind(PNK_EXPORT_BATCH_SPEC)) {
|
||||||
|
MOZ_ASSERT(item->isArity(PN_NULLARY));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (; item; item = item->pn_next) {
|
||||||
MOZ_ASSERT(item->isKind(isImport ? PNK_IMPORT_SPEC : PNK_EXPORT_SPEC));
|
MOZ_ASSERT(item->isKind(isImport ? PNK_IMPORT_SPEC : PNK_EXPORT_SPEC));
|
||||||
MOZ_ASSERT(item->isArity(PN_BINARY));
|
MOZ_ASSERT(item->isArity(PN_BINARY));
|
||||||
MOZ_ASSERT(item->pn_left->isKind(PNK_NAME));
|
MOZ_ASSERT(item->pn_left->isKind(PNK_NAME));
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
// |jit-test| error: SyntaxError
|
||||||
|
export *
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче