mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 16:22:00 +00:00
Merge m-c to fx-team, a=merge
This commit is contained in:
commit
079a117eae
@ -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
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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 */
|
||||
|
@ -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]
|
||||
|
93
browser/base/content/test/general/browser_selectpopup.js
Normal file
93
browser/base/content/test/general/browser_selectpopup.js
Normal 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();
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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],
|
||||
|
36
configure.in
36
configure.in
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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__
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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, ¬ification);
|
||||
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, ¬ification);
|
||||
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, ¬ification);
|
||||
|
||||
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, ¬ification);
|
||||
|
||||
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, ¬ification);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
45
dom/media/MP3Decoder.cpp
Normal 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
26
dom/media/MP3Decoder.h
Normal 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
|
@ -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
|
@ -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
|
@ -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;
|
||||
};
|
||||
|
@ -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()");
|
||||
}
|
||||
|
@ -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);
|
||||
|
537
dom/media/eme/GMPVideoDecoderTrialCreator.cpp
Normal file
537
dom/media/eme/GMPVideoDecoderTrialCreator.cpp
Normal 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
|
175
dom/media/eme/GMPVideoDecoderTrialCreator.h
Normal file
175
dom/media/eme/GMPVideoDecoderTrialCreator.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
429
dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
Normal file
429
dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
Normal 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"
|
@ -8,7 +8,7 @@
|
||||
|
||||
FINAL_TARGET = 'dist/bin/gmp-fakeopenh264/1.0'
|
||||
SOURCES += [
|
||||
'../gmp-plugin/gmp-fake.cpp',
|
||||
'gmp-fake-openh264.cpp',
|
||||
]
|
||||
|
||||
SharedLibrary("fakeopenh264")
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -101,7 +101,7 @@ public:
|
||||
#endif
|
||||
|
||||
#if defined(DEBUG)
|
||||
void Dump(const char* aPath);
|
||||
void Dump(const char* aPath) override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
@ -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',
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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))));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
@ -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();
|
||||
}
|
||||
]);
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
Binary file not shown.
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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'}
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user