Merge m-c to fx-team, a=merge

This commit is contained in:
Wes Kocher 2015-06-05 16:05:33 -07:00
commit 079a117eae
223 changed files with 6328 additions and 2720 deletions

View File

@ -29,7 +29,7 @@ class JetpackRunner(MozbuildObject):
@CommandProvider
class MachCommands(MachCommandBase):
@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):
# 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

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae4ef3f63b921fa46c20d9d4feb271713fb014ba"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae4ef3f63b921fa46c20d9d4feb271713fb014ba"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae4ef3f63b921fa46c20d9d4feb271713fb014ba"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="aac9cc4bb94cf720baf8f7ee419b4d76ac86b1ac"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae4ef3f63b921fa46c20d9d4feb271713fb014ba"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae4ef3f63b921fa46c20d9d4feb271713fb014ba"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae4ef3f63b921fa46c20d9d4feb271713fb014ba"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae4ef3f63b921fa46c20d9d4feb271713fb014ba"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="aac9cc4bb94cf720baf8f7ee419b4d76ac86b1ac"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae4ef3f63b921fa46c20d9d4feb271713fb014ba"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "ae4ef3f63b921fa46c20d9d4feb271713fb014ba",
"git_revision": "1d62b32408567f9f7cf1c71c1e5a0c6593be757b",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "d9730e79977fe0a3416596d1ec9e942ed6dc15a9",
"revision": "63c872ccb3541c2ddb0d67f112480eb8f2650cda",
"repo_path": "integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae4ef3f63b921fa46c20d9d4feb271713fb014ba"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae4ef3f63b921fa46c20d9d4feb271713fb014ba"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -1022,14 +1022,9 @@ chatbox:-moz-full-screen-ancestor > .chat-titlebar {
direction: ltr;
}
.contentSelectDropdown-optgroup {
font-weight: bold;
/* color: menutext used to overwrite the disabled color */
color: menutext;
}
.contentSelectDropdown-ingroup {
-moz-margin-start: 2em;
/* Indent options in optgroups */
.contentSelectDropdown-ingroup .menu-iconic-text {
-moz-padding-start: 2em;
}
/* Give this menupopup an arrow panel styling */

View File

@ -379,6 +379,8 @@ skip-if = e10s
support-files =
searchSuggestionUI.html
searchSuggestionUI.js
[browser_selectpopup.js]
run-if = e10s
[browser_selectTabAtIndex.js]
[browser_ssl_error_reports.js]
[browser_star_hsts.js]

View File

@ -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();
});

View File

@ -355,8 +355,7 @@ nsMacShellService::OpenApplication(int32_t aApplication)
if (!exists)
return NS_ERROR_FILE_NOT_FOUND;
return lf->Launch();
}
break;
}
case nsIMacShellService::APPLICATION_DESKTOP:
{
nsCOMPtr<nsIFile> lf;
@ -367,8 +366,7 @@ nsMacShellService::OpenApplication(int32_t aApplication)
if (!exists)
return NS_ERROR_FILE_NOT_FOUND;
return lf->Launch();
}
break;
}
}
if (appURL && err == noErr) {

View File

@ -25,7 +25,7 @@ let testData = [
["VK_TAB", {shiftKey: true}, "display", -1, 0],
["VK_BACK_SPACE", {}, "", -1, 0],
["c", {}, "caption-side", 0, 10],
["o", {}, "color", 0, 6],
["o", {}, "color", 0, 7],
["VK_TAB", {}, "none", -1, 0],
["r", {}, "rebeccapurple", 0, 6],
["VK_DOWN", {}, "red", 1, 6],

View File

@ -5166,20 +5166,6 @@ else
AC_SUBST(MOZ_SAMPLE_TYPE_FLOAT32)
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 = Disable Speech API pocketsphinx backend
dnl ========================================================
@ -5208,6 +5194,24 @@ fi
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 = Disable Speech API models
dnl ========================================================
@ -5216,6 +5220,10 @@ MOZ_ARG_DISABLE_BOOL(webspeechmodels,
MOZ_WEBSPEECH_MODELS=,
MOZ_WEBSPEECH_MODELS=1)
if test -z "$MOZ_WEBSPEECH"; then
MOZ_WEBSPEECH_MODELS=
fi
if test -n "$MOZ_WEBSPEECH_MODELS"; then
AC_DEFINE(MOZ_WEBSPEECH_MODELS)
fi

View File

@ -725,7 +725,7 @@ DOMException::Create(nsresult aRv)
}
/* static */already_AddRefed<DOMException>
DOMException::Create(nsresult aRv, const nsCString& aMessage)
DOMException::Create(nsresult aRv, const nsACString& aMessage)
{
nsCString name;
nsCString message;

View File

@ -158,7 +158,7 @@ public:
Create(nsresult aRv);
static already_AddRefed<DOMException>
Create(nsresult aRv, const nsCString& aMessage);
Create(nsresult aRv, const nsACString& aMessage);
protected:

View File

@ -34,15 +34,12 @@ JSObject* GetDefaultScopeFromJSContext(JSContext *cx);
// A factory function for turning a JS::Value argv into an nsIArray
// 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
// element is rooted.
// Optionally, aArgv may be nullptr, in which case the array is allocated and
// rooted, but all items remain nullptr. This presumably means the caller
// will then QI us for nsIJSArgArray, and set our array elements.
nsresult NS_CreateJSArgv(JSContext *aContext, uint32_t aArgc, void *aArgv,
nsIJSArgArray **aArray);
nsresult NS_CreateJSArgv(JSContext *aContext, uint32_t aArgc,
const JS::Value* aArgv, nsIJSArgArray **aArray);
#endif // nsDOMJSUtils_h__

View File

@ -294,37 +294,6 @@ nsFrameLoader::ReallyStartLoading()
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
nsFrameLoader::ReallyStartLoadingInternal()
{

View File

@ -7844,7 +7844,7 @@ nsGlobalWindow::OpenDialog(JSContext* aCx, const nsAString& aUrl,
nsCOMPtr<nsIJSArgArray> argvArray;
aError = NS_CreateJSArgv(aCx, aExtraArgument.Length(),
const_cast<JS::Value*>(aExtraArgument.Elements()),
aExtraArgument.Elements(),
getter_AddRefs(argvArray));
if (aError.Failed()) {
return nullptr;

View File

@ -192,10 +192,6 @@ protected:
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
* default implementation returns CORS_NONE unconditionally.

View File

@ -2868,7 +2868,7 @@ mozilla::dom::ShutdownJSEnvironment()
// on-the-fly.
class nsJSArgArray final : public nsIJSArgArray {
public:
nsJSArgArray(JSContext *aContext, uint32_t argc, JS::Value *argv,
nsJSArgArray(JSContext *aContext, uint32_t argc, const JS::Value* argv,
nsresult *prv);
// nsISupports
@ -2891,11 +2891,11 @@ protected:
uint32_t mArgc;
};
nsJSArgArray::nsJSArgArray(JSContext *aContext, uint32_t argc, JS::Value *argv,
nsresult *prv) :
mContext(aContext),
mArgv(nullptr),
mArgc(argc)
nsJSArgArray::nsJSArgArray(JSContext *aContext, uint32_t argc,
const JS::Value* argv, nsresult *prv)
: mContext(aContext)
, mArgv(nullptr)
, mArgc(argc)
{
// copy the array - we don't know its lifetime, and ours is tied to xpcom
// refcounting.
@ -3010,12 +3010,11 @@ NS_IMETHODIMP nsJSArgArray::Enumerate(nsISimpleEnumerator **_retval)
}
// The factory function
nsresult NS_CreateJSArgv(JSContext *aContext, uint32_t argc, void *argv,
nsIJSArgArray **aArray)
nsresult NS_CreateJSArgv(JSContext *aContext, uint32_t argc,
const JS::Value* argv, nsIJSArgArray **aArray)
{
nsresult rv;
nsCOMPtr<nsIJSArgArray> ret = new nsJSArgArray(aContext, argc,
static_cast<JS::Value *>(argv), &rv);
nsCOMPtr<nsIJSArgArray> ret = new nsJSArgArray(aContext, argc, argv, &rv);
if (NS_FAILED(rv)) {
return rv;
}

View File

@ -27,8 +27,7 @@ NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsMimeTypeArray,
mWindow,
mMimeTypes,
mHiddenMimeTypes)
mMimeTypes)
nsMimeTypeArray::nsMimeTypeArray(nsPIDOMWindow* aWindow)
: mWindow(aWindow)
@ -49,7 +48,6 @@ void
nsMimeTypeArray::Refresh()
{
mMimeTypes.Clear();
mHiddenMimeTypes.Clear();
}
nsPIDOMWindow*
@ -114,10 +112,6 @@ nsMimeTypeArray::NamedGetter(const nsAString& aName, bool &aFound)
ToLowerCase(lowerName);
nsMimeType* mimeType = FindMimeType(mMimeTypes, lowerName);
if (!mimeType) {
mimeType = FindMimeType(mHiddenMimeTypes, lowerName);
}
if (mimeType) {
aFound = true;
return mimeType;
@ -164,11 +158,8 @@ nsMimeTypeArray::NamedGetter(const nsAString& aName, bool &aFound)
// If we got here, we support this type! Say so.
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);
mHiddenMimeTypes.AppendElement(mt);
mMimeTypes.AppendElement(mt);
return mt;
}
@ -199,7 +190,7 @@ nsMimeTypeArray::GetSupportedNames(unsigned, nsTArray< nsString >& aRetval)
void
nsMimeTypeArray::EnsurePluginMimeTypes()
{
if (!mMimeTypes.IsEmpty() || !mHiddenMimeTypes.IsEmpty() || !mWindow) {
if (!mMimeTypes.IsEmpty() || !mWindow) {
return;
}
@ -217,7 +208,7 @@ nsMimeTypeArray::EnsurePluginMimeTypes()
return;
}
pluginArray->GetMimeTypes(mMimeTypes, mHiddenMimeTypes);
pluginArray->GetMimeTypes(mMimeTypes);
}
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsMimeType, AddRef)

View File

@ -47,16 +47,9 @@ protected:
nsCOMPtr<nsPIDOMWindow> mWindow;
// mMimeTypes contains MIME types handled by non-hidden plugins, those
// popular plugins that must be exposed in navigator.plugins enumeration to
// avoid breaking web content. Likewise, mMimeTypes are exposed in
// navigator.mimeTypes enumeration.
// mMimeTypes contains MIME types handled by plugins or by an OS
// PreferredApplicationHandler.
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

View File

@ -6,11 +6,9 @@
#include "nsPluginArray.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/PluginArrayBinding.h"
#include "mozilla/dom/PluginBinding.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsMimeTypeArray.h"
#include "Navigator.h"
#include "nsIDocShell.h"
@ -68,8 +66,7 @@ NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginArray,
mWindow,
mPlugins,
mHiddenPlugins)
mPlugins)
static void
GetPluginMimeTypes(const nsTArray<nsRefPtr<nsPluginElement> >& aPlugins,
@ -89,11 +86,9 @@ operator<(const nsRefPtr<nsMimeType>& lhs, const nsRefPtr<nsMimeType>& rhs)
}
void
nsPluginArray::GetMimeTypes(nsTArray<nsRefPtr<nsMimeType> >& aMimeTypes,
nsTArray<nsRefPtr<nsMimeType> >& aHiddenMimeTypes)
nsPluginArray::GetMimeTypes(nsTArray<nsRefPtr<nsMimeType>>& aMimeTypes)
{
aMimeTypes.Clear();
aHiddenMimeTypes.Clear();
if (!AllowPlugins()) {
return;
@ -102,7 +97,6 @@ nsPluginArray::GetMimeTypes(nsTArray<nsRefPtr<nsMimeType> >& aMimeTypes,
EnsurePlugins();
GetPluginMimeTypes(mPlugins, aMimeTypes);
GetPluginMimeTypes(mHiddenPlugins, aHiddenMimeTypes);
// Alphabetize the enumeration order of non-hidden MIME types to reduce
// 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
// the both arrays contain the same plugin tags (though as
// different types).
uint32_t pluginCount = mPlugins.Length() + mHiddenPlugins.Length();
if (newPluginTags.Length() == pluginCount) {
if (newPluginTags.Length() == mPlugins.Length()) {
return;
}
}
mPlugins.Clear();
mHiddenPlugins.Clear();
nsCOMPtr<nsIDOMNavigator> navigator;
mWindow->GetNavigator(getter_AddRefs(navigator));
@ -225,10 +217,6 @@ nsPluginArray::NamedGetter(const nsAString& aName, bool &aFound)
EnsurePlugins();
nsPluginElement* plugin = FindPlugin(mPlugins, aName);
if (!plugin) {
plugin = FindPlugin(mHiddenPlugins, aName);
}
aFound = (plugin != nullptr);
return plugin;
}
@ -286,28 +274,6 @@ nsPluginArray::AllowPlugins() const
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
operator<(const nsRefPtr<nsPluginElement>& lhs,
const nsRefPtr<nsPluginElement>& rhs)
@ -319,7 +285,7 @@ operator<(const nsRefPtr<nsPluginElement>& lhs,
void
nsPluginArray::EnsurePlugins()
{
if (!mPlugins.IsEmpty() || !mHiddenPlugins.IsEmpty()) {
if (!mPlugins.IsEmpty()) {
// We already have an array of plugin elements.
return;
}
@ -333,36 +299,11 @@ nsPluginArray::EnsurePlugins()
nsTArray<nsRefPtr<nsPluginTag> > 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
// scriptable.
for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
nsPluginTag* pluginTag = pluginTags[i];
// 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));
mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTag));
}
// Alphabetize the enumeration order of non-hidden plugins to reduce

View File

@ -40,8 +40,7 @@ public:
void Init();
void Invalidate();
void GetMimeTypes(nsTArray<nsRefPtr<nsMimeType> >& aMimeTypes,
nsTArray<nsRefPtr<nsMimeType> >& aHiddenMimeTypes);
void GetMimeTypes(nsTArray<nsRefPtr<nsMimeType>>& aMimeTypes);
// PluginArray WebIDL methods
@ -61,18 +60,7 @@ private:
void EnsurePlugins();
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;
// 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,

View File

@ -729,7 +729,7 @@ struct BluetoothGattReadParam {
BluetoothGattServiceId mServiceId;
BluetoothGattId mCharId;
BluetoothGattId mDescriptorId;
uint32_t mValueType;
uint16_t mValueType;
uint16_t mValueLength;
uint8_t mValue[BLUETOOTH_GATT_MAX_ATTR_LEN];
uint8_t mStatus;

View File

@ -969,13 +969,13 @@ BluetoothDaemonGattModule::HandleRsp(
MOZ_ASSERT(!NS_IsMainThread()); // I/O thread
size_t length = MOZ_ARRAY_LENGTH(HandleRsp) +
MOZ_ARRAY_LENGTH(HandleClientRsp);
bool isInGattArray = HandleRsp[aHeader.mOpcode];
bool isInGattClientArray = HandleClientRsp[aHeader.mOpcode];
bool isInGattArray = aHeader.mOpcode < MOZ_ARRAY_LENGTH(HandleRsp) &&
HandleRsp[aHeader.mOpcode];
bool isInGattClientArray =
aHeader.mOpcode < MOZ_ARRAY_LENGTH(HandleClientRsp) &&
HandleClientRsp[aHeader.mOpcode];
if (NS_WARN_IF(!(aHeader.mOpcode < length)) ||
NS_WARN_IF(!(isInGattArray || isInGattClientArray))) {
if (NS_WARN_IF(!isInGattArray && !isInGattClientArray)) {
return;
}
@ -1065,13 +1065,13 @@ public:
return rv;
}
/* Read Length */
size_t length;
uint16_t length;
rv = UnpackPDU(pdu, length);
if (NS_FAILED(rv)) {
return rv;
}
/* Read Adv Data */
rv = UnpackPDU(pdu, aArg3);
rv = UnpackPDU(pdu, UnpackArray<uint8_t>(aArg3.mAdvData, length));
if (NS_FAILED(rv)) {
return rv;
}

View File

@ -1772,13 +1772,13 @@ UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothGattReadParam& aOut)
if (NS_FAILED(rv)) {
return rv;
}
/* unpack value type */
rv = UnpackPDU(aPDU, aOut.mValueType);
/* unpack status */
rv = UnpackPDU(aPDU, aOut.mStatus);
if (NS_FAILED(rv)) {
return rv;
}
/* unpack status */
rv = UnpackPDU(aPDU, aOut.mStatus);
/* unpack value type */
rv = UnpackPDU(aPDU, aOut.mValueType);
if (NS_FAILED(rv)) {
return rv;
}

View File

@ -939,12 +939,6 @@ UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothUuid& aOut)
return aPDU.Read(aOut.mUuid, sizeof(aOut.mUuid));
}
inline nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothGattAdvData& aOut)
{
return aPDU.Read(aOut.mAdvData, sizeof(aOut.mAdvData));
}
nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothGattId& aOut);

View File

@ -21464,7 +21464,7 @@ function test_getImageData_after_zero_canvas() {
}
</script>
<p>Canvas test: zero_dimensions_image_data</p>
<p>Canvas test: linedash</p>
<canvas id="c687" width="150" height="50"></canvas>
<script type="text/javascript">
@ -21536,6 +21536,16 @@ function test_linedash() {
isPixel(ctx, 105, 40, 0, 255, 0, 255, 0);
isPixel(ctx, 90, 35, 0, 0, 0, 0, 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>

View File

@ -3281,12 +3281,13 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
}
if (dispatchedToContentProcess) {
dragSession->SetCanDrop(true);
}
// now set the drop effect in the initial dataTransfer. This ensures
// that we can get the desired drop effect in the drop event.
if (initialDataTransfer)
} else if (initialDataTransfer) {
// 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
// dispatched to content, the content process will take care of setting
// this.
initialDataTransfer->SetDropEffectInt(dropEffect);
}
}
break;

View File

@ -1179,6 +1179,9 @@ IMEContentObserver::FlushMergeableNotifications()
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) {
mIsSelectionChangeEventPending = false;
nsContentUtils::AddScriptRunner(

View File

@ -684,6 +684,13 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
uint32_t responseStatus = 200;
nsAutoCString statusText;
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

View File

@ -430,6 +430,7 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcMediaSource)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcStream)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaybackStream)
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(mMediaSource)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcMediaSource)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
@ -515,10 +517,7 @@ HTMLMediaElement::IsVideo()
already_AddRefed<MediaSource>
HTMLMediaElement::GetMozMediaSourceObject() const
{
nsRefPtr<MediaSource> source;
if (mLoadingSrc && IsMediaSourceURI(mLoadingSrc)) {
NS_GetSourceForMediaSourceURI(mLoadingSrc, getter_AddRefs(source));
}
nsRefPtr<MediaSource> source = mMediaSource;
return source.forget();
}
@ -631,7 +630,7 @@ void HTMLMediaElement::ShutdownDecoder()
RemoveMediaElementFromURITable();
NS_ASSERTION(mDecoder, "Must have decoder to shut down");
mDecoder->Shutdown();
SetDecoder(nullptr);
mDecoder = nullptr;
}
void HTMLMediaElement::AbortExistingLoads()
@ -667,12 +666,9 @@ void HTMLMediaElement::AbortExistingLoads()
if (mSrcStream) {
EndSrcMediaStreamPlayback();
}
if (mMediaSource) {
mMediaSource->Detach();
mMediaSource = nullptr;
}
mLoadingSrc = nullptr;
mMediaSource = nullptr;
if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING ||
mNetworkState == nsIDOMHTMLMediaElement::NETWORK_IDLE)
@ -872,6 +868,7 @@ void HTMLMediaElement::SelectResource()
"Should think we're not loading from source children by default");
mLoadingSrc = uri;
mMediaSource = mSrcMediaSource;
UpdatePreloadAction();
if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE &&
!IsMediaStreamURI(mLoadingSrc)) {
@ -1009,6 +1006,7 @@ void HTMLMediaElement::LoadFromSourceChildren()
}
mLoadingSrc = uri;
mMediaSource = childSrc->GetSrcMediaSource();
NS_ASSERTION(mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING,
"Network state should be loading");
@ -1084,7 +1082,7 @@ void HTMLMediaElement::UpdatePreloadAction()
kNameSpaceID_None);
// MSE doesn't work if preload is none, so it ignores the pref when src is
// from MSE.
uint32_t preloadDefault = (mLoadingSrc && IsMediaSourceURI(mLoadingSrc)) ?
uint32_t preloadDefault = mMediaSource ?
HTMLMediaElement::PRELOAD_ATTR_METADATA :
Preferences::GetInt("media.preload.default",
HTMLMediaElement::PRELOAD_ATTR_METADATA);
@ -1193,9 +1191,8 @@ nsresult HTMLMediaElement::LoadResource()
nsRefPtr<DOMMediaStream> stream;
rv = NS_GetStreamForMediaStreamURI(mLoadingSrc, getter_AddRefs(stream));
if (NS_FAILED(rv)) {
nsCString specUTF8;
mLoadingSrc->GetSpec(specUTF8);
NS_ConvertUTF8toUTF16 spec(specUTF8);
nsAutoString spec;
GetCurrentSrc(spec);
const char16_t* params[] = { spec.get() };
ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
return rv;
@ -1204,25 +1201,14 @@ nsresult HTMLMediaElement::LoadResource()
return NS_OK;
}
if (IsMediaSourceURI(mLoadingSrc)) {
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;
}
if (mMediaSource) {
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
// all, due to network errors, causing the user agent to give up
// trying to fetch the resource" section of resource fetch algorithm.
return NS_ERROR_FAILURE;
}
mMediaSource = source.forget();
nsRefPtr<MediaResource> resource =
MediaSourceDecoder::CreateResource(mMediaSource->GetPrincipal());
if (IsAutoplayEnabled()) {
@ -2129,10 +2115,6 @@ HTMLMediaElement::~HTMLMediaElement()
if (mSrcStream) {
EndSrcMediaStreamPlayback();
}
if (mMediaSource) {
mMediaSource->Detach();
mMediaSource = nullptr;
}
NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
"Destroyed media element should no longer be in element table");
@ -2526,6 +2508,33 @@ nsresult HTMLMediaElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
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,
nsIContent* aBindingParent,
bool aCompileEventHandlers)
@ -2797,7 +2806,7 @@ nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder,
nsresult rv = aDecoder->Load(aListener, aCloneDonor);
if (NS_FAILED(rv)) {
SetDecoder(nullptr);
ShutdownDecoder();
LOG(LogLevel::Debug, ("%p Failed to load for decoder %p", this, aDecoder));
return rv;
}
@ -3259,6 +3268,7 @@ void HTMLMediaElement::DecodeError()
ShutdownDecoder();
}
mLoadingSrc = nullptr;
mMediaSource = nullptr;
if (mIsLoadingFromSourceChildren) {
mError = nullptr;
if (mSourceLoadCandidate) {
@ -3425,7 +3435,7 @@ void HTMLMediaElement::CheckProgress(bool aHaveNewProgress)
if (now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) {
DispatchAsyncEvent(NS_LITERAL_STRING("stalled"));
if (mLoadingSrc && IsMediaSourceURI(mLoadingSrc)) {
if (mMediaSource) {
ChangeDelayLoadStatus(false);
}

View File

@ -132,6 +132,9 @@ public:
bool aNotify) override;
virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
bool aNotify) override;
virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
const nsAttrValue* aValue,
bool aNotify) override;
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
@ -645,7 +648,10 @@ protected:
class StreamSizeListener;
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 SetItemValueText(const nsAString& text) override;
@ -1064,7 +1070,14 @@ protected:
// mSrcStream.
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;
// Holds a reference to the first channel we open to the media resource.

View File

@ -9,11 +9,13 @@
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/dom/ResponsiveImageSelector.h"
#include "mozilla/dom/MediaSource.h"
#include "nsGkAtoms.h"
#include "nsIMediaList.h"
#include "nsCSSParser.h"
#include "nsHostObjectProtocolHandler.h"
#include "mozilla/Preferences.h"
@ -31,8 +33,15 @@ HTMLSourceElement::~HTMLSourceElement()
{
}
NS_IMPL_ISUPPORTS_INHERITED(HTMLSourceElement, nsGenericHTMLElement,
nsIDOMHTMLSourceElement)
NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLSourceElement, nsGenericHTMLElement,
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)
@ -114,6 +123,16 @@ HTMLSourceElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
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,

View File

@ -25,6 +25,8 @@ public:
// nsISupports
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLSourceElement,
nsGenericHTMLElement)
NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLSourceElement, source)
@ -49,6 +51,10 @@ public:
static bool WouldMatchMediaForDocument(const nsAString& aMediaStr,
const nsIDocument *aDocument);
// Return the MediaSource object if any associated with the src attribute
// when it was set.
MediaSource* GetSrcMediaSource() { return mSrcMediaSource; };
// WebIDL
void GetSrc(nsString& aSrc)
{
@ -111,6 +117,7 @@ protected:
private:
nsRefPtr<nsMediaList> mMediaList;
nsRefPtr<MediaSource> mSrcMediaSource;
};
} // namespace dom

View File

@ -3194,7 +3194,7 @@ nsGenericHTMLElement::IsEventAttributeName(nsIAtom *aName)
* would be set to. Helper for the media elements.
*/
nsresult
nsGenericHTMLElement::NewURIFromString(const nsAutoString& aURISpec,
nsGenericHTMLElement::NewURIFromString(const nsAString& aURISpec,
nsIURI** aURI)
{
NS_ENSURE_ARG_POINTER(aURI);

View File

@ -1011,7 +1011,7 @@ protected:
* Returns INVALID_STATE_ERR and nulls *aURI if aURISpec is empty
* 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
{

View File

@ -132,18 +132,18 @@ group2 = [ createNode('r', 'g2', false),
group3 = [ 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);
}
for each (let g in group2) {
for (let g of group2) {
$(tag == "input" ? "f1" : "m1").appendChild(g);
}
for each (let g in group3) {
for (let g of group3) {
$(tag == "input" ? "f2" : "m2").appendChild(g);
}
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, false,
"group" + n + "[" + window["group"+n].indexOf(g) +
"] defaultChecked wrong pass 1");
@ -154,8 +154,8 @@ for each (let n in [1, 2, 3]) {
}
group1[1].defaultChecked = true;
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, n == 1 && group1.indexOf(g) == 1,
"group" + n + "[" + window["group"+n].indexOf(g) +
"] defaultChecked wrong pass 2");
@ -166,8 +166,8 @@ for each (let n in [1, 2, 3]) {
}
group1[0].defaultChecked = true;
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 1 ||
group1.indexOf(g) == 0),
"group" + n + "[" + window["group"+n].indexOf(g) +
@ -179,8 +179,8 @@ for each (let n in [1, 2, 3]) {
}
group1[2].defaultChecked = true;
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, n == 1,
"group" + n + "[" + window["group"+n].indexOf(g) +
"] defaultChecked wrong pass 4");
@ -196,8 +196,8 @@ p.removeChild(group1[1]);
group1[1].defaultChecked = false;
group1[1].defaultChecked = true;
p.insertBefore(group1[1], next);
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, n == 1,
"group" + n + "[" + window["group"+n].indexOf(g) +
"] 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;
}
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, false,
"group" + n + "[" + window["group"+n].indexOf(g) +
"] defaultChecked wrong pass 6");
@ -222,8 +222,8 @@ for each (let n in [1, 2, 3]) {
}
group1[1].checked = true;
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, false,
"group" + n + "[" + window["group"+n].indexOf(g) +
"] defaultChecked wrong pass 7");
@ -234,8 +234,8 @@ for each (let n in [1, 2, 3]) {
}
group1[0].defaultChecked = true;
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, n == 1 && group1.indexOf(g) == 0,
"group" + n + "[" + window["group"+n].indexOf(g) +
"] defaultChecked wrong pass 8");
@ -246,8 +246,8 @@ for each (let n in [1, 2, 3]) {
}
group1[2].defaultChecked = true;
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
group1.indexOf(g) == 2),
"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]);
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
group1.indexOf(g) == 2),
"group" + n + "[" + window["group"+n].indexOf(g) +
@ -271,8 +271,8 @@ for each (let n in [1, 2, 3]) {
}
group1[2].checked = true;
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
group1.indexOf(g) == 2),
"group" + n + "[" + window["group"+n].indexOf(g) +
@ -285,8 +285,8 @@ for each (let n in [1, 2, 3]) {
}
group1[0].checked = true;
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
group1.indexOf(g) == 2),
"group" + n + "[" + window["group"+n].indexOf(g) +
@ -303,8 +303,8 @@ p = group2[1].parentNode;
p.removeChild(group2[1]);
p.insertBefore(group2[1], next);
group2[0].checked = true;
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
group1.indexOf(g) == 2),
"group" + n + "[" + window["group"+n].indexOf(g) +
@ -318,8 +318,8 @@ for each (let n in [1, 2, 3]) {
}
p.insertBefore(group2[1], next);
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
group1.indexOf(g) == 2),
"group" + n + "[" + window["group"+n].indexOf(g) +
@ -333,8 +333,8 @@ for each (let n in [1, 2, 3]) {
}
group2[1].defaultChecked = true;
for each (let n in [1, 2, 3]) {
for each (let g in window["group"+n]) {
for (let n of [1, 2, 3]) {
for (let g of window["group"+n]) {
is(g.defaultChecked, (n == 1 && (group1.indexOf(g) == 0 ||
group1.indexOf(g) == 2)) ||
(n == 2 && group2.indexOf(g) == 1),

View File

@ -41,6 +41,7 @@ using struct nsIMEUpdatePreference from "nsIWidget.h";
using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
using mozilla::gfx::IntPoint from "mozilla/gfx/Point.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::WidgetMouseEvent 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
* PR_FALSE if losing focus
* contentCache Cache of content
* preference Native widget preference for IME updates
*/
prio(urgent) sync NotifyIMEFocus(bool focus)
prio(urgent) sync NotifyIMEFocus(bool focus, ContentCache contentCache)
returns (nsIMEUpdatePreference preference);
/**
@ -181,6 +183,7 @@ parent:
* One call can encompass both a delete and an insert operation
* Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates
*
* contentCache Cache of content
* offset Starting offset of the change
* end Ending offset of the range deleted
* newEnd New ending offset after insertion
@ -189,43 +192,35 @@ parent:
* for insertion, offset == end
* 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,
bool causedByComposition);
/**
* Notifies chrome that there is a IME compostion rect updated
*
* offset The starting offset of this rect
* rect The rect of first character of selected IME composition
* caretOffset The offset of caret position
* caretRect The rect of IME caret
* contentCache Cache of content
*/
prio(urgent) async NotifyIMESelectedCompositionRect(uint32_t offset,
LayoutDeviceIntRect[] rect,
uint32_t caretOffset,
LayoutDeviceIntRect caretRect);
prio(urgent) async NotifyIMESelectedCompositionRect(ContentCache contentCache);
/**
* Notifies chrome that there has been a change in selection
* Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates
*
* anchor Offset where the selection started
* focus Offset where the caret is
* writingMode CSS writing-mode in effect at the focus
* contentCache Cache of content
* causedByComposition true if the change is caused by composition
*/
prio(urgent) async NotifyIMESelection(uint32_t anchor,
uint32_t focus,
WritingMode writingMode,
prio(urgent) async NotifyIMESelection(ContentCache contentCache,
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.
@ -235,22 +230,12 @@ parent:
prio(urgent) sync NotifyIMEMouseButtonEvent(IMENotification notification)
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
*
* editorRect Rect of current focused editor
* compositionRects Rects of current composition string
* contentCache Cache of content
*/
prio(urgent) async NotifyIMEPositionChange(LayoutDeviceIntRect editorRect,
LayoutDeviceIntRect[] compositionRects,
LayoutDeviceIntRect caretRect);
prio(urgent) async NotifyIMEPositionChange(ContentCache contentCache);
/**
* Instructs chrome to end any pending composition

View File

@ -261,14 +261,6 @@ TabParent::TabParent(nsIContentParent* aManager,
uint32_t aChromeFlags)
: TabContext(aContext)
, mFrameElement(nullptr)
, mIMESelectionAnchor(0)
, mIMESelectionFocus(0)
, mWritingMode()
, mIMEComposing(false)
, mIMECompositionEnding(false)
, mIMEEventCountAfterEnding(0)
, mIMECompositionStart(0)
, mIMECompositionRectOffset(0)
, mRect(0, 0, 0, 0)
, mDimensions(0, 0)
, mOrientation(0)
@ -1889,6 +1881,7 @@ TabParent::RecvHideTooltip()
bool
TabParent::RecvNotifyIMEFocus(const bool& aFocus,
const ContentCache& aContentCache,
nsIMEUpdatePreference* aPreference)
{
nsCOMPtr<nsIWidget> widget = GetWidget();
@ -1898,21 +1891,20 @@ TabParent::RecvNotifyIMEFocus(const bool& aFocus,
}
mIMETabParent = aFocus ? this : nullptr;
mIMESelectionAnchor = 0;
mIMESelectionFocus = 0;
widget->NotifyIME(IMENotification(aFocus ? NOTIFY_IME_OF_FOCUS :
NOTIFY_IME_OF_BLUR));
IMENotification notification(aFocus ? NOTIFY_IME_OF_FOCUS :
NOTIFY_IME_OF_BLUR);
mContentCache.AssignContent(aContentCache, &notification);
widget->NotifyIME(notification);
if (aFocus) {
*aPreference = widget->GetIMEUpdatePreference();
} else {
mIMECacheText.Truncate(0);
}
return true;
}
bool
TabParent::RecvNotifyIMETextChange(const uint32_t& aStart,
TabParent::RecvNotifyIMETextChange(const ContentCache& aContentCache,
const uint32_t& aStart,
const uint32_t& aEnd,
const uint32_t& aNewEnd,
const bool& aCausedByComposition)
@ -1935,59 +1927,45 @@ TabParent::RecvNotifyIMETextChange(const uint32_t& aStart,
notification.mTextChangeData.mOldEndOffset = aEnd;
notification.mTextChangeData.mNewEndOffset = aNewEnd;
notification.mTextChangeData.mCausedByComposition = aCausedByComposition;
mContentCache.AssignContent(aContentCache, &notification);
widget->NotifyIME(notification);
return true;
}
bool
TabParent::RecvNotifyIMESelectedCompositionRect(
const uint32_t& aOffset,
InfallibleTArray<LayoutDeviceIntRect>&& aRects,
const uint32_t& aCaretOffset,
const LayoutDeviceIntRect& aCaretRect)
const ContentCache& aContentCache)
{
// add rect to cache for another query
mIMECompositionRectOffset = aOffset;
mIMECompositionRects = aRects;
mIMECaretOffset = aCaretOffset;
mIMECaretRect = aCaretRect;
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget) {
return true;
}
widget->NotifyIME(IMENotification(NOTIFY_IME_OF_COMPOSITION_UPDATE));
IMENotification notification(NOTIFY_IME_OF_COMPOSITION_UPDATE);
mContentCache.AssignContent(aContentCache, &notification);
widget->NotifyIME(notification);
return true;
}
bool
TabParent::RecvNotifyIMESelection(const uint32_t& aAnchor,
const uint32_t& aFocus,
const mozilla::WritingMode& aWritingMode,
TabParent::RecvNotifyIMESelection(const ContentCache& aContentCache,
const bool& aCausedByComposition)
{
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget)
return true;
mIMESelectionAnchor = aAnchor;
mIMESelectionFocus = aFocus;
mWritingMode = aWritingMode;
IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
mContentCache.AssignContent(aContentCache, &notification);
const nsIMEUpdatePreference updatePreference =
widget->GetIMEUpdatePreference();
if (updatePreference.WantSelectionChange() &&
(updatePreference.WantChangesCausedByComposition() ||
!aCausedByComposition)) {
IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
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);
mContentCache.InitNotification(notification);
notification.mSelectionChangeData.mCausedByComposition =
aCausedByComposition;
widget->NotifyIME(notification);
@ -1996,10 +1974,14 @@ TabParent::RecvNotifyIMESelection(const uint32_t& aAnchor,
}
bool
TabParent::RecvNotifyIMETextHint(const nsString& aText)
TabParent::RecvUpdateContentCache(const ContentCache& aContentCache)
{
// Replace our cache with new text
mIMECacheText = aText;
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget) {
return true;
}
mContentCache.AssignContent(aContentCache);
return true;
}
@ -2020,31 +2002,20 @@ TabParent::RecvNotifyIMEMouseButtonEvent(
}
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();
if (!widget) {
return true;
}
IMENotification notification(NOTIFY_IME_OF_POSITION_CHANGE);
mContentCache.AssignContent(aContentCache, &notification);
const nsIMEUpdatePreference updatePreference =
widget->GetIMEUpdatePreference();
if (updatePreference.WantPositionChanged()) {
widget->NotifyIME(IMENotification(NOTIFY_IME_OF_POSITION_CHANGE));
widget->NotifyIME(notification);
}
return true;
}
@ -2194,125 +2165,25 @@ TabParent::RecvDispatchAfterKeyboardEvent(const WidgetKeyboardEvent& aEvent)
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
TabParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent)
{
aEvent.mSucceeded = false;
aEvent.mWasAsync = false;
aEvent.mReply.mFocusedWidget = nsCOMPtr<nsIWidget>(GetWidget()).get();
switch (aEvent.message)
{
case NS_QUERY_SELECTED_TEXT:
{
aEvent.mReply.mOffset = std::min(mIMESelectionAnchor, mIMESelectionFocus);
if (mIMESelectionAnchor == mIMESelectionFocus) {
aEvent.mReply.mString.Truncate(0);
} else {
if (mIMESelectionAnchor > mIMECacheText.Length() ||
mIMESelectionFocus > mIMECacheText.Length()) {
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;
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget) {
return true;
}
if (NS_WARN_IF(!mContentCache.HandleQueryContentEvent(aEvent, widget)) ||
NS_WARN_IF(!aEvent.mSucceeded)) {
return true;
}
switch (aEvent.message) {
case NS_QUERY_TEXT_RECT:
case NS_QUERY_CARET_RECT:
case NS_QUERY_EDITOR_RECT:
aEvent.mReply.mRect -= GetChildProcessOffset();
break;
default:
break;
}
return true;
}
@ -2324,56 +2195,22 @@ TabParent::SendCompositionEvent(WidgetCompositionEvent& event)
return false;
}
if (event.CausesDOMTextEvent()) {
return SendCompositionChangeEvent(event);
}
mIMEComposing = !event.CausesDOMCompositionEndEvent();
mIMECompositionStart = std::min(mIMESelectionAnchor, mIMESelectionFocus);
if (mIMECompositionEnding) {
mIMEEventCountAfterEnding++;
if (!mContentCache.OnCompositionEvent(event)) {
return true;
}
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
TabParent::SendSelectionEvent(WidgetSelectionEvent& event)
{
if (mIsDestroyed) {
return false;
}
mIMESelectionAnchor = event.mOffset + (event.mReversed ? event.mLength : 0);
mIMESelectionFocus = event.mOffset + (!event.mReversed ? event.mLength : 0);
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget) {
return true;
}
return PBrowserParent::SendSelectionEvent(event);
}
@ -2443,19 +2280,11 @@ TabParent::RecvEndIMEComposition(const bool& aCancel,
nsString* aComposition)
{
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget)
if (!widget) {
return true;
mIMECompositionEnding = true;
mIMEEventCountAfterEnding = 0;
widget->NotifyIME(IMENotification(aCancel ? REQUEST_TO_CANCEL_COMPOSITION :
REQUEST_TO_COMMIT_COMPOSITION));
mIMECompositionEnding = false;
*aNoCompositionEvent = !mIMEEventCountAfterEnding;
*aComposition = mIMECompositionText;
mIMECompositionText.Truncate(0);
}
*aNoCompositionEvent =
!mContentCache.RequestToCommitComposition(widget, aCancel, *aComposition);
return true;
}

View File

@ -8,13 +8,13 @@
#define mozilla_tabs_TabParent_h
#include "js/TypeDecls.h"
#include "mozilla/ContentCache.h"
#include "mozilla/dom/ipc/IdType.h"
#include "mozilla/dom/PBrowserParent.h"
#include "mozilla/dom/PFilePickerParent.h"
#include "mozilla/dom/TabContext.h"
#include "mozilla/EventForwards.h"
#include "mozilla/dom/File.h"
#include "mozilla/WritingModes.h"
#include "mozilla/RefPtr.h"
#include "nsCOMPtr.h"
#include "nsIAuthPromptProvider.h"
@ -161,29 +161,21 @@ public:
InfallibleTArray<CpowEntry>&& aCpows,
const IPC::Principal& aPrincipal) override;
virtual bool RecvNotifyIMEFocus(const bool& aFocus,
const ContentCache& aContentCache,
nsIMEUpdatePreference* aPreference)
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& aNewEnd,
const bool& aCausedByComposition) override;
virtual bool RecvNotifyIMESelectedCompositionRect(
const uint32_t& aOffset,
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,
virtual bool RecvNotifyIMESelectedCompositionRect(const ContentCache& aContentCache) override;
virtual bool RecvNotifyIMESelection(const ContentCache& aContentCache,
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,
bool* aConsumedByIME) override;
virtual bool RecvNotifyIMEEditorRect(const LayoutDeviceIntRect& aRect) override;
virtual bool RecvNotifyIMEPositionChange(
const LayoutDeviceIntRect& aEditorRect,
InfallibleTArray<LayoutDeviceIntRect>&& aCompositionRects,
const LayoutDeviceIntRect& aCaretRect) override;
virtual bool RecvNotifyIMEPositionChange(const ContentCache& aContentCache) override;
virtual bool RecvEndIMEComposition(const bool& aCancel,
bool* aNoCompositionEvent,
nsString* aComposition) override;
@ -469,8 +461,6 @@ protected:
const int32_t& aX, const int32_t& aY,
const int32_t& aCx, const int32_t& aCy) override;
bool SendCompositionChangeEvent(mozilla::WidgetCompositionEvent& event);
bool InitBrowserConfiguration(const nsCString& aURI,
BrowserConfiguration& aConfiguration);
@ -478,23 +468,7 @@ protected:
// IME
static TabParent *mIMETabParent;
nsString mIMECacheText;
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;
ContentCache mContentCache;
nsIntRect mRect;
ScreenIntSize mDimensions;

View File

@ -75,6 +75,9 @@
#endif
#include "MediaFormatReader.h"
#include "MP3Decoder.h"
#include "MP3Demuxer.h"
namespace mozilla
{
@ -358,6 +361,12 @@ IsMP4SupportedType(const nsACString& aType,
MP4Decoder::CanHandleMediaType(aType, aCodecs, haveAAC, haveH264, haveMP3);
}
#endif
static bool
IsMP3SupportedType(const nsACString& aType,
const nsAString& aCodecs = EmptyString())
{
return aType.EqualsASCII("audio/mpeg") && MP3Decoder::IsEnabled();
}
#ifdef MOZ_APPLEMEDIA
static const char * const gAppleMP3Types[] = {
@ -444,6 +453,10 @@ DecoderTraits::CanHandleMediaType(const char* aMIMEType,
return aHaveRequestedCodecs ? CANPLAY_YES : CANPLAY_MAYBE;
}
#endif
if (IsMP3SupportedType(nsDependentCString(aMIMEType),
aRequestedCodecs)) {
return aHaveRequestedCodecs ? CANPLAY_YES : CANPLAY_MAYBE;
}
#ifdef MOZ_GSTREAMER
if (GStreamerDecoder::CanHandleMediaType(nsDependentCString(aMIMEType),
aHaveRequestedCodecs ? &aRequestedCodecs : nullptr)) {
@ -538,6 +551,10 @@ InstantiateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner)
return decoder.forget();
}
#endif
if (IsMP3SupportedType(aType)) {
decoder = new MP3Decoder();
return decoder.forget();
}
#ifdef MOZ_GSTREAMER
if (IsGStreamerSupportedType(aType)) {
decoder = new GStreamerDecoder();
@ -666,6 +683,9 @@ MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, Abstrac
static_cast<MediaDecoderReader*>(new MP4Reader(aDecoder));
} else
#endif
if (IsMP3SupportedType(aType)) {
decoderReader = new MediaFormatReader(aDecoder, new mp3::MP3Demuxer(aDecoder->GetResource()));
} else
#ifdef MOZ_GSTREAMER
if (IsGStreamerSupportedType(aType)) {
decoderReader = new GStreamerReader(aDecoder);
@ -760,6 +780,7 @@ bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType)
#ifdef MOZ_FMP4
IsMP4SupportedType(aType) ||
#endif
IsMP3SupportedType(aType) ||
#ifdef MOZ_WMF
IsWMFSupportedType(aType) ||
#endif

45
dom/media/MP3Decoder.cpp Normal file
View File

@ -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

26
dom/media/MP3Decoder.h Normal file
View File

@ -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

View File

@ -4,20 +4,98 @@
* 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 "mp4_demuxer/MP3TrackDemuxer.h"
#include "MP3Demuxer.h"
#include <inttypes.h>
#include <algorithm>
#include "mozilla/Assertions.h"
#include "mozilla/Endian.h"
#include "VideoUtils.h"
#include "TimeUnits.h"
namespace mp4_demuxer {
using media::TimeUnit;
using media::TimeIntervals;
namespace mozilla {
namespace mp3 {
// 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),
mOffset(0),
mFirstFrameOffset(0),
mStreamLength(-1),
mNumParsedFrames(0),
mFrameIndex(0),
mTotalFrameLen(0),
@ -28,128 +106,199 @@ MP3Demuxer::MP3Demuxer(Stream* aSource)
}
bool
MP3Demuxer::Init() {
if (!mSource->Length(&mStreamLength)) {
// Length is unknown.
mStreamLength = -1;
MP3TrackDemuxer::Init() {
FastSeek(TimeUnit());
// Read the first frame to fetch sample rate and other meta data.
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
const FrameParser::Frame&
MP3Demuxer::LastFrame() const {
MP3TrackDemuxer::LastFrame() const {
return mParser.PrevFrame();
}
nsRefPtr<MediaRawData>
MP3TrackDemuxer::DemuxSample() {
return GetNextFrame(FindNextFrame());
}
#endif
const ID3Parser::ID3Header&
MP3Demuxer::ID3Header() const {
MP3TrackDemuxer::ID3Header() const {
return mParser.ID3Header();
}
const FrameParser::VBRHeader&
MP3Demuxer::VBRInfo() const {
MP3TrackDemuxer::VBRInfo() const {
return mParser.VBRInfo();
}
void
MP3Demuxer::Seek(Microseconds aTime) {
SlowSeek(aTime);
UniquePtr<TrackInfo>
MP3TrackDemuxer::GetInfo() const {
return mInfo->Clone();
}
void
MP3Demuxer::FastSeek(Microseconds aTime) {
if (!aTime) {
nsRefPtr<MP3TrackDemuxer::SeekPromise>
MP3TrackDemuxer::Seek(TimeUnit 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.
mOffset = mFirstFrameOffset;
mFrameIndex = 0;
mParser.FinishParsing();
return;
return TimeUnit();
}
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;
mOffset = mFirstFrameOffset + numFrames * AverageFrameLength();
mFrameIndex = numFrames;
mParser.FinishParsing();
return Duration(mFrameIndex);
}
void
MP3Demuxer::SlowSeek(Microseconds aTime) {
if (!aTime) {
FastSeek(aTime);
return;
TimeUnit
MP3TrackDemuxer::ScanUntil(TimeUnit aTime) {
if (!aTime.ToMicroseconds()) {
return FastSeek(aTime);
}
if (Duration(mFrameIndex) > aTime) {
FastSeek(aTime);
}
nsRefPtr<MediaRawData> frameData(GetNext());
while (frameData && Duration(mFrameIndex + 1) < aTime) {
frameData = GetNext();
MediaByteRange nextRange = FindNextFrame();
while (SkipNextFrame(nextRange) && Duration(mFrameIndex + 1) < aTime) {
nextRange = FindNextFrame();
}
return Duration(mFrameIndex);
}
already_AddRefed<MediaRawData>
MP3Demuxer::DemuxSample() {
nsRefPtr<MediaRawData> sample(GetNext());
if (!sample) {
return nullptr;
nsRefPtr<MP3TrackDemuxer::SamplesPromise>
MP3TrackDemuxer::GetSamples(int32_t aNumSamples) {
if (!aNumSamples) {
return SamplesPromise::CreateAndReject(
DemuxerFailureReason::DEMUXER_ERROR, __func__);
}
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
MP3Demuxer::GetNextKeyframeTime() {
return -1;
void
MP3TrackDemuxer::Reset() {
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
MP3Demuxer::StreamLength() const {
return mStreamLength;
MP3TrackDemuxer::GetResourceOffset() const {
return mOffset;
}
TimeIntervals
MP3TrackDemuxer::GetBuffered() {
// TODO: bug 1169485.
NS_WARNING("Unimplemented function GetBuffered");
return TimeIntervals();
}
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) {
return -1;
return TimeUnit::FromMicroseconds(-1);
}
const int64_t streamLen = StreamLength();
// Assume we know the exact number of frames from the VBR header.
int64_t numFrames = mParser.VBRInfo().NumFrames();
if (numFrames < 0) {
if (mStreamLength < 0) {
if (streamLen < 0) {
// Unknown length, we can't estimate duration.
return -1;
return TimeUnit::FromMicroseconds(-1);
}
numFrames = (mStreamLength - mFirstFrameOffset) / AverageFrameLength();
numFrames = (streamLen - mFirstFrameOffset) / AverageFrameLength();
}
return Duration(numFrames);
}
int64_t
MP3Demuxer::Duration(int64_t aNumFrames) const {
TimeUnit
MP3TrackDemuxer::Duration(int64_t aNumFrames) const {
if (!mSamplesPerSecond) {
return -1;
return TimeUnit::FromMicroseconds(-1);
}
const double usPerFrame = USECS_PER_S * mSamplesPerFrame / mSamplesPerSecond;
return aNumFrames * usPerFrame;
return TimeUnit::FromMicroseconds(aNumFrames * usPerFrame);
}
already_AddRefed<mozilla::MediaRawData>
MP3Demuxer::GetNext() {
MediaByteRange
MP3TrackDemuxer::FindNextFrame() {
static const int BUFFER_SIZE = 4096;
uint8_t buffer[BUFFER_SIZE];
uint32_t read = 0;
int32_t read = 0;
const uint8_t* frameBeg = nullptr;
const uint8_t* bufferEnd = nullptr;
@ -162,47 +311,53 @@ MP3Demuxer::GetNext() {
}
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;
}
// Valid frame header was fully parsed, let's read the whole frame.
const int32_t frameLen = mParser.CurrentFrame().Length();
nsRefPtr<MediaRawData> frame = new MediaRawData();
frame->mOffset = mOffset - (bufferEnd - frameBeg) + 1;
frame->mOffset = aRange.mStart;
nsAutoPtr<MediaRawDataWriter> frameWriter(frame->CreateWriter());
if (!frameWriter->SetSize(frameLen)) {
if (!frameWriter->SetSize(aRange.Length())) {
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;
}
// Prevent overflow.
if (mTotalFrameLen + frameLen < 0) {
// These variables have a linear dependency and are only used to derive the
// average frame length.
mTotalFrameLen /= 2;
mNumParsedFrames /= 2;
}
UpdateState(aRange);
// Full frame parsed, move offset to its end.
mOffset = frame->mOffset + frame->mSize;
MOZ_ASSERT(mOffset > frame->mOffset);
frame->mTime = Duration(mFrameIndex - 1).ToMicroseconds();
frame->mDuration = Duration(1).ToMicroseconds();
mTotalFrameLen += frameLen;
mSamplesPerFrame = mParser.CurrentFrame().Header().SamplesPerFrame();
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);
MOZ_ASSERT(frame->mTime >= 0);
MOZ_ASSERT(frame->mDuration > 0);
if (mNumParsedFrames == 1) {
// First frame parsed, let's read VBR info if available.
@ -211,22 +366,50 @@ MP3Demuxer::GetNext() {
mFirstFrameOffset = frame->mOffset;
}
// Prepare the parser for the next frame parsing session.
mParser.FinishParsing();
return frame.forget();
}
uint32_t
MP3Demuxer::Read(uint8_t* aBuffer, uint32_t aOffset, uint32_t aSize) {
size_t read = 0;
if (!mSource->ReadAt(aOffset, aBuffer, aSize, &read)) {
read = 0;
void
MP3TrackDemuxer::UpdateState(const MediaByteRange& aRange) {
// Prevent overflow.
if (mTotalFrameLen + aRange.Length() < mTotalFrameLen) {
// 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
MP3Demuxer::AverageFrameLength() const {
MP3TrackDemuxer::AverageFrameLength() const {
if (!mNumParsedFrames) {
return 0.0;
}
@ -752,4 +935,5 @@ ID3Parser::ID3Header::Update(uint8_t c) {
return IsValid(mPos++);
}
} // namespace mp4_demuxer
} // namespace mp3
} // namespace mozilla

View File

@ -2,13 +2,39 @@
* 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 MP3_TRACK_DEMUXER_H_
#define MP3_TRACK_DEMUXER_H_
#ifndef MP3_DEMUXER_H_
#define MP3_DEMUXER_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.
// 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
// MPEG streams.
class MP3Demuxer : public mozilla::TrackDemuxer {
class MP3TrackDemuxer : public MediaTrackDemuxer {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MP3Demuxer);
// Constructor, expecing a valid media resource.
explicit MP3TrackDemuxer(MediaResource* aSource);
// Constructor, expecing a valid stream source.
explicit MP3Demuxer(Stream* aSource);
// Initializes the demuxer by reading the expected stream length, if
// available. Optional, but recommended.
// Currently always returns true.
// Initializes the track demuxer by reading the first frame for meta data.
// Returns initialization success state.
bool Init();
// Returns the total stream length if known, -1 otherwise.
int64_t StreamLength() const;
// Returns the estimated stream duration in microseconds, or -1 if no
// estimation available.
int64_t Duration() const;
// Returns the estimated stream duration, or a 0-duration if unknown.
media::TimeUnit Duration() const;
// Returns the estimated duration up to the given frames number in microseconds,
// or -1 if no estimation available.
int64_t Duration(int64_t aNumFrames) const;
// Returns the estimated duration up to the given frame number,
// or a 0-duration if unknown.
media::TimeUnit Duration(int64_t aNumFrames) const;
#ifdef ENABLE_TESTS
const FrameParser::Frame& LastFrame() const;
nsRefPtr<MediaRawData> DemuxSample();
#endif
const ID3Parser::ID3Header& ID3Header() const;
const FrameParser::VBRHeader& VBRInfo() const;
// TrackDemuxer interface.
virtual void Seek(Microseconds aTime) override;
virtual already_AddRefed<mozilla::MediaRawData> DemuxSample() override;
virtual Microseconds GetNextKeyframeTime() override;
void UpdateConfig(mozilla::AudioInfo& aConfig) {
aConfig.mRate = mSamplesPerSecond;
aConfig.mChannels = mChannels;
aConfig.mBitDepth = 16;
aConfig.mMimeType = "audio/mpeg";
}
// MediaTrackDemuxer interface.
UniquePtr<TrackInfo> GetInfo() const override;
nsRefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
nsRefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
void Reset() override;
nsRefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(
media::TimeUnit aTimeThreshold) override;
int64_t GetResourceOffset() const override;
media::TimeIntervals GetBuffered() override;
int64_t GetEvictionOffset(media::TimeUnit aTime) override;
private:
// Destructor.
~MP3Demuxer() {}
~MP3TrackDemuxer() {}
// Fast approximate seeking to given time.
void FastSeek(Microseconds aTime);
media::TimeUnit FastSeek(media::TimeUnit aTime);
// Slow, more accurate approximate seeking to given time.
void SlowSeek(Microseconds aTime);
// Seeks by scanning the stream up to the given time for more accurate results.
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.
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.
// 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.
double AverageFrameLength() const;
// The (hopefully) MPEG source stream.
nsRefPtr<Stream> mSource;
// The (hopefully) MPEG resource.
nsRefPtr<MediaResource> mSource;
// MPEG frame parser used to detect frames and extract side info.
FrameParser mParser;
// 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.
uint64_t mFirstFrameOffset;
// Total expected stream length, if available, or -1 otherwise.
int64_t mStreamLength;
int64_t mFirstFrameOffset;
// Total parsed frames.
int64_t mNumParsedFrames;
uint64_t mNumParsedFrames;
// Current frame index.
int64_t mFrameIndex;
// 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.
int32_t mSamplesPerFrame;
@ -373,8 +404,12 @@ private:
// Channel count derived from frame headers or 0 if none available.
int32_t mChannels;
// Audio track config info.
UniquePtr<AudioInfo> mInfo;
};
}
} // namespace mp3
} // namespace mozilla
#endif

View File

@ -156,8 +156,7 @@ public:
return aByteRange.mStart >= mStart && aByteRange.mEnd <= mEnd;
}
MediaByteRange Extents(const MediaByteRange& aByteRange) const
{
MediaByteRange Extents(const MediaByteRange& aByteRange) const {
if (IsNull()) {
return aByteRange;
}
@ -165,7 +164,9 @@ public:
std::max(mEnd, aByteRange.mEnd));
}
int64_t Length() { return mEnd - mStart; }
int64_t Length() const {
return mEnd - mStart;
}
int64_t mStart, mEnd;
};

View File

@ -35,7 +35,7 @@ LogToConsole(const nsAString& aMsg)
}
void
DetailedPromise::MaybeReject(nsresult aArg, const nsCString& aReason)
DetailedPromise::MaybeReject(nsresult aArg, const nsACString& aReason)
{
mResponded = true;
@ -47,7 +47,7 @@ DetailedPromise::MaybeReject(nsresult aArg, const nsCString& aReason)
}
void
DetailedPromise::MaybeReject(ErrorResult&, const nsCString& aReason)
DetailedPromise::MaybeReject(ErrorResult&, const nsACString& aReason)
{
NS_NOTREACHED("nsresult expected in MaybeReject()");
}

View File

@ -31,10 +31,10 @@ public:
}
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&, const nsCString& aReason);
void MaybeReject(ErrorResult&, const nsACString& aReason);
private:
explicit DetailedPromise(nsIGlobalObject* aGlobal);

View File

@ -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

View File

@ -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

View File

@ -10,6 +10,9 @@
#include "nsIObserverService.h"
#include "mozilla/Services.h"
#include "mozilla/DetailedPromise.h"
#ifdef XP_WIN
#include "mozilla/WindowsVersion.h"
#endif
namespace mozilla {
namespace dom {
@ -44,6 +47,9 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
MediaKeySystemAccessManager::MediaKeySystemAccessManager(nsPIDOMWindow* aWindow)
: mWindow(aWindow)
, mAddedObservers(false)
#ifdef XP_WIN
, mTrialCreator(new GMPVideoDecoderTrialCreator())
#endif
{
}
@ -144,6 +150,16 @@ MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
if (aOptions.IsEmpty() ||
MediaKeySystemAccess::IsSupported(keySystem, aOptions)) {
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);
return;
}

View File

@ -6,6 +6,9 @@
#define mozilla_dom_MediaKeySystemAccessManager_h
#include "mozilla/dom/MediaKeySystemAccess.h"
#ifdef XP_WIN
#include "mozilla/dom/GMPVideoDecoderTrialCreator.h"
#endif
#include "nsIObserver.h"
#include "nsCycleCollectionParticipant.h"
#include "nsISupportsImpl.h"
@ -15,6 +18,7 @@ namespace mozilla {
namespace dom {
class DetailedPromise;
class TestGMPVideoDecoder;
class MediaKeySystemAccessManager final : public nsIObserver
{
@ -34,9 +38,9 @@ public:
struct PendingRequest {
PendingRequest(DetailedPromise* aPromise,
const nsAString& aKeySystem,
const Sequence<MediaKeySystemOptions>& aOptions,
nsITimer* aTimer);
const nsAString& aKeySystem,
const Sequence<MediaKeySystemOptions>& aOptions,
nsITimer* aTimer);
PendingRequest(const PendingRequest& aOther);
~PendingRequest();
void CancelTimer();
@ -74,6 +78,10 @@ private:
nsCOMPtr<nsPIDOMWindow> mWindow;
bool mAddedObservers;
#ifdef XP_WIN
nsRefPtr<GMPVideoDecoderTrialCreator> mTrialCreator;
#endif
};
} // namespace dom

View File

@ -6,14 +6,12 @@
#include "mozilla/dom/MediaKeys.h"
#include "GMPService.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/dom/HTMLMediaElement.h"
#include "mozilla/dom/MediaKeysBinding.h"
#include "mozilla/dom/MediaKeyMessageEvent.h"
#include "mozilla/dom/MediaKeyError.h"
#include "mozilla/dom/MediaKeySession.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/PluginCrashedEvent.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/CDMProxy.h"
#include "mozilla/EMEUtils.h"
@ -383,83 +381,6 @@ MediaKeys::Init(ErrorResult& aRv)
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
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)) {
return;
}
nsCOMPtr<nsIDocument> doc = mParent->GetExtantDoc();
if (NS_WARN_IF(!doc)) {
return;
}
service->AddPluginCrashCallback(new CrashHandler(aPluginId, mParent, doc));
service->AddPluginCrashedEventTarget(aPluginId, mParent);
EME_LOG("MediaKeys[%p]::OnCDMCreated() registered crash handler for pluginId '%i'",
this, aPluginId);
}

View File

@ -39,6 +39,14 @@ UNIFIED_SOURCES += [
'MediaKeySystemAccessManager.cpp',
]
if CONFIG['OS_ARCH'] == 'WINNT':
UNIFIED_SOURCES += [
'GMPVideoDecoderTrialCreator.cpp',
]
EXPORTS.mozilla.dom += [
'GMPVideoDecoderTrialCreator.h',
]
FINAL_LIBRARY = 'xul'
FAIL_ON_WARNINGS = True

View File

@ -1,4 +1,4 @@
Name: fakeopenh264
Description: Fake GMP Plugin
Version: 1.0
APIs: encode-video[h264], decode-video[h264]
APIs: encode-video[h264:fake], decode-video[h264:fake]

View File

@ -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"

View File

@ -8,7 +8,7 @@
FINAL_TARGET = 'dist/bin/gmp-fakeopenh264/1.0'
SOURCES += [
'../gmp-plugin/gmp-fake.cpp',
'gmp-fake-openh264.cpp',
]
SharedLibrary("fakeopenh264")

View File

@ -1,5 +1,5 @@
Name: fake
Description: Fake GMP Plugin
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

View File

@ -34,21 +34,13 @@
*/
#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"
@ -62,337 +54,8 @@
#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
@ -404,11 +67,10 @@ extern "C" {
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;
// Note: Deliberately advertise in our .info file that we support
// video-decode, but we fail the "get" call here to simulate what
// happens when decoder init fails.
return GMPGenericErr;
#if defined(GMP_FAKE_SUPPORT_DECRYPT)
} else if (!strcmp (aApiName, GMP_API_DECRYPTOR)) {
*aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI));

View File

@ -16,25 +16,19 @@ GMPDecryptorParent::GMPDecryptorParent(GMPContentParent* aPlugin)
, mShuttingDown(false)
, mActorDestroyed(false)
, mPlugin(aPlugin)
, mPluginId(aPlugin->GetPluginId())
, mCallback(nullptr)
#ifdef DEBUG
, mGMPThread(aPlugin->GMPThread())
#endif
{
MOZ_ASSERT(mPlugin && mGMPThread);
mPluginId = aPlugin->GetPluginId();
}
GMPDecryptorParent::~GMPDecryptorParent()
{
}
const uint32_t
GMPDecryptorParent::GetPluginId() const
{
return mPluginId;
}
nsresult
GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback)
{

View File

@ -29,7 +29,7 @@ public:
// GMPDecryptorProxy
virtual const uint32_t GetPluginId() const override;
virtual const uint32_t GetPluginId() const override { return mPluginId; }
virtual nsresult Init(GMPDecryptorProxyCallback* aCallback) override;

View File

@ -64,6 +64,7 @@ GMPParent::GMPParent()
, mAsyncShutdownRequired(false)
, mAsyncShutdownInProgress(false)
, mChildPid(0)
, mHoldingSelfRef(false)
{
LOGD("GMPParent ctor");
mPluginId = GeckoChildProcessHost::GetUniqueID();
@ -74,6 +75,8 @@ GMPParent::~GMPParent()
// Can't Close or Destroy the process here, since destruction is MainThread only
MOZ_ASSERT(NS_IsMainThread());
LOGD("GMPParent dtor");
MOZ_ASSERT(!mProcess);
}
nsresult
@ -178,6 +181,13 @@ GMPParent::LoadProcess()
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;
}
@ -408,6 +418,10 @@ GMPParent::DeleteProcess()
new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId)),
NS_DISPATCH_NORMAL);
if (mHoldingSelfRef) {
Release();
mHoldingSelfRef = false;
}
}
GMPState

View File

@ -218,6 +218,12 @@ private:
bool mAsyncShutdownInProgress;
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

View File

@ -37,6 +37,9 @@
#include "nsIFile.h"
#include "nsISimpleEnumerator.h"
#include "mozilla/dom/PluginCrashedEvent.h"
#include "mozilla/EventDispatcher.h"
namespace mozilla {
#ifdef LOG
@ -160,33 +163,115 @@ GeckoMediaPluginService::RemoveObsoletePluginCrashCallbacks()
{
MOZ_ASSERT(NS_IsMainThread());
for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
nsRefPtr<GMPCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
if (!callback->IsStillValid()) {
LOGD(("%s::%s - Removing obsolete callback for pluginId %i",
__CLASS__, __FUNCTION__, callback->PluginId()));
__CLASS__, __FUNCTION__, callback->GetPluginId()));
mPluginCrashCallbacks.RemoveElementAt(i - 1);
}
}
}
void
GeckoMediaPluginService::AddPluginCrashCallback(
nsRefPtr<PluginCrashCallback> aPluginCrashCallback)
GeckoMediaPluginService::GMPCrashCallback::GMPCrashCallback(const uint32_t aPluginId,
nsPIDOMWindow* aParentWindow,
nsIDocument* aDocument)
: mPluginId(aPluginId)
, mParentWindowWeakPtr(do_GetWeakReference(aParentWindow))
, mDocumentWeakPtr(do_GetWeakReference(aDocument))
{
RemoveObsoletePluginCrashCallbacks();
mPluginCrashCallbacks.AppendElement(aPluginCrashCallback);
MOZ_ASSERT(NS_IsMainThread());
}
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();
for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
if (callback->PluginId() == aPluginId) {
mPluginCrashCallbacks.RemoveElementAt(i - 1);
// If the plugin with that ID has already crashed without being handled,
// just run the handler now.
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
@ -195,20 +280,21 @@ GeckoMediaPluginService::RunPluginCrashCallbacks(const uint32_t aPluginId,
{
MOZ_ASSERT(NS_IsMainThread());
LOGD(("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId));
RemoveObsoletePluginCrashCallbacks();
for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
const uint32_t callbackPluginId = callback->PluginId();
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) {
nsRefPtr<GMPCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
if (callback->GetPluginId() == aPluginId) {
LOGD(("%s::%s(%i) - Running #%u",
__CLASS__, __FUNCTION__, aPluginId, i - 1));
callback->Run(aPluginName);
mPluginCrashCallbacks.RemoveElementAt(i - 1);
}
}
mPluginCrashes.AppendElement(PluginCrash(aPluginId, aPluginName));
if (mPluginCrashes.Length() > MAX_PLUGIN_CRASHES) {
mPluginCrashes.RemoveElementAt(0);
}
}
nsresult

View File

@ -16,6 +16,9 @@
#include "nsCOMPtr.h"
#include "nsIThread.h"
#include "nsThreadUtils.h"
#include "nsPIDOMWindow.h"
#include "nsIDocument.h"
#include "nsIWeakReference.h"
template <class> struct already_AddRefed;
@ -62,37 +65,21 @@ public:
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,
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:
GeckoMediaPluginService();
virtual ~GeckoMediaPluginService();
void RemoveObsoletePluginCrashCallbacks(); // Called from add/run.
virtual void InitializePlugins() = 0;
virtual bool GetContentParentFrom(const nsACString& aNodeId,
const nsCString& aAPI,
@ -102,14 +89,54 @@ protected:
nsresult GMPDispatch(nsIRunnable* event, uint32_t flags = NS_DISPATCH_NORMAL);
void ShutdownGMPThread();
protected:
Mutex mMutex; // Protects mGMPThread and mGMPThreadShutdown and some members
// in derived classes.
nsCOMPtr<nsIThread> mGMPThread;
bool mGMPThreadShutdown;
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

View File

@ -46,6 +46,7 @@ GMPVideoDecoderParent::GMPVideoDecoderParent(GMPContentParent* aPlugin)
, mPlugin(aPlugin)
, mCallback(nullptr)
, mVideoHost(this)
, mPluginId(aPlugin->GetPluginId())
{
MOZ_ASSERT(mPlugin);
}
@ -83,6 +84,10 @@ GMPVideoDecoderParent::InitDecode(const GMPVideoCodec& aCodecSettings,
GMPVideoDecoderCallbackProxy* aCallback,
int32_t aCoreCount)
{
if (mActorDestroyed) {
NS_WARNING("Trying to use a destroyed GMP video decoder!");
return NS_ERROR_FAILURE;
}
if (mIsOpen) {
NS_WARNING("Trying to re-init an in-use GMP video decoder!");
return NS_ERROR_FAILURE;

View File

@ -44,7 +44,7 @@ public:
int64_t aRenderTimeMs = -1) override;
virtual nsresult Reset() 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;
// GMPSharedMemManager
@ -85,6 +85,7 @@ private:
nsRefPtr<GMPContentParent> mPlugin;
GMPVideoDecoderCallbackProxy* mCallback;
GMPVideoHostImpl mVideoHost;
const uint32_t mPluginId;
};
} // namespace gmp

View File

@ -44,7 +44,7 @@ public:
int64_t aRenderTimeMs = -1) = 0;
virtual nsresult Reset() = 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
// interface/codec.

View File

@ -52,7 +52,8 @@ GMPVideoEncoderParent::GMPVideoEncoderParent(GMPContentParent *aPlugin)
mActorDestroyed(false),
mPlugin(aPlugin),
mCallback(nullptr),
mVideoHost(this)
mVideoHost(this),
mPluginId(aPlugin->GetPluginId())
{
MOZ_ASSERT(mPlugin);
@ -211,12 +212,6 @@ GMPVideoEncoderParent::SetPeriodicKeyFrames(bool aEnable)
return GMPNoErr;
}
const uint32_t
GMPVideoEncoderParent::ParentID()
{
return mPlugin ? mPlugin->GetPluginId() : 0;
}
// Note: Consider keeping ActorDestroy sync'd up when making changes here.
void
GMPVideoEncoderParent::Shutdown()

View File

@ -45,7 +45,7 @@ public:
virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) override;
virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) override;
virtual GMPErr SetPeriodicKeyFrames(bool aEnable) override;
virtual const uint32_t ParentID() override;
virtual const uint32_t GetPluginId() const override { return mPluginId; }
// GMPSharedMemManager
virtual bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override
@ -82,6 +82,7 @@ private:
GMPVideoEncoderCallbackProxy* mCallback;
GMPVideoHostImpl mVideoHost;
nsCOMPtr<nsIThread> mEncodedThread;
const uint32_t mPluginId;
};
} // namespace gmp

View File

@ -46,7 +46,7 @@ public:
virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0;
virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 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
// interface/codec.

View File

@ -13,6 +13,11 @@
#include "GMPVideoEncoderProxy.h"
#include "GMPDecryptorProxy.h"
#include "GMPServiceParent.h"
#ifdef XP_WIN
#include "GMPVideoDecoderTrialCreator.h"
#include "mozilla/dom/MediaKeySystemAccess.h"
#include "mozilla/Monitor.h"
#endif
#include "nsAppDirectoryServiceDefs.h"
#include "nsIFile.h"
#include "nsISimpleEnumerator.h"
@ -82,6 +87,7 @@ protected:
{
nsTArray<nsCString> tags;
tags.AppendElement(NS_LITERAL_CSTRING("h264"));
tags.AppendElement(NS_LITERAL_CSTRING("fake"));
nsRefPtr<GeckoMediaPluginService> service =
GeckoMediaPluginService::GetGeckoMediaPluginService();
@ -174,7 +180,7 @@ private:
EXPECT_TRUE(aGMP);
if (aGMP) {
EXPECT_TRUE(mGMP &&
(mGMP->ParentID() == aGMP->ParentID()) == mShouldBeEqual);
(mGMP->GetPluginId() == aGMP->GetPluginId()) == mShouldBeEqual);
}
if (mGMP) {
mGMP->Close();
@ -1344,7 +1350,12 @@ class GMPStorageTest : public GMPDecryptorProxyCallback
virtual void Decrypted(uint32_t aId,
GMPErr aResult,
const nsTArray<uint8_t>& aDecryptedData) override { }
virtual void Terminated() override { }
virtual void Terminated() override {
if (mDecryptor) {
mDecryptor->Close();
mDecryptor = nullptr;
}
}
private:
~GMPStorageTest() { }
@ -1475,3 +1486,65 @@ TEST(GeckoMediaPlugins, GMPStorageLongRecordNames) {
nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
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

View File

@ -11,8 +11,9 @@
#include "mozilla/Services.h"
#include "nsDirectoryServiceDefs.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_NEW_VERSION NS_LITERAL_STRING("1.1")
@ -414,6 +415,10 @@ void
GMPRemoveTest::Terminated()
{
mIsTerminated = true;
if (mDecoder) {
mDecoder->Close();
mDecoder = nullptr;
}
}
void
@ -435,6 +440,17 @@ GMPRemoveTest::GeneratePlugin()
rv = origDir->Append(GMP_OLD_VERSION);
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);
EXPECT_OK(rv);

View File

@ -6,13 +6,11 @@
#include <gtest/gtest.h>
#include <vector>
#include "mp4_demuxer/mp4_demuxer.h"
#include "mp4_demuxer/MP3TrackDemuxer.h"
#include "MP4Stream.h"
#include "MP3Demuxer.h"
#include "mozilla/ArrayUtils.h"
#include "MockMediaResource.h"
using namespace mp4_demuxer;
using namespace mp3;
struct MP3Resource {
const char* mFilePath;
@ -40,7 +38,7 @@ struct MP3Resource {
// The first n frame offsets.
std::vector<int32_t> mSyncOffsets;
nsRefPtr<MockMediaResource> mResource;
nsRefPtr<MP3Demuxer> mDemuxer;
nsRefPtr<MP3TrackDemuxer> mDemuxer;
};
class MP3DemuxerTest : public ::testing::Test {
@ -98,7 +96,7 @@ protected:
for (auto& target: mTargets) {
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_TRUE(target.mDemuxer->Init());
@ -208,7 +206,7 @@ TEST_F(MP3DemuxerTest, Duration) {
EXPECT_EQ(static_cast<int64_t>(target.mFileSize), target.mDemuxer->StreamLength());
while (frameData) {
EXPECT_NEAR(target.mDuration, target.mDemuxer->Duration(),
EXPECT_NEAR(target.mDuration, target.mDemuxer->Duration().ToMicroseconds(),
target.mDurationError * target.mDuration);
frameData = target.mDemuxer->DemuxSample();

View File

@ -101,7 +101,7 @@ public:
#endif
#if defined(DEBUG)
void Dump(const char* aPath);
void Dump(const char* aPath) override;
#endif
private:

View File

@ -137,6 +137,8 @@ EXPORTS += [
'MediaTimer.h',
'MediaTrack.h',
'MediaTrackList.h',
'MP3Decoder.h',
'MP3Demuxer.h',
'MP3FrameParser.h',
'nsIDocumentActivity.h',
'RtspMediaResource.h',
@ -234,6 +236,8 @@ UNIFIED_SOURCES += [
'MediaTimer.cpp',
'MediaTrack.cpp',
'MediaTrackList.cpp',
'MP3Decoder.cpp',
'MP3Demuxer.cpp',
'MP3FrameParser.cpp',
'RTCIdentityProviderRegistrar.cpp',
'RtspMediaResource.cpp',

View File

@ -458,6 +458,8 @@ MediaCodecReader::DecodeAudioDataTask()
}
} else if (AudioQueue().AtEndOfStream()) {
mAudioTrack.mAudioPromise.Reject(END_OF_STREAM, __func__);
} else if (AudioQueue().GetSize() == 0) {
DispatchAudioTask();
}
}
@ -466,6 +468,9 @@ MediaCodecReader::DecodeVideoFrameTask(int64_t aTimeThreshold)
{
DecodeVideoFrameSync(aTimeThreshold);
MonitorAutoLock al(mVideoTrack.mTrackMonitor);
if (mVideoTrack.mVideoPromise.IsEmpty()) {
return;
}
if (VideoQueue().GetSize() > 0) {
nsRefPtr<VideoData> v = VideoQueue().PopFront();
if (v) {
@ -477,6 +482,8 @@ MediaCodecReader::DecodeVideoFrameTask(int64_t aTimeThreshold)
}
} else if (VideoQueue().AtEndOfStream()) {
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) {
// Notify mDecoder that we have parsed a video frame.
if (&aTrack == &mVideoTrack) {
if (aTrack.mType == Track::kVideo) {
mDecoder->NotifyDecodedFrames(1, 0, 0);
}
if (!IsValidTimestampUs(aThreshold) || info.mTimeUs >= aThreshold) {
@ -1905,6 +1912,7 @@ MediaCodecReader::EnsureCodecFormatParsed(Track& aTrack)
}
FillCodecInputData(aTrack);
}
aTrack.mCodec->releaseOutputBuffer(index);
return aTrack.mCodec->getOutputFormat(&format) == OK;
}

View File

@ -28,79 +28,6 @@ const signalingStateTransitions = {
"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
// Note, we don't bother removing the fmtp lines, which makes a good test
// for some SDP parsing issues.
@ -768,7 +695,7 @@ function PeerConnectionWrapper(label, configuration, h264) {
this.constraints = [ ];
this.offerOptions = {};
this.streams = [ ];
this.mediaCheckers = [ ];
this.mediaElements = [ ];
this.dataChannels = [ ];
@ -918,7 +845,7 @@ PeerConnectionWrapper.prototype = {
}
var element = createMediaElement(type, this.label + '_' + side + this.streams.length);
this.mediaCheckers.push(new MediaElementChecker(element));
this.mediaElements.push(element);
element.mozSrcObject = stream;
element.play();
@ -1528,11 +1455,93 @@ PeerConnectionWrapper.prototype = {
},
/**
* Check that media flow is present on all media elements involved in this
* test by waiting for confirmation that media flow is present.
* Check that media flow is present on the given media element by waiting for
* 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() {
return Promise.all(this.mediaCheckers.map(checker => checker.waitForMediaFlow()));
waitForMediaElementFlow : function(element) {
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))));
},
/**

View File

@ -446,13 +446,14 @@ var commandsPeerConnectionOfferAnswer = [
return test.pcRemote.checkMediaTracks();
},
function PC_LOCAL_CHECK_MEDIA_FLOW_PRESENT(test) {
return test.pcLocal.checkMediaFlowPresent();
function PC_LOCAL_WAIT_FOR_MEDIA_FLOW(test) {
return test.pcLocal.waitForMediaFlow();
},
function PC_REMOTE_CHECK_MEDIA_FLOW_PRESENT(test) {
return test.pcRemote.checkMediaFlowPresent();
function PC_REMOTE_WAIT_FOR_MEDIA_FLOW(test) {
return test.pcRemote.waitForMediaFlow();
},
function PC_LOCAL_CHECK_STATS(test) {
return test.pcLocal.getStats(null).then(stats => {
test.pcLocal.checkStats(stats, test.steeplechase);

View File

@ -25,22 +25,14 @@ runNetworkTest(function() {
var test = new PeerConnectionTest();
test.setOfferOptions({ offerToReceiveVideo: 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) {
return metadataLoaded
.then(() => {
var stream = v1.mozCaptureStreamUntilEnded();
is(stream.getTracks().length, 2, "Captured stream has 2 tracks");
stream.getTracks().forEach(track => {
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;
test.pcLocal.attachMedia(stream, "audiovideo", "local");
});
}
]);

View File

@ -66,8 +66,8 @@
function PC_REMOTE_VIDEOONLY_REPLACE_VIDEOTRACK(test) {
return replacetest(test.pcRemote);
},
function PC_LOCAL_NEW_VIDEOTRACK_CHECK_MEDIA_FLOW_PRESENT(test) {
return test.pcLocal.checkMediaFlowPresent();
function PC_LOCAL_NEW_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW(test) {
return test.pcLocal.waitForMediaFlow();
}
]);
@ -77,14 +77,14 @@
function PC_LOCAL_AUDIOVIDEO_REPLACE_VIDEOTRACK_1(test) {
return replacetest(test.pcLocal);
},
function PC_REMOTE_NEW_VIDEOTRACK_CHECK_MEDIA_FLOW_PRESENT_1(test) {
return test.pcRemote.checkMediaFlowPresent();
function PC_REMOTE_NEW_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW_1(test) {
return test.pcRemote.waitForMediaFlow();
},
function PC_LOCAL_AUDIOVIDEO_REPLACE_VIDEOTRACK_2(test) {
return replacetest(test.pcLocal);
},
function PC_REMOTE_NEW_VIDEOTRACK_CHECK_MEDIA_FLOW_PRESENT_2(test) {
return test.pcRemote.checkMediaFlowPresent();
function PC_REMOTE_NEW_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW_2(test) {
return test.pcRemote.waitForMediaFlow();
}
]);
@ -96,8 +96,8 @@
return sender.replaceTrack(sender.track)
.then(() => ok(true, "replacing with itself should succeed"));
},
function PC_REMOTE_NEW_SAME_VIDEOTRACK_CHECK_MEDIA_FLOW_PRESENT(test) {
return test.pcRemote.checkMediaFlowPresent();
function PC_REMOTE_NEW_SAME_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW(test) {
return test.pcRemote.waitForMediaFlow();
}
]);

View File

@ -13,7 +13,8 @@
SimpleTest.waitForExplicitFinish();
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) {
for (var i = 0; i < navigator.plugins.length; i++) {
@ -36,38 +37,37 @@
}
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");
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'");
ok(navigator.plugins["Second Test Plug-in"], "Should have queried a hidden plugin named 'Test Plug-in'");
pluginElement = document.getElementById("disabledPlugin");
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'");
ok(!findPlugin("Second Test Plug-in"), "Should NOT found a hidden plugin named 'Test Plug-in'");
pluginElement = document.getElementById("clickToPlayPlugin");
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.mimeTypes["application/x-second-test"], "Should have queried a MIME type named 'application/x-second-test'");
ok(navigator.plugins["Test Plug-in"], "Should have queried a plugin named 'Test Plug-in'");
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(!findMimeType("application/x-second-test"), "Should NOT have found a MIME type named 'application/x-second-test'");
ok(findPlugin("Test Plug-in"), "Should have found a plugin named 'Test Plug-in'");
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();
}
</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>
</html>

View File

@ -368,7 +368,7 @@ nsVolumeService::FindVolumeByMountLockName(const nsAString& aMountLockName)
}
already_AddRefed<nsVolume>
nsVolumeService::FindVolumeByName(const nsAString& aName, nsVolume::Array::index_type* aIndex)
nsVolumeService::FindVolumeByName(const nsAString& aName)
{
mArrayMonitor.AssertCurrentThreadOwns();
@ -377,34 +377,52 @@ nsVolumeService::FindVolumeByName(const nsAString& aName, nsVolume::Array::index
for (volIndex = 0; volIndex < numVolumes; volIndex++) {
nsRefPtr<nsVolume> vol = mVolumeArray[volIndex];
if (vol->Name().Equals(aName)) {
if (aIndex) {
*aIndex = volIndex;
}
return vol.forget();
}
}
return nullptr;
}
//static
already_AddRefed<nsVolume>
nsVolumeService::CreateOrFindVolumeByName(const nsAString& aName, bool aIsFake /*= false*/)
{
MonitorAutoLock autoLock(mArrayMonitor);
nsRefPtr<nsVolume> vol;
vol = FindVolumeByName(aName);
if (vol) {
return vol.forget();
}
// Volume not found - add a new one
vol = new nsVolume(aName);
vol->SetIsFake(aIsFake);
mVolumeArray.AppendElement(vol);
return vol.forget();
}
void
nsVolumeService::UpdateVolume(nsVolume* aVolume, bool aNotifyObservers)
nsVolumeService::UpdateVolume(nsIVolume* aVolume, bool aNotifyObservers)
{
MOZ_ASSERT(NS_IsMainThread());
{
MonitorAutoLock autoLock(mArrayMonitor);
nsVolume::Array::index_type volIndex;
nsRefPtr<nsVolume> vol = FindVolumeByName(aVolume->Name(), &volIndex);
if (!vol) {
mVolumeArray.AppendElement(aVolume);
} else if (vol->Equals(aVolume) || (!vol->IsFake() && aVolume->IsFake())) {
// Ignore if nothing changed or if a fake tries to override a real volume.
return;
} else {
mVolumeArray.ReplaceElementAt(volIndex, aVolume);
}
nsString volName;
aVolume->GetName(volName);
bool aIsFake;
aVolume->GetIsFake(&aIsFake);
nsRefPtr<nsVolume> vol = CreateOrFindVolumeByName(volName, aIsFake);
if (vol->Equals(aVolume)) {
// Nothing has really changed. Don't bother telling anybody.
return;
}
if (!vol->IsFake() && aIsFake) {
// Prevent an incoming fake volume from overriding an existing real volume.
return;
}
vol->Set(aVolume);
if (!aNotifyObservers) {
return;
}
@ -413,8 +431,8 @@ nsVolumeService::UpdateVolume(nsVolume* aVolume, bool aNotifyObservers)
if (!obs) {
return;
}
NS_ConvertUTF8toUTF16 stateStr(aVolume->StateStr());
obs->NotifyObservers(aVolume, NS_VOLUME_STATE_CHANGED, stateStr.get());
NS_ConvertUTF8toUTF16 stateStr(vol->StateStr());
obs->NotifyObservers(vol, NS_VOLUME_STATE_CHANGED, stateStr.get());
}
NS_IMETHODIMP
@ -453,7 +471,9 @@ nsVolumeService::SetFakeVolumeState(const nsAString& name, int32_t state)
return NS_ERROR_NOT_AVAILABLE;
}
// Clone the existing volume so we can replace it
// UpdateVolume expects the volume passed in to NOT be the
// same pointer as what CreateOrFindVolumeByName would return,
// which is why we allocate a temporary volume here.
nsRefPtr<nsVolume> volume = new nsVolume(name);
volume->Set(vol);
volume->SetState(state);
@ -482,15 +502,15 @@ nsVolumeService::RemoveFakeVolume(const nsAString& name)
void
nsVolumeService::RemoveVolumeByName(const nsAString& aName)
{
nsRefPtr<nsVolume> vol;
{
MonitorAutoLock autoLock(mArrayMonitor);
nsVolume::Array::index_type volIndex;
nsRefPtr<nsVolume> vol = FindVolumeByName(aName, &volIndex);
if (!vol) {
return;
}
mVolumeArray.RemoveElementAt(volIndex);
vol = FindVolumeByName(aName);
}
if (!vol) {
return;
}
mVolumeArray.RemoveElement(vol);
if (XRE_GetProcessType() == GeckoProcessType_Default) {
nsCOMPtr<nsIObserverService> obs = GetObserverService();

View File

@ -47,7 +47,7 @@ public:
void DumpNoLock(const char* aLabel);
// To use this function, you have to create a new volume and pass it in.
void UpdateVolume(nsVolume* aVolume, bool aNotifyObservers = true);
void UpdateVolume(nsIVolume* aVolume, bool aNotifyObservers = true);
void UpdateVolumeIOThread(const Volume* aVolume);
void RecvVolumesFromParent(const nsTArray<dom::VolumeInfo>& aVolumes);
@ -61,9 +61,8 @@ private:
void CheckMountLock(const nsAString& aMountLockName,
const nsAString& aMountLockState);
already_AddRefed<nsVolume> FindVolumeByMountLockName(const nsAString& aMountLockName);
already_AddRefed<nsVolume> FindVolumeByName(const nsAString& aName,
nsVolume::Array::index_type* aIndex = nullptr);
already_AddRefed<nsVolume> FindVolumeByName(const nsAString& aName);
already_AddRefed<nsVolume> CreateOrFindVolumeByName(const nsAString& aName, bool aIsFake = false);
Monitor mArrayMonitor;
nsVolume::Array mVolumeArray;

View File

@ -27,11 +27,11 @@ skip-if = buildapp == 'b2g' # Bug 1137683
skip-if = buildapp == 'b2g' # Bug 1137683
[test_fetch_basic_http.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
[test_fetch_cors.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
[test_formdataparsing.html]
[test_formdataparsing_sw_reroute.html]

View File

@ -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()) {
AssertIsOnMainThread();

View File

@ -28,6 +28,7 @@
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/LoadContext.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/DOMError.h"
@ -814,6 +815,9 @@ public:
return;
}
AssertIsOnMainThread();
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_UPDATED, 1);
nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
nsCOMPtr<nsIURI> scriptURI;
@ -1322,6 +1326,9 @@ ServiceWorkerManager::Register(nsIDOMWindow* aWindow,
new ServiceWorkerRegisterJob(queue, cleanedScope, spec, cb, documentPrincipal);
queue->Append(job);
AssertIsOnMainThread();
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_REGISTRATIONS, 1);
promise.forget(aPromise);
return NS_OK;
}
@ -2295,12 +2302,13 @@ ServiceWorkerManager::GetInstance()
// this can resurrect the ServiceWorkerManager pretty late during shutdown.
static bool firstTime = true;
if (firstTime) {
firstTime = false;
AssertIsOnMainThread();
gInstance = new ServiceWorkerManager();
gInstance->Init();
ClearOnShutdown(&gInstance);
firstTime = false;
}
nsRefPtr<ServiceWorkerManager> copy = gInstance.get();
return copy.forget();
@ -2789,6 +2797,7 @@ ServiceWorkerManager::StartControllingADocument(ServiceWorkerRegistrationInfo* a
aRegistration->StartControllingADocument();
mControlledDocuments.Put(aDoc, aRegistration);
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
}
void

View File

@ -9,7 +9,14 @@
function runTests() {
return Promise.resolve()
.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);
}
</script>

View File

@ -19,7 +19,8 @@ function registerServiceWorker() {
function runTests() {
return Promise.resolve()
.then(() => { return testFetchAppResource('networkresponse'); })
.then(() => { return testFetchAppResource('foo.txt',
'networkresponse'); })
.then(registerServiceWorker)
.then(ready);
}

View File

@ -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")';
event.respondWith(new Response(scriptContent, {
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'}
}));
}
});

View File

@ -14,16 +14,26 @@ function done() {
alert('DONE');
}
function testFetchAppResource(aExpectedResponse) {
return fetch('foo.txt').then(res => {
function testFetchAppResource(aUrl,
aExpectedResponse,
aExpectedContentType) {
return fetch(aUrl).then(res => {
ok(true, 'fetch should resolve');
if (res.type == 'error') {
ok(false, 'fetch failed');
}
ok(res.status == 200, 'status should be 200');
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 => {
ok(body == aExpectedResponse, 'body should match');
ok(body == aExpectedResponse, 'body ' + body +
' should match with ' + aExpectedResponse);
});
});
}

View File

@ -140,6 +140,25 @@ fetchXHR('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?st
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();
fetch('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*')
.then(function(res) {

Some files were not shown because too many files have changed in this diff Show More