зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to fx-team. a=merge
This commit is contained in:
Коммит
e2d3873930
|
@ -102,7 +102,6 @@ pref("network.predictor.max-db-size", 2097152); // bytes
|
|||
pref("network.predictor.preserve", 50); // percentage of predictor data to keep when cleaning up
|
||||
|
||||
/* session history */
|
||||
pref("browser.sessionhistory.max_total_viewers", 1);
|
||||
pref("browser.sessionhistory.max_entries", 50);
|
||||
pref("browser.sessionhistory.contentViewerTimeout", 360);
|
||||
|
||||
|
|
|
@ -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="9a8880a95ee4a4aea7895d4e2bcab31bc49ea281"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="2d7f369fd923b6df3b00d76844c413c1202c04ba"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="62cfa11ae7d77f6330de019a5aa79607e35be7d1"/>
|
||||
<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="9a8880a95ee4a4aea7895d4e2bcab31bc49ea281"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="2d7f369fd923b6df3b00d76844c413c1202c04ba"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="62cfa11ae7d77f6330de019a5aa79607e35be7d1"/>
|
||||
<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="9a8880a95ee4a4aea7895d4e2bcab31bc49ea281"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2d7f369fd923b6df3b00d76844c413c1202c04ba"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="62cfa11ae7d77f6330de019a5aa79607e35be7d1"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/>
|
||||
|
|
|
@ -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="9a8880a95ee4a4aea7895d4e2bcab31bc49ea281"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="2d7f369fd923b6df3b00d76844c413c1202c04ba"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="62cfa11ae7d77f6330de019a5aa79607e35be7d1"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="36b945cb98a4e9009d57b8c20a720fc1a5905452"/>
|
||||
|
|
|
@ -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="9a8880a95ee4a4aea7895d4e2bcab31bc49ea281"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="2d7f369fd923b6df3b00d76844c413c1202c04ba"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="62cfa11ae7d77f6330de019a5aa79607e35be7d1"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="05a36844c1046a1eb07d5b1325f85ed741f961ea">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="9a8880a95ee4a4aea7895d4e2bcab31bc49ea281"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="2d7f369fd923b6df3b00d76844c413c1202c04ba"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="62cfa11ae7d77f6330de019a5aa79607e35be7d1"/>
|
||||
<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="9a8880a95ee4a4aea7895d4e2bcab31bc49ea281"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2d7f369fd923b6df3b00d76844c413c1202c04ba"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="62cfa11ae7d77f6330de019a5aa79607e35be7d1"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/>
|
||||
|
|
|
@ -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="9a8880a95ee4a4aea7895d4e2bcab31bc49ea281"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="2d7f369fd923b6df3b00d76844c413c1202c04ba"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="62cfa11ae7d77f6330de019a5aa79607e35be7d1"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"git": {
|
||||
"git_revision": "9a8880a95ee4a4aea7895d4e2bcab31bc49ea281",
|
||||
"git_revision": "2d7f369fd923b6df3b00d76844c413c1202c04ba",
|
||||
"remote": "https://git.mozilla.org/releases/gaia.git",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "aa0cf409f343d966a552839ab5d3bc85d1ebdda9",
|
||||
"revision": "5c86f4cf87b91d2bfe9a3e49aa8706b452e4f97e",
|
||||
"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="9a8880a95ee4a4aea7895d4e2bcab31bc49ea281"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="2d7f369fd923b6df3b00d76844c413c1202c04ba"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="62cfa11ae7d77f6330de019a5aa79607e35be7d1"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="36b945cb98a4e9009d57b8c20a720fc1a5905452"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="05a36844c1046a1eb07d5b1325f85ed741f961ea">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="9a8880a95ee4a4aea7895d4e2bcab31bc49ea281"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="2d7f369fd923b6df3b00d76844c413c1202c04ba"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="62cfa11ae7d77f6330de019a5aa79607e35be7d1"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -5369,11 +5369,27 @@
|
|||
canvas.mozOpaque = true;
|
||||
canvas.width = 160 * scale;
|
||||
canvas.height = 90 * scale;
|
||||
if (!gMultiProcessBrowser) {
|
||||
// Bug 863512 - Make page thumbnails work in e10s
|
||||
let toDrag;
|
||||
if (gMultiProcessBrowser) {
|
||||
// Create a panel to use it in setDragImage
|
||||
// which will tell xul to render a panel that follows
|
||||
// the pointer while a dnd session is on.
|
||||
var panel = document.createElement("panel");
|
||||
panel.setAttribute("type", "drag");
|
||||
panel.appendChild(canvas);
|
||||
document.documentElement.appendChild(panel);
|
||||
// PageThumb is async with e10s but that's fine
|
||||
// since we can update the panel during the dnd.
|
||||
PageThumbs.captureToCanvas(browser, canvas);
|
||||
toDrag = panel;
|
||||
} else {
|
||||
// For the non e10s case we can just use PageThumbs
|
||||
// sync. No need for xul magic, the native dnd will
|
||||
// be fine, so let's use the canvas for setDragImage.
|
||||
PageThumbs.captureToCanvas(browser, canvas);
|
||||
toDrag = canvas;
|
||||
}
|
||||
dt.setDragImage(canvas, -16 * scale, -16 * scale);
|
||||
dt.setDragImage(toDrag, -16 * scale, -16 * scale);
|
||||
|
||||
// _dragData.offsetX/Y give the coordinates that the mouse should be
|
||||
// positioned relative to the corner of the new window created upon
|
||||
|
@ -5738,7 +5754,7 @@
|
|||
xbl:inherits="value=visibleLabel,crop,accesskey,fadein,pinned,selected,visuallyselected"
|
||||
class="tab-text tab-label"
|
||||
role="presentation"/>
|
||||
<xul:image xbl:inherits="soundplaying,pinned,muted"
|
||||
<xul:image xbl:inherits="soundplaying,pinned,muted,visuallyselected"
|
||||
anonid="soundplaying-icon"
|
||||
class="tab-icon-sound"
|
||||
role="presentation"/>
|
||||
|
|
|
@ -158,7 +158,7 @@ function* test_browser_swapping(tab, browser) {
|
|||
yield test_swapped_browser(tab, newBrowser, true)
|
||||
|
||||
// FIXME: this is needed to work around bug 1190903.
|
||||
yield new Promise(resolve => setTimeout(resolve, 1000));
|
||||
yield new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
// Now, test swapping with a muted but not playing tab.
|
||||
// Note that the tab remains muted, so we only need to pause playback.
|
||||
|
@ -262,6 +262,7 @@ add_task(function*() {
|
|||
});
|
||||
});
|
||||
|
||||
requestLongerTimeout(2);
|
||||
add_task(function* test_page() {
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
|
|
|
@ -9,6 +9,7 @@ this.EXPORTED_SYMBOLS = ["SessionStorage"];
|
|||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/BrowserUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
|
@ -72,9 +73,9 @@ let SessionStorageInternal = {
|
|||
return;
|
||||
}
|
||||
|
||||
// Get the root domain of the current history entry
|
||||
// and use that as a key for the per-host storage data.
|
||||
let origin = principal.jarPrefix + principal.originNoSuffix;
|
||||
// Get the origin of the current history entry
|
||||
// and use that as a key for the per-principal storage data.
|
||||
let origin = principal.origin;
|
||||
if (visitedOrigins.has(origin)) {
|
||||
// Don't read a host twice.
|
||||
return;
|
||||
|
@ -102,10 +103,9 @@ let SessionStorageInternal = {
|
|||
* {"example.com": {"key": "value", "my_number": 123}}
|
||||
*/
|
||||
restore: function (aDocShell, aStorageData) {
|
||||
for (let host of Object.keys(aStorageData)) {
|
||||
let data = aStorageData[host];
|
||||
let uri = Services.io.newURI(host, null, null);
|
||||
let principal = Services.scriptSecurityManager.getDocShellCodebasePrincipal(uri, aDocShell);
|
||||
for (let origin of Object.keys(aStorageData)) {
|
||||
let data = aStorageData[origin];
|
||||
let principal = BrowserUtils.principalFromOrigin(origin);
|
||||
let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
|
||||
let window = aDocShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
|
||||
|
||||
|
|
|
@ -4,82 +4,41 @@
|
|||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
use:not(:target) {
|
||||
.icon:not(:target) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: #4d4d4d;
|
||||
fill: #262626;
|
||||
}
|
||||
.icon.hover {
|
||||
fill: #333333;
|
||||
}
|
||||
.icon.pressed {
|
||||
fill: #000;
|
||||
}
|
||||
.icon.dark {
|
||||
fill: #ccc;
|
||||
}
|
||||
.icon.dark.hover {
|
||||
fill: #b2b2b2;
|
||||
}
|
||||
.icon.dark.pressed {
|
||||
.icon > .outline {
|
||||
fill: #fff;
|
||||
}
|
||||
.muted {
|
||||
opacity: .7;
|
||||
stroke: #4d4d4d;
|
||||
stroke-width: 0;
|
||||
|
||||
.icon.white {
|
||||
fill: #fff;
|
||||
}
|
||||
.muted.hover {
|
||||
opacity: .85;
|
||||
stroke: #333333;
|
||||
}
|
||||
.muted.pressed {
|
||||
opacity: 1;
|
||||
stroke: #000;
|
||||
}
|
||||
.muted.dark {
|
||||
stroke: #ccc;
|
||||
}
|
||||
.muted.dark.hover {
|
||||
stroke: #b2b2b2;
|
||||
}
|
||||
.muted.dark.pressed {
|
||||
stroke: #fff;
|
||||
.icon.white > .outline {
|
||||
fill: #000;
|
||||
fill-opacity: .5;
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
<clipPath id="clip-wave">
|
||||
<path d="M 11,7 l 3,-8 l 2,0 l 0,18 l -2,0 l -3,-8 z" />
|
||||
</clipPath>
|
||||
<mask id="disabled-cutout">
|
||||
<rect width="16" height="16" fill="#fff" />
|
||||
<line x1="4" y1="14" x2="14" y2="4" stroke="#000" stroke-width="2" />
|
||||
</mask>
|
||||
<g id="shape-tab-audio">
|
||||
<rect x="3" y="6" width="5" height="4" rx="1" ry="1" />
|
||||
<polygon points="5.5,6.5 9,3 9,13 5.5,9.5" />
|
||||
<path d="M 10,6.5 a 1.5 1.5 0 0,1 0,3 z" />
|
||||
<path d="M 10,4 a 4 4 0 0,1 0,8 l 0,-1 a 3 3 0 0,0 0,-6 z" clip-path="url(#clip-wave)" />
|
||||
</g>
|
||||
<g id="shape-tab-audio-muted">
|
||||
<g mask="url(#disabled-cutout)">
|
||||
<rect x="4" y="6" width="5" height="4" rx="1" ry="1" />
|
||||
<polygon points="6.5,6.5 10,3 10,13 6.5,9.5" />
|
||||
</g>
|
||||
<line x1="3" y1="14" x2="13" y2="4" stroke-width="1.5" />
|
||||
</g>
|
||||
</defs>
|
||||
<use id="tab-audio" class="icon" xlink:href="#shape-tab-audio"/>
|
||||
<use id="tab-audio-hover" class="icon hover" xlink:href="#shape-tab-audio"/>
|
||||
<use id="tab-audio-pressed" class="icon pressed" xlink:href="#shape-tab-audio"/>
|
||||
<use id="tab-audio-muted" class="icon muted" xlink:href="#shape-tab-audio-muted" />
|
||||
<use id="tab-audio-muted-hover" class="icon muted hover" xlink:href="#shape-tab-audio-muted" />
|
||||
<use id="tab-audio-muted-pressed" class="icon muted pressed" xlink:href="#shape-tab-audio-muted" />
|
||||
<use id="tab-audio-dark" class="icon dark" xlink:href="#shape-tab-audio"/>
|
||||
<use id="tab-audio-dark-hover" class="icon hover dark" xlink:href="#shape-tab-audio"/>
|
||||
<use id="tab-audio-dark-pressed" class="icon pressed dark" xlink:href="#shape-tab-audio"/>
|
||||
<use id="tab-audio-muted-dark" class="icon muted dark" xlink:href="#shape-tab-audio-muted" />
|
||||
<use id="tab-audio-muted-dark-hover" class="icon muted hover dark" xlink:href="#shape-tab-audio-muted" />
|
||||
<use id="tab-audio-muted-dark-pressed" class="icon muted pressed dark" xlink:href="#shape-tab-audio-muted" />
|
||||
|
||||
<g id="tab-audio" class="icon">
|
||||
<path class="outline" d="M12.4,3.6l-1-0.6l-0.9,2.5H10V1.8c0-0.4-0.5-0.7-0.9-0.4L5.6,5H4C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.6l3.6,3.6 c0.3,0.3,0.9,0.1,0.9-0.4v-3.7h0.5l0.9,2.5l1-0.6C14,11.5,15,9.8,15,8S14,4.5,12.4,3.6z M9,13l-3-3H4c-0.6,0-1-0.4-1-1V7 c0-0.6,0.4-1,1-1h2l3-3V13z M10,9.5v-3c0.8,0,1.5,0.7,1.5,1.5S10.8,9.5,10,9.5z M11.9,11.5l-0.4-0.9C12.4,10,13,9.1,13,8 s-0.6-2-1.4-2.5l0.3-1C13.2,5.2,14,6.5,14,8S13.2,10.8,11.9,11.5z"/>
|
||||
<path d="M4,6C3.4,6,3,6.4,3,7v2c0,0.6,0.4,1,1,1h2l3,3V3L6,6H4z M10,6.5v3c0.8,0,1.5-0.7,1.5-1.5S10.8,6.5,10,6.5z M11.9,4.5 l-0.4,0.9C12.4,6,13,6.9,13,8s-0.6,2-1.4,2.5l0.4,0.9c1.2-0.7,2.1-2,2.1-3.5S13.2,5.2,11.9,4.5z"/>
|
||||
</g>
|
||||
<g id="tab-audio-muted" class="icon">
|
||||
<path class="outline" d="M5.6,5H4C2.9,5,2,5.9,2,7v2c0,0.7,0.3,1.3,0.9,1.7l-1.8,1.8l2.5,2.5l3-3l2.6,2.6c0.3,0.3,0.9,0.1,0.9-0.4V8.5l3.9-3.9 l-2.5-2.5L10,3.5V1.8c0-0.4-0.5-0.7-0.9-0.4L5.6,5z"/>
|
||||
<path d="M11.5,3.5L9,5.9V3L6,6H4C3.4,6,3,6.4,3,7v2c0,0.6,0.4,1,1,1h0.9l-2.5,2.5l1.1,1.1l9-9L11.5,3.5z M9,13V9.7l-1.7,1.7L9,13z"/>
|
||||
</g>
|
||||
|
||||
<g id="tab-audio-white" class="icon white">
|
||||
<path class="outline" d="M12.4,3.6l-1-0.6l-0.9,2.5H10V1.8c0-0.4-0.5-0.7-0.9-0.4L5.6,5H4C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.6l3.6,3.6 c0.3,0.3,0.9,0.1,0.9-0.4v-3.7h0.5l0.9,2.5l1-0.6C14,11.5,15,9.8,15,8S14,4.5,12.4,3.6z M9,13l-3-3H4c-0.6,0-1-0.4-1-1V7 c0-0.6,0.4-1,1-1h2l3-3V13z M10,9.5v-3c0.8,0,1.5,0.7,1.5,1.5S10.8,9.5,10,9.5z M11.9,11.5l-0.4-0.9C12.4,10,13,9.1,13,8 s-0.6-2-1.4-2.5l0.3-1C13.2,5.2,14,6.5,14,8S13.2,10.8,11.9,11.5z"/>
|
||||
<path d="M4,6C3.4,6,3,6.4,3,7v2c0,0.6,0.4,1,1,1h2l3,3V3L6,6H4z M10,6.5v3c0.8,0,1.5-0.7,1.5-1.5S10.8,6.5,10,6.5z M11.9,4.5 l-0.4,0.9C12.4,6,13,6.9,13,8s-0.6,2-1.4,2.5l0.4,0.9c1.2-0.7,2.1-2,2.1-3.5S13.2,5.2,11.9,4.5z"/>
|
||||
</g>
|
||||
<g id="tab-audio-white-muted" class="icon white">
|
||||
<path class="outline" d="M5.6,5H4C2.9,5,2,5.9,2,7v2c0,0.7,0.3,1.3,0.9,1.7l-1.8,1.8l2.5,2.5l3-3l2.6,2.6c0.3,0.3,0.9,0.1,0.9-0.4V8.5l3.9-3.9 l-2.5-2.5L10,3.5V1.8c0-0.4-0.5-0.7-0.9-0.4L5.6,5z"/>
|
||||
<path d="M11.5,3.5L9,5.9V3L6,6H4C3.4,6,3,6.4,3,7v2c0,0.6,0.4,1,1,1h0.9l-2.5,2.5l1.1,1.1l9-9L11.5,3.5z M9,13V9.7l-1.7,1.7L9,13z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 3.0 KiB После Ширина: | Высота: | Размер: 2.8 KiB |
|
@ -4,83 +4,84 @@
|
|||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
use:not(:target) {
|
||||
.icon:not(:target) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: #666;
|
||||
fill: #333;
|
||||
opacity: .75;
|
||||
}
|
||||
.icon.hover {
|
||||
fill: #4d4d4d;
|
||||
fill: #1a1a1a;
|
||||
opacity: .85;
|
||||
}
|
||||
.icon.pressed {
|
||||
fill: #000;
|
||||
fill: #0d0d0d;
|
||||
opacity: .95;
|
||||
}
|
||||
.icon.dark {
|
||||
fill: #999;
|
||||
}
|
||||
.icon.dark.hover {
|
||||
fill: #b2b2b2;
|
||||
}
|
||||
.icon.dark.pressed {
|
||||
|
||||
.icon.white {
|
||||
fill: #fff;
|
||||
}
|
||||
.muted {
|
||||
opacity: .7;
|
||||
stroke: #666;
|
||||
stroke-width: 0;
|
||||
.icon.white.hover {
|
||||
opacity: .9;
|
||||
}
|
||||
.muted.hover {
|
||||
opacity: .85;
|
||||
stroke: #4d4d4d;
|
||||
}
|
||||
.muted.pressed {
|
||||
.icon.white.pressed {
|
||||
opacity: 1;
|
||||
stroke: #000;
|
||||
}
|
||||
.muted.dark {
|
||||
stroke: #999;
|
||||
.icon.white > .outline {
|
||||
fill: #000;
|
||||
fill-opacity: .5;
|
||||
}
|
||||
.muted.dark.hover {
|
||||
stroke: #b2b2b2;
|
||||
}
|
||||
.muted.dark.pressed {
|
||||
stroke: #fff;
|
||||
|
||||
.icon.backgroundTab,
|
||||
.icon.backgroundTab.hover,
|
||||
.icon.backgroundTab.pressed {
|
||||
fill: -moz-MenuBarText;
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
<clipPath id="clip-wave">
|
||||
<path d="M 10,7 l 3,-8 l 2,0 l 0,18 l -2,0 l -3,-8 z" />
|
||||
</clipPath>
|
||||
<mask id="disabled-cutout">
|
||||
<rect width="16" height="16" fill="#fff" />
|
||||
<line x1="4" y1="14" x2="14" y2="4" stroke="#000" stroke-width="2" />
|
||||
</mask>
|
||||
<g id="shape-tab-audio">
|
||||
<rect x="2" y="5" width="6" height="6" rx="2" ry="2" />
|
||||
<polygon points="4,6 9,2 9,14 4,10" />
|
||||
<path d="M 10,7 a 1 1 0 0,1 0,2 z" />
|
||||
<path d="M 10,5 a 3 3 0 0,1 0,6 l 0,-1 a 2 2 0 0,0 0,-4 z" clip-path="url(#clip-wave)" />
|
||||
<path d="M 10,3 a 5 5 0 0,1 0,10 l 0,-1 a 4 4 0 0,0 0,-8 z" clip-path="url(#clip-wave)" />
|
||||
</g>
|
||||
<g id="shape-tab-audio-muted">
|
||||
<g mask="url(#disabled-cutout)">
|
||||
<rect x="3" y="5" width="6" height="6" rx="2" ry="2" />
|
||||
<polygon points="5,6 10,2 10,14 5,10" />
|
||||
</g>
|
||||
<line x1="2" y1="13" x2="14" y2="3" stroke-width="1.5" />
|
||||
</g>
|
||||
</defs>
|
||||
<use id="tab-audio" class="icon" xlink:href="#shape-tab-audio"/>
|
||||
<use id="tab-audio-hover" class="icon hover" xlink:href="#shape-tab-audio"/>
|
||||
<use id="tab-audio-pressed" class="icon pressed" xlink:href="#shape-tab-audio"/>
|
||||
<use id="tab-audio-muted" class="icon muted" xlink:href="#shape-tab-audio-muted" />
|
||||
<use id="tab-audio-muted-hover" class="icon muted hover" xlink:href="#shape-tab-audio-muted" />
|
||||
<use id="tab-audio-muted-pressed" class="icon muted pressed" xlink:href="#shape-tab-audio-muted" />
|
||||
<use id="tab-audio-dark" class="icon dark" xlink:href="#shape-tab-audio"/>
|
||||
<use id="tab-audio-dark-hover" class="icon hover dark" xlink:href="#shape-tab-audio"/>
|
||||
<use id="tab-audio-dark-pressed" class="icon pressed dark" xlink:href="#shape-tab-audio"/>
|
||||
<use id="tab-audio-muted-dark" class="icon muted dark" xlink:href="#shape-tab-audio-muted" />
|
||||
<use id="tab-audio-muted-dark-hover" class="icon muted hover dark" xlink:href="#shape-tab-audio-muted" />
|
||||
<use id="tab-audio-muted-dark-pressed" class="icon muted pressed dark" xlink:href="#shape-tab-audio-muted" />
|
||||
|
||||
<path id="tab-audio" class="icon" d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
|
||||
<path id="tab-audio-hover" class="icon hover" d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
|
||||
<path id="tab-audio-pressed" class="icon pressed" d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
|
||||
|
||||
<path id="tab-audio-muted" class="icon" d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
|
||||
<path id="tab-audio-muted-hover" class="icon hover" d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
|
||||
<path id="tab-audio-muted-pressed" class="icon pressed" d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
|
||||
|
||||
<path id="tab-audio-backgroundTab" class="icon backgroundTab" d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
|
||||
<path id="tab-audio-backgroundTab-hover" class="icon backgroundTab hover" d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
|
||||
<path id="tab-audio-backgroundTab-pressed" class="icon backgroundTab pressed" d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
|
||||
|
||||
<path id="tab-audio-backgroundTab-muted" class="icon backgroundTab" d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
|
||||
<path id="tab-audio-backgroundTab-muted-hover" class="icon backgroundTab hover" d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
|
||||
<path id="tab-audio-backgroundTab-muted-pressed" class="icon backgroundTab pressed" d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
|
||||
|
||||
<g id="tab-audio-white" class="icon white">
|
||||
<path class="outline" d="M9,2v12l-3.8-3H4c-1.1,0-2-0.9-2-2V7c0-1.1,0.9-2,2-2h1.2L9,2 M11.4,3.2C13.5,3.8,15,5.7,15,8 s-1.5,4.2-3.5,4.7l-0.4-0.9c1.7-0.4,2.9-2,2.9-3.8s-1.2-3.4-3-3.9L11.4,3.2 M10.7,5.1C12,5.4,13,6.6,13,8s-1,2.6-2.2,2.9L10.4,10 C11.3,9.8,12,9,12,8s-0.7-1.8-1.6-2L10.7,5.1 M10,7c0.6,0,1,0.4,1,1s-0.4,1-1,1V7 M10-0.1L8.4,1.2L4.9,4H4C2.3,4,1,5.3,1,7v2 c0,1.7,1.3,3,3,3h0.9l3.5,2.8l1.6,1.3V14v-2.2l0.2,0.4l0.4,0.9l0.3,0.8l0.8-0.2C14.2,13,16,10.7,16,8c0-2.7-1.7-5-4.3-5.8L10.8,2 l-0.4,0.8l-0.4,0.9L10,3.9V2V-0.1L10-0.1z"/>
|
||||
<path d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
|
||||
</g>
|
||||
<g id="tab-audio-white-hover" class="icon white hover">
|
||||
<path class="outline" d="M9,2v12l-3.8-3H4c-1.1,0-2-0.9-2-2V7c0-1.1,0.9-2,2-2h1.2L9,2 M11.4,3.2C13.5,3.8,15,5.7,15,8 s-1.5,4.2-3.5,4.7l-0.4-0.9c1.7-0.4,2.9-2,2.9-3.8s-1.2-3.4-3-3.9L11.4,3.2 M10.7,5.1C12,5.4,13,6.6,13,8s-1,2.6-2.2,2.9L10.4,10 C11.3,9.8,12,9,12,8s-0.7-1.8-1.6-2L10.7,5.1 M10,7c0.6,0,1,0.4,1,1s-0.4,1-1,1V7 M10-0.1L8.4,1.2L4.9,4H4C2.3,4,1,5.3,1,7v2 c0,1.7,1.3,3,3,3h0.9l3.5,2.8l1.6,1.3V14v-2.2l0.2,0.4l0.4,0.9l0.3,0.8l0.8-0.2C14.2,13,16,10.7,16,8c0-2.7-1.7-5-4.3-5.8L10.8,2 l-0.4,0.8l-0.4,0.9L10,3.9V2V-0.1L10-0.1z"/>
|
||||
<path d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
|
||||
</g>
|
||||
<g id="tab-audio-white-pressed" class="icon white pressed">
|
||||
<path class="outline" d="M9,2v12l-3.8-3H4c-1.1,0-2-0.9-2-2V7c0-1.1,0.9-2,2-2h1.2L9,2 M11.4,3.2C13.5,3.8,15,5.7,15,8 s-1.5,4.2-3.5,4.7l-0.4-0.9c1.7-0.4,2.9-2,2.9-3.8s-1.2-3.4-3-3.9L11.4,3.2 M10.7,5.1C12,5.4,13,6.6,13,8s-1,2.6-2.2,2.9L10.4,10 C11.3,9.8,12,9,12,8s-0.7-1.8-1.6-2L10.7,5.1 M10,7c0.6,0,1,0.4,1,1s-0.4,1-1,1V7 M10-0.1L8.4,1.2L4.9,4H4C2.3,4,1,5.3,1,7v2 c0,1.7,1.3,3,3,3h0.9l3.5,2.8l1.6,1.3V14v-2.2l0.2,0.4l0.4,0.9l0.3,0.8l0.8-0.2C14.2,13,16,10.7,16,8c0-2.7-1.7-5-4.3-5.8L10.8,2 l-0.4,0.8l-0.4,0.9L10,3.9V2V-0.1L10-0.1z"/>
|
||||
<path d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
|
||||
</g>
|
||||
|
||||
<g id="tab-audio-muted-white" class="icon muted white">
|
||||
<path class="outline" d="M9,2v4.3l3.5-2.9l0.9,1.2l-11,9l-1-1.2l1.9-1.5C2.6,10.6,2,9.9,2,9V7c0-1.1,0.9-2,2-2h1.2L9,2 M9,10v4l-2.5-2L9,10 M10-0.1 L8.4,1.2L4.9,4H4C2.3,4,1,5.3,1,7v2c0,0.7,0.3,1.4,0.7,2l-0.8,0.7l-0.8,0.6l0.6,0.8l1,1.2L2.3,15l0.8-0.6l2.3-1.9l0.4,0.3l2.5,2 l1.6,1.3V14v-4V8.7l4.1-3.4l0.8-0.6l-0.6-0.8l-0.9-1.2L12.7,2l-0.8,0.6L10,4.2V2V-0.1L10-0.1z"/>
|
||||
<path d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
|
||||
</g>
|
||||
<g id="tab-audio-muted-white-hover" class="icon muted white hover">
|
||||
<path class="outline" d="M9,2v4.3l3.5-2.9l0.9,1.2l-11,9l-1-1.2l1.9-1.5C2.6,10.6,2,9.9,2,9V7c0-1.1,0.9-2,2-2h1.2L9,2 M9,10v4l-2.5-2L9,10 M10-0.1 L8.4,1.2L4.9,4H4C2.3,4,1,5.3,1,7v2c0,0.7,0.3,1.4,0.7,2l-0.8,0.7l-0.8,0.6l0.6,0.8l1,1.2L2.3,15l0.8-0.6l2.3-1.9l0.4,0.3l2.5,2 l1.6,1.3V14v-4V8.7l4.1-3.4l0.8-0.6l-0.6-0.8l-0.9-1.2L12.7,2l-0.8,0.6L10,4.2V2V-0.1L10-0.1z"/>
|
||||
<path d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
|
||||
</g>
|
||||
<g id="tab-audio-muted-white-pressed" class="icon muted white pressed">
|
||||
<path class="outline" d="M9,2v4.3l3.5-2.9l0.9,1.2l-11,9l-1-1.2l1.9-1.5C2.6,10.6,2,9.9,2,9V7c0-1.1,0.9-2,2-2h1.2L9,2 M9,10v4l-2.5-2L9,10 M10-0.1 L8.4,1.2L4.9,4H4C2.3,4,1,5.3,1,7v2c0,0.7,0.3,1.4,0.7,2l-0.8,0.7l-0.8,0.6l0.6,0.8l1,1.2L2.3,15l0.8-0.6l2.3-1.9l0.4,0.3l2.5,2 l1.6,1.3V14v-4V8.7l4.1-3.4l0.8-0.6l-0.6-0.8l-0.9-1.2L12.7,2l-0.8,0.6L10,4.2V2V-0.1L10-0.1z"/>
|
||||
<path d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 3.1 KiB После Ширина: | Высота: | Размер: 8.7 KiB |
|
@ -123,48 +123,16 @@
|
|||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio");
|
||||
}
|
||||
|
||||
.tab-icon-overlay[soundplaying][pinned]:hover {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-hover");
|
||||
}
|
||||
|
||||
.tab-icon-overlay[soundplaying][pinned]:hover:active {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-pressed");
|
||||
}
|
||||
|
||||
.tab-icon-overlay[muted][pinned]:not([crashed]) {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted");
|
||||
}
|
||||
|
||||
.tab-icon-overlay[muted][pinned]:not([crashed]):hover {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted-hover");
|
||||
#TabsToolbar[brighttext] .tab-icon-overlay[soundplaying][pinned]:not(:hover) {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white");
|
||||
}
|
||||
|
||||
.tab-icon-overlay[muted][pinned]:not([crashed]):hover:active {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted-pressed");
|
||||
}
|
||||
|
||||
#TabsToolbar[brighttext] .tab-icon-overlay[soundplaying][pinned] {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-dark");
|
||||
}
|
||||
|
||||
#TabsToolbar[brighttext] .tab-icon-overlay[soundplaying][pinned]:hover {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-hover");
|
||||
}
|
||||
|
||||
#TabsToolbar[brighttext] .tab-icon-overlay[soundplaying][pinned]:hover:active {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-pressed");
|
||||
}
|
||||
|
||||
#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned]:not([crashed]) {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted");
|
||||
}
|
||||
|
||||
#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned]:not([crashed]):hover {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted-hover");
|
||||
}
|
||||
|
||||
#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned]:not([crashed]):hover:active {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted-pressed");
|
||||
#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned]:not([crashed]):not(:hover) {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white-muted");
|
||||
}
|
||||
|
||||
.tab-throbber[busy] {
|
||||
|
@ -199,51 +167,75 @@
|
|||
}
|
||||
|
||||
.tab-icon-sound[soundplaying] {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio");
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-backgroundTab");
|
||||
}
|
||||
|
||||
.tab-icon-sound[soundplaying]:hover {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-hover");
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-backgroundTab-hover");
|
||||
}
|
||||
|
||||
.tab-icon-sound[soundplaying]:hover:active {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-pressed");
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-backgroundTab-pressed");
|
||||
}
|
||||
|
||||
.tab-icon-sound[muted] {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted");
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-backgroundTab-muted");
|
||||
}
|
||||
|
||||
.tab-icon-sound[muted]:hover {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-hover");
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-backgroundTab-muted-hover");
|
||||
}
|
||||
|
||||
.tab-icon-sound[muted]:hover:active {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-backgroundTab-muted-pressed");
|
||||
}
|
||||
|
||||
.tab-icon-sound[visuallyselected=true][soundplaying] {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio");
|
||||
}
|
||||
|
||||
.tab-icon-sound[visuallyselected=true][soundplaying]:hover {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-hover");
|
||||
}
|
||||
|
||||
.tab-icon-sound[visuallyselected=true][soundplaying]:hover:active {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-pressed");
|
||||
}
|
||||
|
||||
.tab-icon-sound[visuallyselected=true][muted] {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted");
|
||||
}
|
||||
|
||||
.tab-icon-sound[visuallyselected=true][muted]:hover {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-hover");
|
||||
}
|
||||
|
||||
.tab-icon-sound[visuallyselected=true][muted]:hover:active {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-pressed");
|
||||
}
|
||||
|
||||
#TabsToolbar[brighttext] .tab-icon-sound[soundplaying] {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-dark");
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-white");
|
||||
}
|
||||
|
||||
#TabsToolbar[brighttext] .tab-icon-sound[soundplaying]:hover {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-dark-hover");
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-white-hover");
|
||||
}
|
||||
|
||||
#TabsToolbar[brighttext] .tab-icon-sound[soundplaying]:hover:active {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-dark-pressed");
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-white-pressed");
|
||||
}
|
||||
|
||||
#TabsToolbar[brighttext] .tab-icon-sound[muted] {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-dark");
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-white");
|
||||
}
|
||||
|
||||
#TabsToolbar[brighttext] .tab-icon-sound[muted]:hover {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-dark-hover");
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-white-hover");
|
||||
}
|
||||
|
||||
#TabsToolbar[brighttext] .tab-icon-sound[muted]:hover:active {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-dark-pressed");
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-white-pressed");
|
||||
}
|
||||
|
||||
.tab-background,
|
||||
|
|
|
@ -20,7 +20,7 @@ DEFINES += -DAPP_BUILDID=$(APP_BUILDID)
|
|||
|
||||
APP_INI_DEPS += $(DEPTH)/config/autoconf.mk
|
||||
|
||||
MOZ_SOURCE_STAMP := $(firstword $(shell cd $(topsrcdir)/$(MOZ_BUILD_APP)/.. && hg parent --template='{node|short}\n' 2>/dev/null))
|
||||
MOZ_SOURCE_STAMP := $(firstword $(shell cd $(topsrcdir)/$(MOZ_BUILD_APP)/.. && hg parent --template='{node}\n' 2>/dev/null))
|
||||
ifdef MOZ_SOURCE_STAMP
|
||||
DEFINES += -DMOZ_SOURCE_STAMP='$(MOZ_SOURCE_STAMP)'
|
||||
endif
|
||||
|
|
|
@ -60,7 +60,7 @@ else
|
|||
ac_add_options "--with-compiler-wrapper=python2.7 $topsrcdir/sccache/sccache.py"
|
||||
mk_add_options MOZ_PREFLIGHT_ALL+=build/sccache.mk
|
||||
mk_add_options MOZ_POSTFLIGHT_ALL+=build/sccache.mk
|
||||
mk_add_options "export UPLOAD_EXTRA_FILES+=sccache.log.gz"
|
||||
mk_add_options "UPLOAD_EXTRA_FILES+=sccache.log.gz"
|
||||
case "$platform" in
|
||||
win*)
|
||||
# sccache supports a special flag to create depfiles.
|
||||
|
|
13
client.mk
13
client.mk
|
@ -190,9 +190,10 @@ WANT_MOZCONFIG_MK = 1
|
|||
endif
|
||||
|
||||
ifdef WANT_MOZCONFIG_MK
|
||||
# For now, only output "export" lines from mach environment --format=client.mk output.
|
||||
MOZCONFIG_MK_LINES := $(filter export||%,$(MOZCONFIG_OUT_LINES))
|
||||
$(OBJDIR)/.mozconfig.mk: $(FOUND_MOZCONFIG) $(call mkdir_deps,$(OBJDIR)) $(OBJDIR)/CLOBBER
|
||||
# For now, only output "export" lines and lines containing UPLOAD_EXTRA_FILES
|
||||
# from mach environment --format=client.mk output.
|
||||
MOZCONFIG_MK_LINES := $(filter export||% UPLOAD_EXTRA_FILES% %UPLOAD_EXTRA_FILES%,$(MOZCONFIG_OUT_LINES))
|
||||
$(OBJDIR)/.mozconfig.mk: $(TOPSRCDIR)/client.mk $(FOUND_MOZCONFIG) $(call mkdir_deps,$(OBJDIR)) $(OBJDIR)/CLOBBER
|
||||
$(if $(MOZCONFIG_MK_LINES),( $(foreach line,$(MOZCONFIG_MK_LINES), echo '$(subst ||, ,$(line))';) )) > $@
|
||||
|
||||
# Include that makefile so that it is created. This should not actually change
|
||||
|
@ -201,12 +202,6 @@ $(OBJDIR)/.mozconfig.mk: $(FOUND_MOZCONFIG) $(call mkdir_deps,$(OBJDIR)) $(OBJDI
|
|||
include $(OBJDIR)/.mozconfig.mk
|
||||
endif
|
||||
|
||||
# UPLOAD_EXTRA_FILES is appended to and exported from mozconfig, which makes
|
||||
# submakes as well as configure add even more to that, so just unexport it
|
||||
# for submakes to pick it from .mozconfig.mk and for configure to pick it
|
||||
# from mach environment.
|
||||
unexport UPLOAD_EXTRA_FILES
|
||||
|
||||
# Print out any options loaded from mozconfig.
|
||||
all realbuild clean distclean export libs install realclean::
|
||||
ifneq (,$(strip $(MOZCONFIG_OUT_FILTERED)))
|
||||
|
|
|
@ -526,7 +526,7 @@ File::GetLastModifiedDate(ErrorResult& aRv)
|
|||
return Date();
|
||||
}
|
||||
|
||||
return Date(value);
|
||||
return Date(JS::TimeClip(value));
|
||||
}
|
||||
|
||||
int64_t
|
||||
|
|
|
@ -231,8 +231,6 @@ StructuredCloneHelper::Write(JSContext* aCx,
|
|||
if (!StructuredCloneHelperInternal::Write(aCx, aValue, aTransfer)) {
|
||||
aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
|
||||
}
|
||||
|
||||
mTransferringPort.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -492,11 +490,6 @@ StructuredCloneHelper::WriteTransferCallback(JSContext* aCx,
|
|||
MessagePortBase* port = nullptr;
|
||||
nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
if (mTransferringPort.Contains(port)) {
|
||||
// No duplicates.
|
||||
return false;
|
||||
}
|
||||
|
||||
// We use aExtraData to store the index of this new port identifier.
|
||||
*aExtraData = mPortIdentifiers.Length();
|
||||
MessagePortIdentifier* identifier = mPortIdentifiers.AppendElement();
|
||||
|
@ -505,8 +498,6 @@ StructuredCloneHelper::WriteTransferCallback(JSContext* aCx,
|
|||
return false;
|
||||
}
|
||||
|
||||
mTransferringPort.AppendElement(port);
|
||||
|
||||
*aTag = SCTAG_DOM_MAP_MESSAGEPORT;
|
||||
*aOwnership = JS::SCTAG_TMO_CUSTOM;
|
||||
*aContent = nullptr;
|
||||
|
|
|
@ -233,11 +233,6 @@ private:
|
|||
// outside that method. For this reason it's a raw pointer.
|
||||
nsISupports* MOZ_NON_OWNING_REF mParent;
|
||||
|
||||
// This hashtable contains the ports while doing write (transferring and
|
||||
// mapping transferred objects to the objects in the clone). It's an empty
|
||||
// array outside the 'Write()' method.
|
||||
nsTArray<nsRefPtr<MessagePortBase>> mTransferringPort;
|
||||
|
||||
// This array contains the ports once we've finished the reading. It's
|
||||
// generated from the mPortIdentifiers array.
|
||||
nsTArray<nsRefPtr<MessagePortBase>> mTransferredPorts;
|
||||
|
|
|
@ -142,6 +142,24 @@ function runTests(obj) {
|
|||
});
|
||||
})
|
||||
|
||||
// no dup transfering
|
||||
.then(function() {
|
||||
if (!obj.transferableObjects) {
|
||||
return;
|
||||
}
|
||||
|
||||
// MessagePort
|
||||
return new Promise(function(r, rr) {
|
||||
var mc = new MessageChannel();
|
||||
obj.send(42, [mc.port1, mc.port1]).then(function(received) {
|
||||
ok(false, "Duplicate ports should throw!");
|
||||
}, function() {
|
||||
ok(true, "Duplicate ports should throw!");
|
||||
})
|
||||
.then(r);
|
||||
});
|
||||
})
|
||||
|
||||
// non transfering tests
|
||||
.then(function() {
|
||||
if (obj.transferableObjects) {
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
|
||||
#include "mozilla/dom/Date.h"
|
||||
|
||||
#include "jsapi.h" // for JS_ObjectIsDate, JS_NewDateObjectMsec
|
||||
#include "jsapi.h" // for JS_ObjectIsDate
|
||||
#include "jsfriendapi.h" // for DateGetMsecSinceEpoch
|
||||
#include "js/Date.h" // for JS::NewDateObject, JS::ClippedTime, JS::TimeClip
|
||||
#include "js/RootingAPI.h" // for Rooted, MutableHandle
|
||||
#include "js/Value.h" // for Value
|
||||
#include "mozilla/FloatingPoint.h" // for IsNaN, UnspecifiedNaN
|
||||
|
@ -15,30 +16,22 @@
|
|||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
Date::Date()
|
||||
: mMsecSinceEpoch(UnspecifiedNaN<double>())
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
Date::IsUndefined() const
|
||||
{
|
||||
return IsNaN(mMsecSinceEpoch);
|
||||
}
|
||||
|
||||
bool
|
||||
Date::SetTimeStamp(JSContext* aCx, JSObject* aObject)
|
||||
{
|
||||
JS::Rooted<JSObject*> obj(aCx, aObject);
|
||||
MOZ_ASSERT(JS_ObjectIsDate(aCx, obj));
|
||||
mMsecSinceEpoch = js::DateGetMsecSinceEpoch(aCx, obj);
|
||||
double msecs = js::DateGetMsecSinceEpoch(aCx, obj);
|
||||
JS::ClippedTime time = JS::TimeClip(msecs);
|
||||
MOZ_ASSERT(NumbersAreIdentical(msecs, time.toDouble()));
|
||||
mMsecSinceEpoch = time;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Date::ToDateObject(JSContext* aCx, JS::MutableHandle<JS::Value> aRval) const
|
||||
{
|
||||
JSObject* obj = JS_NewDateObjectMsec(aCx, mMsecSinceEpoch);
|
||||
JSObject* obj = JS::NewDateObject(aCx, mMsecSinceEpoch);
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#ifndef mozilla_dom_Date_h
|
||||
#define mozilla_dom_Date_h
|
||||
|
||||
#include "js/Date.h"
|
||||
#include "js/TypeDecls.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -17,21 +18,33 @@ namespace dom {
|
|||
class Date
|
||||
{
|
||||
public:
|
||||
// Not inlining much here to avoid the includes we'd need.
|
||||
Date();
|
||||
explicit Date(double aMilliseconds)
|
||||
Date() {}
|
||||
explicit Date(JS::ClippedTime aMilliseconds)
|
||||
: mMsecSinceEpoch(aMilliseconds)
|
||||
{}
|
||||
|
||||
bool IsUndefined() const;
|
||||
double TimeStamp() const
|
||||
bool IsUndefined() const
|
||||
{
|
||||
return !mMsecSinceEpoch.isValid();
|
||||
}
|
||||
|
||||
JS::ClippedTime TimeStamp() const
|
||||
{
|
||||
return mMsecSinceEpoch;
|
||||
}
|
||||
void SetTimeStamp(double aMilliseconds)
|
||||
|
||||
// Returns an integer in the range [-8.64e15, +8.64e15] (-0 excluded), *or*
|
||||
// returns NaN. DO NOT ASSUME THIS IS FINITE!
|
||||
double ToDouble() const
|
||||
{
|
||||
return mMsecSinceEpoch.toDouble();
|
||||
}
|
||||
|
||||
void SetTimeStamp(JS::ClippedTime aMilliseconds)
|
||||
{
|
||||
mMsecSinceEpoch = aMilliseconds;
|
||||
}
|
||||
|
||||
// Can return false if CheckedUnwrap fails. This will NOT throw;
|
||||
// callers should do it as needed.
|
||||
bool SetTimeStamp(JSContext* aCx, JSObject* aObject);
|
||||
|
@ -39,7 +52,7 @@ public:
|
|||
bool ToDateObject(JSContext* aCx, JS::MutableHandle<JS::Value> aRval) const;
|
||||
|
||||
private:
|
||||
double mMsecSinceEpoch;
|
||||
JS::ClippedTime mMsecSinceEpoch;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -4319,7 +4319,7 @@ nsDOMDeviceStorage::EnumerateInternal(const nsAString& aPath,
|
|||
|
||||
PRTime since = 0;
|
||||
if (aOptions.mSince.WasPassed() && !aOptions.mSince.Value().IsUndefined()) {
|
||||
since = PRTime(aOptions.mSince.Value().TimeStamp());
|
||||
since = PRTime(aOptions.mSince.Value().TimeStamp().toDouble());
|
||||
}
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "js/Value.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "jsapi.h"
|
||||
#include "js/Date.h"
|
||||
#include "mozilla/dom/FileModeBinding.h"
|
||||
#include "nsDebug.h"
|
||||
#include "nsIFileStreams.h"
|
||||
|
@ -49,7 +50,7 @@ MetadataHelper::GetSuccessResult(JSContext* aCx,
|
|||
|
||||
if (mParams->LastModifiedRequested()) {
|
||||
double msec = mParams->LastModified();
|
||||
JSObject *date = JS_NewDateObjectMsec(aCx, msec);
|
||||
JSObject *date = JS::NewDateObject(aCx, JS::TimeClip(msec));
|
||||
NS_ENSURE_TRUE(date, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
JS::Rooted<JS::Value> dateRoot(aCx, JS::ObjectValue(*date));
|
||||
|
|
|
@ -1514,12 +1514,12 @@ HTMLInputElement::ConvertStringToNumber(nsAString& aValue,
|
|||
return false;
|
||||
}
|
||||
|
||||
double date = JS::MakeDate(year, month - 1, day);
|
||||
if (IsNaN(date)) {
|
||||
JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
|
||||
if (!time.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aResultValue = Decimal::fromDouble(date);
|
||||
aResultValue = Decimal::fromDouble(time.toDouble());
|
||||
return true;
|
||||
}
|
||||
case NS_FORM_INPUT_TIME:
|
||||
|
@ -1762,7 +1762,8 @@ HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
|
|||
return Nullable<Date>();
|
||||
}
|
||||
|
||||
return Nullable<Date>(Date(JS::MakeDate(year, month - 1, day)));
|
||||
JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
|
||||
return Nullable<Date>(Date(time));
|
||||
}
|
||||
case NS_FORM_INPUT_TIME:
|
||||
{
|
||||
|
@ -1773,7 +1774,11 @@ HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
|
|||
return Nullable<Date>();
|
||||
}
|
||||
|
||||
return Nullable<Date>(Date(millisecond));
|
||||
JS::ClippedTime time = JS::TimeClip(millisecond);
|
||||
MOZ_ASSERT(time.toDouble() == millisecond,
|
||||
"HTML times are restricted to the day after the epoch and "
|
||||
"never clip");
|
||||
return Nullable<Date>(Date(time));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1795,7 +1800,7 @@ HTMLInputElement::SetValueAsDate(Nullable<Date> aDate, ErrorResult& aRv)
|
|||
return;
|
||||
}
|
||||
|
||||
SetValue(Decimal::fromDouble(aDate.Value().TimeStamp()));
|
||||
SetValue(Decimal::fromDouble(aDate.Value().TimeStamp().toDouble()));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "IndexedDatabaseInlines.h"
|
||||
#include "IndexedDatabaseManager.h"
|
||||
#include "js/Class.h"
|
||||
#include "js/Date.h"
|
||||
#include "js/StructuredClone.h"
|
||||
#include "KeyPath.h"
|
||||
#include "mozilla/Endian.h"
|
||||
|
@ -749,8 +750,8 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> date(aCx,
|
||||
JS_NewDateObjectMsec(aCx, aData.lastModifiedDate));
|
||||
JS::ClippedTime time = JS::TimeClip(aData.lastModifiedDate);
|
||||
JS::Rooted<JSObject*> date(aCx, JS::NewDateObject(aCx, time));
|
||||
if (NS_WARN_IF(!date)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Key.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include "js/Date.h"
|
||||
#include "js/Value.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "mozilla/Endian.h"
|
||||
|
@ -237,7 +238,11 @@ Key::DecodeJSValInternal(const unsigned char*& aPos, const unsigned char* aEnd,
|
|||
}
|
||||
else if (*aPos - aTypeOffset == eDate) {
|
||||
double msec = static_cast<double>(DecodeNumber(aPos, aEnd));
|
||||
JSObject* date = JS_NewDateObjectMsec(aCx, msec);
|
||||
JS::ClippedTime time = JS::TimeClip(msec);
|
||||
MOZ_ASSERT(msec == time.toDouble(),
|
||||
"encoding from a Date object not containing an invalid date "
|
||||
"means we should always have clipped values");
|
||||
JSObject* date = JS::NewDateObject(aCx, time);
|
||||
if (!date) {
|
||||
IDB_WARNING("Failed to make date!");
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
|
|
|
@ -429,8 +429,8 @@ public:
|
|||
}
|
||||
T firstEnd = std::max(mIntervals[0].mStart, aInterval.mStart);
|
||||
T secondStart = std::min(mIntervals.LastElement().mEnd, aInterval.mEnd);
|
||||
ElemType startInterval(mIntervals[0].mStart, firstEnd, aInterval.mFuzz);
|
||||
ElemType endInterval(secondStart, mIntervals.LastElement().mEnd, aInterval.mFuzz);
|
||||
ElemType startInterval(mIntervals[0].mStart, firstEnd);
|
||||
ElemType endInterval(secondStart, mIntervals.LastElement().mEnd);
|
||||
SelfType intervals(Move(startInterval));
|
||||
intervals += Move(endInterval);
|
||||
return Intersection(intervals);
|
||||
|
|
|
@ -89,6 +89,7 @@ MediaFormatReader::Shutdown()
|
|||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
mDemuxerInitRequest.DisconnectIfExists();
|
||||
mDecodersInitRequest.DisconnectIfExists();
|
||||
mMetadataPromise.RejectIfExists(ReadMetadataFailureReason::METADATA_ERROR, __func__);
|
||||
mSeekPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
|
||||
mSkipRequest.DisconnectIfExists();
|
||||
|
@ -270,19 +271,19 @@ MediaFormatReader::AsyncReadMetadata()
|
|||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
nsRefPtr<MetadataPromise> p = mMetadataPromise.Ensure(__func__);
|
||||
|
||||
if (mInitDone) {
|
||||
// We are returning from dormant.
|
||||
if (!EnsureDecodersSetup()) {
|
||||
return MetadataPromise::CreateAndReject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
|
||||
if (!EnsureDecodersCreated()) {
|
||||
mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
|
||||
return p;
|
||||
}
|
||||
nsRefPtr<MetadataHolder> metadata = new MetadataHolder();
|
||||
metadata->mInfo = mInfo;
|
||||
metadata->mTags = nullptr;
|
||||
return MetadataPromise::CreateAndResolve(metadata, __func__);
|
||||
MOZ_ASSERT(!mDecodersInitRequest.Exists());
|
||||
EnsureDecodersInitialized();
|
||||
return p;
|
||||
}
|
||||
|
||||
nsRefPtr<MetadataPromise> p = mMetadataPromise.Ensure(__func__);
|
||||
|
||||
mDemuxerInitRequest.Begin(mDemuxer->Init()
|
||||
->Then(OwnerThread(), __func__, this,
|
||||
&MediaFormatReader::OnDemuxerInitDone,
|
||||
|
@ -372,16 +373,24 @@ MediaFormatReader::OnDemuxerInitDone(nsresult)
|
|||
MOZ_ASSERT(mAudioTrackDemuxer);
|
||||
}
|
||||
|
||||
mInitDone = true;
|
||||
|
||||
if (!IsWaitingOnCDMResource() && !EnsureDecodersSetup()) {
|
||||
mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
|
||||
} else {
|
||||
if (IsWaitingOnCDMResource()) {
|
||||
// Decoder can't be allocated before CDM resource is ready, so resolving the
|
||||
// mMetadataPromise here to wait for CDM on MDSM.
|
||||
mInitDone = true;
|
||||
nsRefPtr<MetadataHolder> metadata = new MetadataHolder();
|
||||
metadata->mInfo = mInfo;
|
||||
metadata->mTags = nullptr;
|
||||
mMetadataPromise.Resolve(metadata, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EnsureDecodersCreated()) {
|
||||
mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!mDecodersInitRequest.Exists());
|
||||
EnsureDecodersInitialized();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -396,10 +405,9 @@ MediaFormatReader::OnDemuxerInitFailed(DemuxerFailureReason aFailure)
|
|||
}
|
||||
|
||||
bool
|
||||
MediaFormatReader::EnsureDecodersSetup()
|
||||
MediaFormatReader::EnsureDecodersCreated()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
MOZ_ASSERT(mInitDone);
|
||||
|
||||
if (!mPlatform) {
|
||||
if (IsEncrypted()) {
|
||||
|
@ -439,6 +447,7 @@ MediaFormatReader::EnsureDecodersSetup()
|
|||
NS_ENSURE_TRUE(IsSupportedAudioMimeType(mInfo.mAudio.mMimeType),
|
||||
false);
|
||||
|
||||
mAudio.mDecoderInitialized = false;
|
||||
mAudio.mDecoder =
|
||||
mPlatform->CreateDecoder(mAudio.mInfo ?
|
||||
*mAudio.mInfo->GetAsAudioInfo() :
|
||||
|
@ -446,14 +455,13 @@ MediaFormatReader::EnsureDecodersSetup()
|
|||
mAudio.mTaskQueue,
|
||||
mAudio.mCallback);
|
||||
NS_ENSURE_TRUE(mAudio.mDecoder != nullptr, false);
|
||||
nsresult rv = mAudio.mDecoder->Init();
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
}
|
||||
|
||||
if (HasVideo() && !mVideo.mDecoder) {
|
||||
NS_ENSURE_TRUE(IsSupportedVideoMimeType(mInfo.mVideo.mMimeType),
|
||||
false);
|
||||
|
||||
mVideo.mDecoderInitialized = false;
|
||||
if (mSharedDecoderManager &&
|
||||
mPlatform->SupportsSharedDecoders(mInfo.mVideo)) {
|
||||
mVideo.mDecoder =
|
||||
|
@ -476,13 +484,86 @@ MediaFormatReader::EnsureDecodersSetup()
|
|||
mDecoder->GetImageContainer());
|
||||
}
|
||||
NS_ENSURE_TRUE(mVideo.mDecoder != nullptr, false);
|
||||
nsresult rv = mVideo.mDecoder->Init();
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
MediaFormatReader::EnsureDecodersInitialized()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
MOZ_ASSERT(mVideo.mDecoder || mAudio.mDecoder);
|
||||
|
||||
// DecodeDemuxedSample() could call this function before mDecodersInitRequest
|
||||
// is completed. And it is ok to return false here because DecodeDemuxedSample
|
||||
// will call ScheduleUpdate() again.
|
||||
// It also avoids calling decoder->Init() multiple times.
|
||||
if (mDecodersInitRequest.Exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsTArray<nsRefPtr<MediaDataDecoder::InitPromise>> promises;
|
||||
|
||||
if (mVideo.mDecoder && !mVideo.mDecoderInitialized) {
|
||||
promises.AppendElement(mVideo.mDecoder->Init());
|
||||
}
|
||||
|
||||
if (mAudio.mDecoder && !mAudio.mDecoderInitialized) {
|
||||
promises.AppendElement(mAudio.mDecoder->Init());
|
||||
}
|
||||
|
||||
if (promises.Length()) {
|
||||
mDecodersInitRequest.Begin(MediaDataDecoder::InitPromise::All(OwnerThread(), promises)
|
||||
->Then(OwnerThread(), __func__, this,
|
||||
&MediaFormatReader::OnDecoderInitDone,
|
||||
&MediaFormatReader::OnDecoderInitFailed));
|
||||
}
|
||||
|
||||
LOG("Init decoders: audio: %p, audio init: %d, video: %p, video init: %d",
|
||||
mAudio.mDecoder.get(), mAudio.mDecoderInitialized,
|
||||
mVideo.mDecoder.get(), mVideo.mDecoderInitialized);
|
||||
|
||||
// Return false if any decoder is under initialization.
|
||||
return !promises.Length();
|
||||
}
|
||||
|
||||
void
|
||||
MediaFormatReader::OnDecoderInitDone(const nsTArray<TrackType>& aTrackTypes)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
mDecodersInitRequest.Complete();
|
||||
|
||||
for (const auto& track : aTrackTypes) {
|
||||
auto& decoder = GetDecoderData(track);
|
||||
decoder.mDecoderInitialized = true;
|
||||
|
||||
ScheduleUpdate(track);
|
||||
}
|
||||
|
||||
if (!mMetadataPromise.IsEmpty()) {
|
||||
mInitDone = true;
|
||||
nsRefPtr<MetadataHolder> metadata = new MetadataHolder();
|
||||
metadata->mInfo = mInfo;
|
||||
metadata->mTags = nullptr;
|
||||
mMetadataPromise.Resolve(metadata, __func__);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaFormatReader::OnDecoderInitFailed(MediaDataDecoder::DecoderFailureReason aReason)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
mDecodersInitRequest.Complete();
|
||||
|
||||
NS_WARNING("Failed to init decoder");
|
||||
|
||||
mMetadataPromise.RejectIfExists(ReadMetadataFailureReason::METADATA_ERROR, __func__);
|
||||
|
||||
NotifyError(TrackType::kAudioTrack);
|
||||
NotifyError(TrackType::kVideoTrack);
|
||||
}
|
||||
|
||||
void
|
||||
MediaFormatReader::ReadUpdatedMetadata(MediaInfo* aInfo)
|
||||
{
|
||||
|
@ -509,7 +590,7 @@ MediaFormatReader::DisableHardwareAcceleration()
|
|||
Flush(TrackInfo::kVideoTrack);
|
||||
mVideo.mDecoder->Shutdown();
|
||||
mVideo.mDecoder = nullptr;
|
||||
if (!EnsureDecodersSetup()) {
|
||||
if (!EnsureDecodersCreated()) {
|
||||
LOG("Unable to re-create decoder, aborting");
|
||||
NotifyError(TrackInfo::kVideoTrack);
|
||||
return;
|
||||
|
@ -559,13 +640,6 @@ MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe,
|
|||
return VideoDataPromise::CreateAndReject(CANCELED, __func__);
|
||||
}
|
||||
|
||||
if (!EnsureDecodersSetup()) {
|
||||
NS_WARNING("Error constructing decoders");
|
||||
return VideoDataPromise::CreateAndReject(DECODE_ERROR, __func__);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(HasVideo() && mPlatform && mVideo.mDecoder);
|
||||
|
||||
mVideo.mForceDecodeAhead = aForceDecodeAhead;
|
||||
media::TimeUnit timeThreshold{media::TimeUnit::FromMicroseconds(aTimeThreshold)};
|
||||
if (ShouldSkip(aSkipToNextKeyframe, timeThreshold)) {
|
||||
|
@ -657,13 +731,9 @@ MediaFormatReader::RequestAudioData()
|
|||
return AudioDataPromise::CreateAndReject(CANCELED, __func__);
|
||||
}
|
||||
|
||||
if (!EnsureDecodersSetup()) {
|
||||
NS_WARNING("Error constructing decoders");
|
||||
return AudioDataPromise::CreateAndReject(DECODE_ERROR, __func__);
|
||||
}
|
||||
|
||||
nsRefPtr<AudioDataPromise> p = mAudio.mPromise.Ensure(__func__);
|
||||
ScheduleUpdate(TrackInfo::kAudioTrack);
|
||||
ScheduleUpdate(TrackType::kAudioTrack);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
@ -877,6 +947,17 @@ MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack,
|
|||
return;
|
||||
}
|
||||
|
||||
if (!EnsureDecodersCreated()) {
|
||||
NS_WARNING("Error constructing decoders");
|
||||
NotifyError(aTrack);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EnsureDecodersInitialized()) {
|
||||
ScheduleUpdate(aTrack);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGV("Giving %s input to decoder", TrackTypeToStr(aTrack));
|
||||
|
||||
// Decode all our demuxed frames.
|
||||
|
@ -912,15 +993,9 @@ MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack,
|
|||
Flush(aTrack);
|
||||
decoder.mDecoder->Shutdown();
|
||||
decoder.mDecoder = nullptr;
|
||||
if (!EnsureDecodersSetup()) {
|
||||
LOG("Unable to re-create decoder, aborting");
|
||||
NotifyError(aTrack);
|
||||
return;
|
||||
}
|
||||
LOGV("%s decoder:%p created for sid:%u",
|
||||
TrackTypeToStr(aTrack), decoder.mDecoder.get(), info->GetID());
|
||||
if (sample->mKeyframe) {
|
||||
decoder.mQueuedSamples.MoveElementsFrom(samples);
|
||||
ScheduleUpdate(aTrack);
|
||||
} else {
|
||||
MOZ_ASSERT(decoder.mTimeThreshold.isNothing());
|
||||
LOG("Stream change occurred on a non-keyframe. Seeking to:%lld",
|
||||
|
@ -953,8 +1028,8 @@ MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack,
|
|||
}
|
||||
decoder.mTimeThreshold.reset();
|
||||
}));
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
LOGV("Input:%lld (dts:%lld kf:%d)",
|
||||
|
@ -1343,7 +1418,8 @@ MediaFormatReader::Seek(int64_t aTime, int64_t aUnused)
|
|||
return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
mPendingSeekTime.emplace(media::TimeUnit::FromMicroseconds(aTime));
|
||||
mOriginalSeekTime = Some(media::TimeUnit::FromMicroseconds(aTime));
|
||||
mPendingSeekTime = mOriginalSeekTime;
|
||||
|
||||
nsRefPtr<SeekPromise> p = mSeekPromise.Ensure(__func__);
|
||||
|
||||
|
@ -1377,6 +1453,34 @@ MediaFormatReader::OnSeekFailed(TrackType aTrack, DemuxerFailureReason aResult)
|
|||
}
|
||||
|
||||
if (aResult == DemuxerFailureReason::WAITING_FOR_DATA) {
|
||||
if (HasVideo() && aTrack == TrackType::kAudioTrack &&
|
||||
mOriginalSeekTime.isSome() &&
|
||||
mPendingSeekTime.ref() != mOriginalSeekTime.ref()) {
|
||||
// We have failed to seek audio where video seeked to earlier.
|
||||
// Attempt to seek instead to the closest point that we know we have in
|
||||
// order to limit A/V sync discrepency.
|
||||
|
||||
// Ensure we have the most up to date buffered ranges.
|
||||
UpdateReceivedNewData(TrackType::kAudioTrack);
|
||||
Maybe<media::TimeUnit> nextSeekTime;
|
||||
// Find closest buffered time found after video seeked time.
|
||||
for (const auto& timeRange : mAudio.mTimeRanges) {
|
||||
if (timeRange.mStart >= mPendingSeekTime.ref()) {
|
||||
nextSeekTime.emplace(timeRange.mStart);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nextSeekTime.isNothing() ||
|
||||
nextSeekTime.ref() > mOriginalSeekTime.ref()) {
|
||||
nextSeekTime = mOriginalSeekTime;
|
||||
LOG("Unable to seek audio to video seek time. A/V sync may be broken");
|
||||
} else {
|
||||
mOriginalSeekTime.reset();
|
||||
}
|
||||
mPendingSeekTime = nextSeekTime;
|
||||
DoAudioSeek();
|
||||
return;
|
||||
}
|
||||
NotifyWaitingForData(aTrack);
|
||||
return;
|
||||
}
|
||||
|
@ -1406,6 +1510,7 @@ MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime)
|
|||
|
||||
if (HasAudio()) {
|
||||
MOZ_ASSERT(mPendingSeekTime.isSome());
|
||||
mPendingSeekTime = Some(aTime);
|
||||
DoAudioSeek();
|
||||
} else {
|
||||
mPendingSeekTime.reset();
|
||||
|
|
|
@ -105,7 +105,10 @@ private:
|
|||
void NotifyDemuxer(uint32_t aLength, int64_t aOffset);
|
||||
void ReturnOutput(MediaData* aData, TrackType aTrack);
|
||||
|
||||
bool EnsureDecodersSetup();
|
||||
bool EnsureDecodersCreated();
|
||||
// It returns true when all decoders are initialized. False when there is pending
|
||||
// initialization.
|
||||
bool EnsureDecodersInitialized();
|
||||
|
||||
// Enqueues a task to call Update(aTrack) on the decoder task queue.
|
||||
// Lock for corresponding track must be held.
|
||||
|
@ -194,6 +197,7 @@ private:
|
|||
, mWaitingForData(false)
|
||||
, mReceivedNewData(false)
|
||||
, mDiscontinuity(true)
|
||||
, mDecoderInitialized(false)
|
||||
, mOutputRequested(false)
|
||||
, mInputExhausted(false)
|
||||
, mError(false)
|
||||
|
@ -242,6 +246,8 @@ private:
|
|||
}
|
||||
|
||||
// MediaDataDecoder handler's variables.
|
||||
// False when decoder is created. True when decoder Init() promise is resolved.
|
||||
bool mDecoderInitialized;
|
||||
bool mOutputRequested;
|
||||
bool mInputExhausted;
|
||||
bool mError;
|
||||
|
@ -337,6 +343,9 @@ private:
|
|||
|
||||
DecoderData& GetDecoderData(TrackType aTrack);
|
||||
|
||||
void OnDecoderInitDone(const nsTArray<TrackType>& aTrackTypes);
|
||||
void OnDecoderInitFailed(MediaDataDecoder::DecoderFailureReason aReason);
|
||||
|
||||
// Demuxer objects.
|
||||
void OnDemuxerInitDone(nsresult);
|
||||
void OnDemuxerInitFailed(DemuxerFailureReason aFailure);
|
||||
|
@ -408,9 +417,13 @@ private:
|
|||
OnSeekFailed(TrackType::kAudioTrack, aFailure);
|
||||
}
|
||||
// Temporary seek information while we wait for the data
|
||||
Maybe<media::TimeUnit> mOriginalSeekTime;
|
||||
Maybe<media::TimeUnit> mPendingSeekTime;
|
||||
MozPromiseHolder<SeekPromise> mSeekPromise;
|
||||
|
||||
// Pending decoders initialization.
|
||||
MozPromiseRequestHolder<MediaDataDecoder::InitPromise::AllPromiseType> mDecodersInitRequest;
|
||||
|
||||
#ifdef MOZ_EME
|
||||
nsRefPtr<CDMProxy> mCDMProxy;
|
||||
#endif
|
||||
|
|
|
@ -278,8 +278,6 @@ CreateTestH264Decoder(layers::LayersBackend aBackend,
|
|||
if (!decoder) {
|
||||
return nullptr;
|
||||
}
|
||||
nsresult rv = decoder->Init();
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
return decoder.forget();
|
||||
}
|
||||
|
@ -293,7 +291,6 @@ MP4Decoder::IsVideoAccelerated(layers::LayersBackend aBackend)
|
|||
return false;
|
||||
}
|
||||
bool result = decoder->IsHardwareAccelerated();
|
||||
decoder->Shutdown();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -336,8 +333,6 @@ CreateTestAACDecoder(AudioInfo& aConfig)
|
|||
if (!decoder) {
|
||||
return nullptr;
|
||||
}
|
||||
nsresult rv = decoder->Init();
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
return decoder.forget();
|
||||
}
|
||||
|
@ -376,7 +371,6 @@ MP4Decoder::CanCreateAACDecoder()
|
|||
MOZ_ARRAY_LENGTH(sTestAACExtraData));
|
||||
nsRefPtr<MediaDataDecoder> decoder(CreateTestAACDecoder(config));
|
||||
if (decoder) {
|
||||
decoder->Shutdown();
|
||||
result = true;
|
||||
}
|
||||
haveCachedResult = true;
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include "gmp-video-encode.h"
|
||||
#include "GMPPlatform.h"
|
||||
#include "mozilla/dom/CrashReporterChild.h"
|
||||
#include "mozilla/Tokenizer.h"
|
||||
#include "GMPUtils.h"
|
||||
#include "prio.h"
|
||||
|
||||
using mozilla::dom::CrashReporterChild;
|
||||
|
@ -341,24 +341,6 @@ ReadIntoString(nsIFile* aFile,
|
|||
return rv;
|
||||
}
|
||||
|
||||
static nsTArray<nsCString>
|
||||
SplitAt(Tokenizer::Token aDelim, const nsACString& aInput)
|
||||
{
|
||||
nsTArray<nsCString> tokens;
|
||||
Tokenizer tokenizer(aInput);
|
||||
|
||||
while (!tokenizer.HasFailed()) {
|
||||
tokenizer.Record();
|
||||
Tokenizer::Token token;
|
||||
while (tokenizer.Next(token) && !token.Equals(aDelim))
|
||||
; // Skip up to next delimeter, or EOF.
|
||||
nsAutoCString value;
|
||||
tokenizer.Claim(value);
|
||||
tokens.AppendElement(value);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// Pre-load DLLs that need to be used by the EME plugin but that can't be
|
||||
// loaded after the sandbox has started
|
||||
bool
|
||||
|
@ -387,7 +369,10 @@ GMPChild::PreLoadLibraries(const nsAString& aPluginPath)
|
|||
return false;
|
||||
}
|
||||
|
||||
nsTArray<nsCString> lines = SplitAt(Tokenizer::Token::NewLine(), info);
|
||||
// Note: we pass "\r\n" to SplitAt so that we'll split lines delimited
|
||||
// by \n (Unix), \r\n (Windows) and \r (old MacOSX).
|
||||
nsTArray<nsCString> lines;
|
||||
SplitAt("\r\n", info, lines);
|
||||
for (nsCString line : lines) {
|
||||
// Make lowercase.
|
||||
std::transform(line.BeginWriting(),
|
||||
|
@ -401,8 +386,8 @@ GMPChild::PreLoadLibraries(const nsAString& aPluginPath)
|
|||
continue;
|
||||
}
|
||||
// Line starts with "libraries:".
|
||||
nsTArray<nsCString> libs = SplitAt(Tokenizer::Token::Char(','),
|
||||
Substring(line, offset + strlen(libraries)));
|
||||
nsTArray<nsCString> libs;
|
||||
SplitAt(",", Substring(line, offset + strlen(libraries)), libs);
|
||||
for (nsCString lib : libs) {
|
||||
lib.Trim(" ");
|
||||
for (const char* whiteListedLib : whitelist) {
|
||||
|
|
|
@ -158,8 +158,9 @@ CreateRecord(const char* aRecordName,
|
|||
GMPRecord** aOutRecord,
|
||||
GMPRecordClient* aClient)
|
||||
{
|
||||
if (aRecordNameSize > GMP_MAX_RECORD_NAME_SIZE) {
|
||||
NS_WARNING("GMP tried to CreateRecord with too long record name");
|
||||
if (aRecordNameSize > GMP_MAX_RECORD_NAME_SIZE ||
|
||||
aRecordNameSize == 0) {
|
||||
NS_WARNING("GMP tried to CreateRecord with too long or 0 record name");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
GMPStorageChild* storage = sChild->GetGMPStorage();
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "GMPStorageParent.h"
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
#include "plhash.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
|
@ -12,8 +11,8 @@
|
|||
#include "GMPParent.h"
|
||||
#include "gmp-storage.h"
|
||||
#include "mozilla/unused.h"
|
||||
#include "nsTHashtable.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "mozilla/Endian.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "prio.h"
|
||||
#include "mozIGeckoMediaPluginService.h"
|
||||
#include "nsContentCID.h"
|
||||
|
@ -84,244 +83,43 @@ GetGMPStorageDir(nsIFile** aTempDir, const nsCString& aNodeId)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
enum OpenFileMode { ReadWrite, Truncate };
|
||||
|
||||
static nsresult
|
||||
OpenStorageFile(const nsCString& aRecordName,
|
||||
const nsCString& aNodeId,
|
||||
const OpenFileMode aMode,
|
||||
PRFileDesc** aOutFD)
|
||||
{
|
||||
MOZ_ASSERT(aOutFD);
|
||||
|
||||
nsCOMPtr<nsIFile> f;
|
||||
nsresult rv = GetGMPStorageDir(getter_AddRefs(f), aNodeId);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoString recordNameHash;
|
||||
recordNameHash.AppendInt(HashString(aRecordName.get()));
|
||||
f->Append(recordNameHash);
|
||||
|
||||
auto mode = PR_RDWR | PR_CREATE_FILE;
|
||||
if (aMode == Truncate) {
|
||||
mode |= PR_TRUNCATE;
|
||||
}
|
||||
|
||||
return f->OpenNSPRFileDesc(mode, PR_IRWXU, aOutFD);
|
||||
}
|
||||
|
||||
static nsresult
|
||||
RemoveStorageFile(const nsCString& aRecordName,
|
||||
const nsCString& aNodeId)
|
||||
{
|
||||
nsCOMPtr<nsIFile> f;
|
||||
nsresult rv = GetGMPStorageDir(getter_AddRefs(f), aNodeId);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoString recordNameHash;
|
||||
recordNameHash.AppendInt(HashString(aRecordName.get()));
|
||||
f->Append(recordNameHash);
|
||||
|
||||
return f->Remove(/* bool recursive= */ false);
|
||||
}
|
||||
|
||||
PLDHashOperator
|
||||
CloseFile(const nsACString& key, PRFileDesc*& entry, void* cx)
|
||||
{
|
||||
if (PR_Close(entry) != PR_SUCCESS) {
|
||||
NS_WARNING("GMPDiskStorage failed to close file.");
|
||||
}
|
||||
return PL_DHASH_REMOVE;
|
||||
}
|
||||
|
||||
// Disk-backed GMP storage. Records are stored in files on disk in
|
||||
// the profile directory. The record name is a hash of the filename,
|
||||
// and we resolve hash collisions by just adding 1 to the hash code.
|
||||
// The format of records on disk is:
|
||||
// 4 byte, uint32_t $recordNameLength, in little-endian byte order,
|
||||
// record name (i.e. $recordNameLength bytes, no null terminator)
|
||||
// record bytes (entire remainder of file)
|
||||
class GMPDiskStorage : public GMPStorage {
|
||||
public:
|
||||
explicit GMPDiskStorage(const nsCString& aNodeId)
|
||||
: mNodeId(aNodeId)
|
||||
{
|
||||
}
|
||||
|
||||
~GMPDiskStorage() {
|
||||
mFiles.Enumerate(CloseFile, nullptr);
|
||||
MOZ_ASSERT(!mFiles.Count());
|
||||
}
|
||||
|
||||
virtual GMPErr Open(const nsCString& aRecordName) override
|
||||
{
|
||||
MOZ_ASSERT(!IsOpen(aRecordName));
|
||||
PRFileDesc* fd = nullptr;
|
||||
if (NS_FAILED(OpenStorageFile(aRecordName, mNodeId, ReadWrite, &fd))) {
|
||||
NS_WARNING("Failed to open storage file.");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
mFiles.Put(aRecordName, fd);
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
virtual bool IsOpen(const nsCString& aRecordName) override {
|
||||
return mFiles.Contains(aRecordName);
|
||||
}
|
||||
|
||||
static
|
||||
GMPErr ReadRecordMetadata(PRFileDesc* aFd,
|
||||
int32_t& aOutFileLength,
|
||||
int32_t& aOutRecordLength,
|
||||
nsACString& aOutRecordName)
|
||||
{
|
||||
int32_t fileLength = PR_Seek(aFd, 0, PR_SEEK_END);
|
||||
PR_Seek(aFd, 0, PR_SEEK_SET);
|
||||
|
||||
if (fileLength > GMP_MAX_RECORD_SIZE) {
|
||||
// Refuse to read big records.
|
||||
return GMPQuotaExceededErr;
|
||||
}
|
||||
aOutFileLength = fileLength;
|
||||
aOutRecordLength = 0;
|
||||
|
||||
// At the start of the file the length of the record name is stored in a
|
||||
// size_t (host byte order) followed by the record name at the start of
|
||||
// the file. The record name is not null terminated. The remainder of the
|
||||
// file is the record's data.
|
||||
|
||||
size_t recordNameLength = 0;
|
||||
if (fileLength == 0 || sizeof(recordNameLength) >= (size_t)fileLength) {
|
||||
// Record file is empty, or doesn't even have enough contents to
|
||||
// store the record name length and/or record name. Report record
|
||||
// as empty.
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
int32_t bytesRead = PR_Read(aFd, &recordNameLength, sizeof(recordNameLength));
|
||||
if (sizeof(recordNameLength) != bytesRead ||
|
||||
recordNameLength > fileLength - sizeof(recordNameLength)) {
|
||||
// Record file has invalid contents. Report record as empty.
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
nsCString recordName;
|
||||
recordName.SetLength(recordNameLength);
|
||||
bytesRead = PR_Read(aFd, recordName.BeginWriting(), recordNameLength);
|
||||
if (bytesRead != (int32_t)recordNameLength) {
|
||||
// Record file has invalid contents. Report record as empty.
|
||||
return GMPGenericErr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(fileLength > 0 && (size_t)fileLength >= sizeof(recordNameLength) + recordNameLength);
|
||||
int32_t recordLength = fileLength - (sizeof(recordNameLength) + recordNameLength);
|
||||
|
||||
aOutRecordLength = recordLength;
|
||||
aOutRecordName = recordName;
|
||||
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
virtual GMPErr Read(const nsCString& aRecordName,
|
||||
nsTArray<uint8_t>& aOutBytes) override
|
||||
{
|
||||
// Our error strategy is to report records with invalid contents as
|
||||
// containing 0 bytes. Zero length records are considered "deleted" by
|
||||
// the GMPStorage API.
|
||||
aOutBytes.SetLength(0);
|
||||
|
||||
PRFileDesc* fd = mFiles.Get(aRecordName);
|
||||
if (!fd) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
|
||||
int32_t fileLength = 0;
|
||||
int32_t recordLength = 0;
|
||||
nsCString recordName;
|
||||
GMPErr err = ReadRecordMetadata(fd,
|
||||
fileLength,
|
||||
recordLength,
|
||||
recordName);
|
||||
if (NS_WARN_IF(GMP_FAILED(err))) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (recordLength == 0) {
|
||||
// Record is empoty but not invalid, or it's invalid and we're going to
|
||||
// just act like it's empty and let the client overwrite it.
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
if (!aRecordName.Equals(recordName)) {
|
||||
NS_WARNING("Hash collision in GMPStorage");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
|
||||
// After calling ReadRecordMetadata, we should be ready to read the
|
||||
// record data.
|
||||
MOZ_ASSERT(PR_Available(fd) == recordLength);
|
||||
|
||||
aOutBytes.SetLength(recordLength);
|
||||
int32_t bytesRead = PR_Read(fd, aOutBytes.Elements(), recordLength);
|
||||
return (bytesRead == recordLength) ? GMPNoErr : GMPGenericErr;
|
||||
}
|
||||
|
||||
virtual GMPErr Write(const nsCString& aRecordName,
|
||||
const nsTArray<uint8_t>& aBytes) override
|
||||
{
|
||||
PRFileDesc* fd = mFiles.Get(aRecordName);
|
||||
if (!fd) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
|
||||
// Write operations overwrite the entire record. So close it now.
|
||||
PR_Close(fd);
|
||||
mFiles.Remove(aRecordName);
|
||||
|
||||
// Writing 0 bytes means removing (deleting) the file.
|
||||
if (aBytes.Length() == 0) {
|
||||
nsresult rv = RemoveStorageFile(aRecordName, mNodeId);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
// Could not delete file -> Continue with trying to erase the contents.
|
||||
} else {
|
||||
return GMPNoErr;
|
||||
// Close all open file handles.
|
||||
for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
Record* record = iter.UserData();
|
||||
if (record->mFileDesc) {
|
||||
PR_Close(record->mFileDesc);
|
||||
record->mFileDesc = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Write operations overwrite the entire record. So re-open the file
|
||||
// in truncate mode, to clear its contents.
|
||||
if (NS_FAILED(OpenStorageFile(aRecordName, mNodeId, Truncate, &fd))) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
mFiles.Put(aRecordName, fd);
|
||||
|
||||
// Store the length of the record name followed by the record name
|
||||
// at the start of the file.
|
||||
int32_t bytesWritten = 0;
|
||||
if (aBytes.Length() > 0) {
|
||||
size_t recordNameLength = aRecordName.Length();
|
||||
bytesWritten = PR_Write(fd, &recordNameLength, sizeof(recordNameLength));
|
||||
if (NS_WARN_IF(bytesWritten != sizeof(recordNameLength))) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
bytesWritten = PR_Write(fd, aRecordName.get(), recordNameLength);
|
||||
if (NS_WARN_IF(bytesWritten != (int32_t)recordNameLength)) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
}
|
||||
|
||||
bytesWritten = PR_Write(fd, aBytes.Elements(), aBytes.Length());
|
||||
return (bytesWritten == (int32_t)aBytes.Length()) ? GMPNoErr : GMPGenericErr;
|
||||
}
|
||||
|
||||
virtual GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) override
|
||||
{
|
||||
nsresult Init() {
|
||||
// Build our index of records on disk.
|
||||
nsCOMPtr<nsIFile> storageDir;
|
||||
nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mNodeId);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return GMPGenericErr;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISimpleEnumerator> iter;
|
||||
rv = storageDir->GetDirectoryEntries(getter_AddRefs(iter));
|
||||
if (NS_FAILED(rv)) {
|
||||
return GMPGenericErr;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
bool hasMore;
|
||||
|
@ -336,66 +134,367 @@ public:
|
|||
continue;
|
||||
}
|
||||
|
||||
nsAutoCString leafName;
|
||||
rv = dirEntry->GetNativeLeafName(leafName);
|
||||
if (NS_FAILED(rv)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PRFileDesc* fd = nullptr;
|
||||
if (NS_FAILED(dirEntry->OpenNSPRFileDesc(PR_RDONLY, 0, &fd))) {
|
||||
continue;
|
||||
}
|
||||
int32_t fileLength = 0;
|
||||
int32_t recordLength = 0;
|
||||
nsCString recordName;
|
||||
GMPErr err = ReadRecordMetadata(fd,
|
||||
fileLength,
|
||||
recordLength,
|
||||
recordName);
|
||||
nsresult err = ReadRecordMetadata(fd, recordLength, recordName);
|
||||
PR_Close(fd);
|
||||
if (NS_WARN_IF(GMP_FAILED(err))) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (recordName.IsEmpty() || recordLength == 0) {
|
||||
if (NS_FAILED(err)) {
|
||||
// File is not a valid storage file. Don't index it. Delete the file,
|
||||
// to make our indexing faster in future.
|
||||
dirEntry->Remove(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure the file name is the hash of the record name stored in the
|
||||
// record file. Otherwise it's not a valid record.
|
||||
nsAutoCString recordNameHash;
|
||||
recordNameHash.AppendInt(HashString(recordName.get()));
|
||||
if (!recordNameHash.Equals(leafName)) {
|
||||
nsAutoString filename;
|
||||
rv = dirEntry->GetLeafName(filename);
|
||||
if (NS_FAILED(rv)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
aOutRecordNames.AppendElement(recordName);
|
||||
mRecords.Put(recordName, new Record(filename, recordName));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
GMPErr Open(const nsCString& aRecordName) override
|
||||
{
|
||||
MOZ_ASSERT(!IsOpen(aRecordName));
|
||||
nsresult rv;
|
||||
Record* record = nullptr;
|
||||
if (!mRecords.Get(aRecordName, &record)) {
|
||||
// New file.
|
||||
nsAutoString filename;
|
||||
rv = GetUnusedFilename(aRecordName, filename);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
record = new Record(filename, aRecordName);
|
||||
mRecords.Put(aRecordName, record);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(record);
|
||||
if (record->mFileDesc) {
|
||||
NS_WARNING("Tried to open already open record");
|
||||
return GMPRecordInUse;
|
||||
}
|
||||
|
||||
rv = OpenStorageFile(record->mFilename, ReadWrite, &record->mFileDesc);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(IsOpen(aRecordName));
|
||||
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
virtual void Close(const nsCString& aRecordName) override
|
||||
bool IsOpen(const nsCString& aRecordName) override {
|
||||
// We are open if we have a record indexed, and it has a valid
|
||||
// file descriptor.
|
||||
Record* record = nullptr;
|
||||
return mRecords.Get(aRecordName, &record) &&
|
||||
!!record->mFileDesc;
|
||||
}
|
||||
|
||||
GMPErr Read(const nsCString& aRecordName,
|
||||
nsTArray<uint8_t>& aOutBytes) override
|
||||
{
|
||||
PRFileDesc* fd = mFiles.Get(aRecordName);
|
||||
if (fd) {
|
||||
if (PR_Close(fd) == PR_SUCCESS) {
|
||||
mFiles.Remove(aRecordName);
|
||||
if (!IsOpen(aRecordName)) {
|
||||
return GMPClosedErr;
|
||||
}
|
||||
|
||||
Record* record = nullptr;
|
||||
mRecords.Get(aRecordName, &record);
|
||||
MOZ_ASSERT(record && !!record->mFileDesc); // IsOpen() guarantees this.
|
||||
|
||||
// Our error strategy is to report records with invalid contents as
|
||||
// containing 0 bytes. Zero length records are considered "deleted" by
|
||||
// the GMPStorage API.
|
||||
aOutBytes.SetLength(0);
|
||||
|
||||
int32_t recordLength = 0;
|
||||
nsCString recordName;
|
||||
nsresult err = ReadRecordMetadata(record->mFileDesc,
|
||||
recordLength,
|
||||
recordName);
|
||||
if (NS_FAILED(err) || recordLength == 0) {
|
||||
// We failed to read the record metadata. Or the record is 0 length.
|
||||
// Treat damaged records as empty.
|
||||
// ReadRecordMetadata() could fail if the GMP opened a new record and
|
||||
// tried to read it before anything was written to it..
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
if (!aRecordName.Equals(recordName)) {
|
||||
NS_WARNING("Record file contains some other record's contents!");
|
||||
return GMPRecordCorrupted;
|
||||
}
|
||||
|
||||
// After calling ReadRecordMetadata, we should be ready to read the
|
||||
// record data.
|
||||
if (PR_Available(record->mFileDesc) != recordLength) {
|
||||
NS_WARNING("Record file length mismatch!");
|
||||
return GMPRecordCorrupted;
|
||||
}
|
||||
|
||||
aOutBytes.SetLength(recordLength);
|
||||
int32_t bytesRead = PR_Read(record->mFileDesc, aOutBytes.Elements(), recordLength);
|
||||
return (bytesRead == recordLength) ? GMPNoErr : GMPRecordCorrupted;
|
||||
}
|
||||
|
||||
GMPErr Write(const nsCString& aRecordName,
|
||||
const nsTArray<uint8_t>& aBytes) override
|
||||
{
|
||||
if (!IsOpen(aRecordName)) {
|
||||
return GMPClosedErr;
|
||||
}
|
||||
|
||||
Record* record = nullptr;
|
||||
mRecords.Get(aRecordName, &record);
|
||||
MOZ_ASSERT(record && !!record->mFileDesc); // IsOpen() guarantees this.
|
||||
|
||||
// Write operations overwrite the entire record. So close it now.
|
||||
PR_Close(record->mFileDesc);
|
||||
record->mFileDesc = nullptr;
|
||||
|
||||
// Writing 0 bytes means removing (deleting) the file.
|
||||
if (aBytes.Length() == 0) {
|
||||
nsresult rv = RemoveStorageFile(record->mFilename);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
// Could not delete file -> Continue with trying to erase the contents.
|
||||
} else {
|
||||
NS_WARNING("GMPDiskStorage failed to close file.");
|
||||
return GMPNoErr;
|
||||
}
|
||||
}
|
||||
|
||||
// Write operations overwrite the entire record. So re-open the file
|
||||
// in truncate mode, to clear its contents.
|
||||
if (NS_FAILED(OpenStorageFile(record->mFilename,
|
||||
Truncate,
|
||||
&record->mFileDesc))) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
|
||||
// Store the length of the record name followed by the record name
|
||||
// at the start of the file.
|
||||
int32_t bytesWritten = 0;
|
||||
char buf[sizeof(uint32_t)] = {0};
|
||||
LittleEndian::writeUint32(buf, aRecordName.Length());
|
||||
bytesWritten = PR_Write(record->mFileDesc, buf, MOZ_ARRAY_LENGTH(buf));
|
||||
if (bytesWritten != MOZ_ARRAY_LENGTH(buf)) {
|
||||
NS_WARNING("Failed to write GMPStorage record name length.");
|
||||
return GMPRecordCorrupted;
|
||||
}
|
||||
bytesWritten = PR_Write(record->mFileDesc,
|
||||
aRecordName.get(),
|
||||
aRecordName.Length());
|
||||
if (bytesWritten != (int32_t)aRecordName.Length()) {
|
||||
NS_WARNING("Failed to write GMPStorage record name.");
|
||||
return GMPRecordCorrupted;
|
||||
}
|
||||
|
||||
bytesWritten = PR_Write(record->mFileDesc, aBytes.Elements(), aBytes.Length());
|
||||
if (bytesWritten != (int32_t)aBytes.Length()) {
|
||||
NS_WARNING("Failed to write GMPStorage record data.");
|
||||
return GMPRecordCorrupted;
|
||||
}
|
||||
|
||||
// Try to sync the file to disk, so that in the event of a crash,
|
||||
// the record is less likely to be corrupted.
|
||||
PR_Sync(record->mFileDesc);
|
||||
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) override
|
||||
{
|
||||
for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
aOutRecordNames.AppendElement(iter.UserData()->mRecordName);
|
||||
}
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
void Close(const nsCString& aRecordName) override
|
||||
{
|
||||
Record* record = nullptr;
|
||||
mRecords.Get(aRecordName, &record);
|
||||
if (record && !!record->mFileDesc) {
|
||||
PR_Close(record->mFileDesc);
|
||||
record->mFileDesc = nullptr;
|
||||
}
|
||||
MOZ_ASSERT(!IsOpen(aRecordName));
|
||||
}
|
||||
|
||||
private:
|
||||
nsDataHashtable<nsCStringHashKey, PRFileDesc*> mFiles;
|
||||
|
||||
// We store records in a file which is a hash of the record name.
|
||||
// If there is a hash collision, we just keep adding 1 to the hash
|
||||
// code, until we find a free slot.
|
||||
nsresult GetUnusedFilename(const nsACString& aRecordName,
|
||||
nsString& aOutFilename)
|
||||
{
|
||||
nsCOMPtr<nsIFile> storageDir;
|
||||
nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mNodeId);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
uint64_t recordNameHash = HashString(PromiseFlatCString(aRecordName).get());
|
||||
for (int i = 0; i < 1000000; i++) {
|
||||
nsCOMPtr<nsIFile> f;
|
||||
rv = storageDir->Clone(getter_AddRefs(f));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
nsAutoString hashStr;
|
||||
hashStr.AppendInt(recordNameHash);
|
||||
rv = f->Append(hashStr);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
bool exists = false;
|
||||
f->Exists(&exists);
|
||||
if (!exists) {
|
||||
// Filename not in use, we can write into this file.
|
||||
aOutFilename = hashStr;
|
||||
return NS_OK;
|
||||
} else {
|
||||
// Hash collision; just increment the hash name and try that again.
|
||||
++recordNameHash;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Somehow, we've managed to completely fail to find a vacant file name.
|
||||
// Give up.
|
||||
NS_WARNING("GetUnusedFilename had extreme hash collision!");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
enum OpenFileMode { ReadWrite, Truncate };
|
||||
|
||||
nsresult OpenStorageFile(const nsAString& aFileLeafName,
|
||||
const OpenFileMode aMode,
|
||||
PRFileDesc** aOutFD)
|
||||
{
|
||||
MOZ_ASSERT(aOutFD);
|
||||
|
||||
nsCOMPtr<nsIFile> f;
|
||||
nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mNodeId);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
f->Append(aFileLeafName);
|
||||
|
||||
auto mode = PR_RDWR | PR_CREATE_FILE;
|
||||
if (aMode == Truncate) {
|
||||
mode |= PR_TRUNCATE;
|
||||
}
|
||||
|
||||
return f->OpenNSPRFileDesc(mode, PR_IRWXU, aOutFD);
|
||||
}
|
||||
|
||||
nsresult ReadRecordMetadata(PRFileDesc* aFd,
|
||||
int32_t& aOutRecordLength,
|
||||
nsACString& aOutRecordName)
|
||||
{
|
||||
int32_t offset = PR_Seek(aFd, 0, PR_SEEK_END);
|
||||
PR_Seek(aFd, 0, PR_SEEK_SET);
|
||||
|
||||
if (offset < 0 || offset > GMP_MAX_RECORD_SIZE) {
|
||||
// Refuse to read big records, or records where we can't get a length.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
const uint32_t fileLength = static_cast<uint32_t>(offset);
|
||||
|
||||
// At the start of the file the length of the record name is stored in a
|
||||
// uint32_t (little endian byte order) followed by the record name at the
|
||||
// start of the file. The record name is not null terminated. The remainder
|
||||
// of the file is the record's data.
|
||||
|
||||
if (fileLength < sizeof(uint32_t)) {
|
||||
// Record file doesn't have enough contents to store the record name
|
||||
// length. Fail.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Read length, and convert to host byte order.
|
||||
uint32_t recordNameLength = 0;
|
||||
char buf[sizeof(recordNameLength)] = { 0 };
|
||||
int32_t bytesRead = PR_Read(aFd, &buf, sizeof(recordNameLength));
|
||||
recordNameLength = LittleEndian::readUint32(buf);
|
||||
if (sizeof(recordNameLength) != bytesRead ||
|
||||
recordNameLength == 0 ||
|
||||
recordNameLength + sizeof(recordNameLength) > fileLength ||
|
||||
recordNameLength > GMP_MAX_RECORD_NAME_SIZE) {
|
||||
// Record file has invalid contents. Fail.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCString recordName;
|
||||
recordName.SetLength(recordNameLength);
|
||||
bytesRead = PR_Read(aFd, recordName.BeginWriting(), recordNameLength);
|
||||
if ((uint32_t)bytesRead != recordNameLength) {
|
||||
// Read failed.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(fileLength >= sizeof(recordNameLength) + recordNameLength);
|
||||
int32_t recordLength = fileLength - (sizeof(recordNameLength) + recordNameLength);
|
||||
|
||||
aOutRecordLength = recordLength;
|
||||
aOutRecordName = recordName;
|
||||
|
||||
// Read cursor should be positioned after the record name, before the record contents.
|
||||
if (PR_Seek(aFd, 0, PR_SEEK_CUR) != (int32_t)(sizeof(recordNameLength) + recordNameLength)) {
|
||||
NS_WARNING("Read cursor mismatch after ReadRecordMetadata()");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult RemoveStorageFile(const nsString& aFilename)
|
||||
{
|
||||
nsCOMPtr<nsIFile> f;
|
||||
nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mNodeId);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
rv = f->Append(aFilename);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
return f->Remove(/* bool recursive= */ false);
|
||||
}
|
||||
|
||||
struct Record {
|
||||
Record(const nsAString& aFilename,
|
||||
const nsACString& aRecordName)
|
||||
: mFilename(aFilename)
|
||||
, mRecordName(aRecordName)
|
||||
, mFileDesc(0)
|
||||
{}
|
||||
~Record() {
|
||||
MOZ_ASSERT(!mFileDesc);
|
||||
}
|
||||
nsString mFilename;
|
||||
nsCString mRecordName;
|
||||
PRFileDesc* mFileDesc;
|
||||
};
|
||||
|
||||
// Hash record name to record data.
|
||||
nsClassHashtable<nsCStringHashKey, Record> mRecords;
|
||||
const nsAutoCString mNodeId;
|
||||
};
|
||||
|
||||
class GMPMemoryStorage : public GMPStorage {
|
||||
public:
|
||||
virtual GMPErr Open(const nsCString& aRecordName) override
|
||||
GMPErr Open(const nsCString& aRecordName) override
|
||||
{
|
||||
MOZ_ASSERT(!IsOpen(aRecordName));
|
||||
|
||||
|
@ -408,7 +507,7 @@ public:
|
|||
return GMPNoErr;
|
||||
}
|
||||
|
||||
virtual bool IsOpen(const nsCString& aRecordName) override {
|
||||
bool IsOpen(const nsCString& aRecordName) override {
|
||||
Record* record = nullptr;
|
||||
if (!mRecords.Get(aRecordName, &record)) {
|
||||
return false;
|
||||
|
@ -416,8 +515,8 @@ public:
|
|||
return record->mIsOpen;
|
||||
}
|
||||
|
||||
virtual GMPErr Read(const nsCString& aRecordName,
|
||||
nsTArray<uint8_t>& aOutBytes) override
|
||||
GMPErr Read(const nsCString& aRecordName,
|
||||
nsTArray<uint8_t>& aOutBytes) override
|
||||
{
|
||||
Record* record = nullptr;
|
||||
if (!mRecords.Get(aRecordName, &record)) {
|
||||
|
@ -427,8 +526,8 @@ public:
|
|||
return GMPNoErr;
|
||||
}
|
||||
|
||||
virtual GMPErr Write(const nsCString& aRecordName,
|
||||
const nsTArray<uint8_t>& aBytes) override
|
||||
GMPErr Write(const nsCString& aRecordName,
|
||||
const nsTArray<uint8_t>& aBytes) override
|
||||
{
|
||||
Record* record = nullptr;
|
||||
if (!mRecords.Get(aRecordName, &record)) {
|
||||
|
@ -438,13 +537,15 @@ public:
|
|||
return GMPNoErr;
|
||||
}
|
||||
|
||||
virtual GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) override
|
||||
GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) override
|
||||
{
|
||||
mRecords.EnumerateRead(EnumRecordNames, &aOutRecordNames);
|
||||
for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
aOutRecordNames.AppendElement(iter.Key());
|
||||
}
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
virtual void Close(const nsCString& aRecordName) override
|
||||
void Close(const nsCString& aRecordName) override
|
||||
{
|
||||
Record* record = nullptr;
|
||||
if (!mRecords.Get(aRecordName, &record)) {
|
||||
|
@ -466,16 +567,6 @@ private:
|
|||
bool mIsOpen;
|
||||
};
|
||||
|
||||
static PLDHashOperator
|
||||
EnumRecordNames(const nsACString& aKey,
|
||||
Record* aRecord,
|
||||
void* aUserArg)
|
||||
{
|
||||
nsTArray<nsCString>* names = reinterpret_cast<nsTArray<nsCString>*>(aUserArg);
|
||||
names->AppendElement(aKey);
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
nsClassHashtable<nsCStringHashKey, Record> mRecords;
|
||||
};
|
||||
|
||||
|
@ -504,7 +595,12 @@ GMPStorageParent::Init()
|
|||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (persistent) {
|
||||
mStorage = MakeUnique<GMPDiskStorage>(mNodeId);
|
||||
UniquePtr<GMPDiskStorage> storage = MakeUnique<GMPDiskStorage>(mNodeId);
|
||||
if (NS_FAILED(storage->Init())) {
|
||||
NS_WARNING("Failed to initialize on disk GMP storage");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mStorage = Move(storage);
|
||||
} else {
|
||||
mStorage = MakeUnique<GMPMemoryStorage>();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "nsIFile.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsLiteralString.h"
|
||||
#include "nsCRTGlue.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -36,4 +37,17 @@ EMEVoucherFileExists()
|
|||
exists;
|
||||
}
|
||||
|
||||
void
|
||||
SplitAt(const char* aDelims,
|
||||
const nsACString& aInput,
|
||||
nsTArray<nsCString>& aOutTokens)
|
||||
{
|
||||
nsAutoCString str(aInput);
|
||||
char* end = str.BeginWriting();
|
||||
const char* start = nullptr;
|
||||
while (!!(start = NS_strtok(aDelims, &end))) {
|
||||
aOutTokens.AppendElement(nsCString(start));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
#define GMPUtils_h_
|
||||
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
class nsIFile;
|
||||
class nsCString;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -27,6 +29,11 @@ bool GetEMEVoucherPath(nsIFile** aPath);
|
|||
|
||||
bool EMEVoucherFileExists();
|
||||
|
||||
void
|
||||
SplitAt(const char* aDelims,
|
||||
const nsACString& aInput,
|
||||
nsTArray<nsCString>& aOutTokens);
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
|
@ -48,6 +48,7 @@ typedef enum {
|
|||
GMPEndOfEnumeration = 11,
|
||||
GMPInvalidArgErr = 12,
|
||||
GMPAbortedErr = 13,
|
||||
GMPRecordCorrupted = 14,
|
||||
GMPLastErr // Placeholder, must be last. This enum's values must remain consecutive!
|
||||
} GMPErr;
|
||||
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "GMPVideoDecoderProxy.h"
|
||||
#include "GMPServiceParent.h"
|
||||
#include "GMPService.h"
|
||||
#include "GMPUtils.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
|
||||
#define GMP_DIR_NAME NS_LITERAL_STRING("gmp-fakeopenh264")
|
||||
#define GMP_OLD_VERSION NS_LITERAL_STRING("1.0")
|
||||
|
@ -21,6 +25,9 @@
|
|||
|
||||
#define EXPECT_OK(X) EXPECT_TRUE(NS_SUCCEEDED(X))
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gmp;
|
||||
|
||||
class GMPRemoveTest : public nsIObserver
|
||||
, public GMPVideoDecoderCallbackProxy
|
||||
{
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "GMPUtils.h"
|
||||
#include "nsString.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace mozilla;
|
||||
|
||||
void TestSplitAt(const char* aInput,
|
||||
const char* aDelims,
|
||||
size_t aNumExpectedTokens,
|
||||
const char* aExpectedTokens[])
|
||||
{
|
||||
nsCString input(aInput);
|
||||
nsTArray<nsCString> tokens;
|
||||
SplitAt(aDelims, input, tokens);
|
||||
EXPECT_EQ(tokens.Length(), aNumExpectedTokens) << "Should get expected number of tokens";
|
||||
for (size_t i = 0; i < tokens.Length(); i++) {
|
||||
EXPECT_TRUE(tokens[i].EqualsASCII(aExpectedTokens[i]))
|
||||
<< "Tokenize fail; expected=" << aExpectedTokens[i] << " got=" <<
|
||||
tokens[i].BeginReading();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GeckoMediaPlugins, GMPUtils) {
|
||||
{
|
||||
const char* input = "1,2,3,4";
|
||||
const char* delims = ",";
|
||||
const char* tokens[] = { "1", "2", "3", "4" };
|
||||
TestSplitAt(input, delims, MOZ_ARRAY_LENGTH(tokens), tokens);
|
||||
}
|
||||
|
||||
{
|
||||
const char* input = "a simple, comma, seperated, list";
|
||||
const char* delims = ",";
|
||||
const char* tokens[] = { "a simple", " comma", " seperated", " list" };
|
||||
TestSplitAt(input, delims, MOZ_ARRAY_LENGTH(tokens), tokens);
|
||||
}
|
||||
|
||||
{
|
||||
const char* input = // Various platform line endings...
|
||||
"line1\r\n" // Windows
|
||||
"line2\r" // Old MacOSX
|
||||
"line3\n" // Unix
|
||||
"line4";
|
||||
const char* delims = "\r\n";
|
||||
const char* tokens[] = { "line1", "line2", "line3", "line4" };
|
||||
TestSplitAt(input, delims, MOZ_ARRAY_LENGTH(tokens), tokens);
|
||||
}
|
||||
}
|
|
@ -776,4 +776,46 @@ TEST(IntervalSet, Substraction)
|
|||
EXPECT_EQ(1u, i0.Length());
|
||||
EXPECT_EQ(5, i0[0].mStart);
|
||||
EXPECT_EQ(8, i0[0].mEnd);
|
||||
|
||||
i0 = IntIntervals();
|
||||
i0 += IntInterval(0, 10);
|
||||
IntIntervals i2;
|
||||
i2 += IntInterval(4, 6);
|
||||
i0 -= i2;
|
||||
EXPECT_EQ(2u, i0.Length());
|
||||
EXPECT_EQ(0, i0[0].mStart);
|
||||
EXPECT_EQ(4, i0[0].mEnd);
|
||||
EXPECT_EQ(6, i0[1].mStart);
|
||||
EXPECT_EQ(10, i0[1].mEnd);
|
||||
|
||||
i0 = IntIntervals();
|
||||
i0 += IntInterval(0, 1);
|
||||
i0 += IntInterval(3, 10);
|
||||
EXPECT_EQ(2u, i0.Length());
|
||||
// This fuzz should collapse i0 into [0,10).
|
||||
i0.SetFuzz(1);
|
||||
EXPECT_EQ(1u, i0.Length());
|
||||
EXPECT_EQ(1, i0[0].mFuzz);
|
||||
i2 = IntInterval(4, 6);
|
||||
i0 -= i2;
|
||||
EXPECT_EQ(2u, i0.Length());
|
||||
EXPECT_EQ(0, i0[0].mStart);
|
||||
EXPECT_EQ(4, i0[0].mEnd);
|
||||
EXPECT_EQ(6, i0[1].mStart);
|
||||
EXPECT_EQ(10, i0[1].mEnd);
|
||||
EXPECT_EQ(1, i0[0].mFuzz);
|
||||
EXPECT_EQ(1, i0[1].mFuzz);
|
||||
|
||||
i0 = IntIntervals();
|
||||
i0 += IntInterval(0, 10);
|
||||
// [4,6) with fuzz 1 used to fail because the complementary interval set
|
||||
// [0,4)+[6,10) would collapse into [0,10).
|
||||
i2 = IntInterval(4, 6);
|
||||
i2.SetFuzz(1);
|
||||
i0 -= i2;
|
||||
EXPECT_EQ(2u, i0.Length());
|
||||
EXPECT_EQ(0, i0[0].mStart);
|
||||
EXPECT_EQ(4, i0[0].mEnd);
|
||||
EXPECT_EQ(6, i0[1].mStart);
|
||||
EXPECT_EQ(10, i0[1].mEnd);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
#include "mozilla/ArrayUtils.h"
|
||||
#include "MockMediaResource.h"
|
||||
|
||||
using namespace mp3;
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::mp3;
|
||||
|
||||
// Regular MP3 file mock resource.
|
||||
class MockMP3MediaResource : public MockMediaResource {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "TaskQueue.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "MockMediaResource.h"
|
||||
#include "VideoUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mp4_demuxer;
|
||||
|
|
|
@ -9,6 +9,7 @@ UNIFIED_SOURCES += [
|
|||
'TestAudioCompactor.cpp',
|
||||
'TestGMPCrossOrigin.cpp',
|
||||
'TestGMPRemoveAndDelete.cpp',
|
||||
'TestGMPUtils.cpp',
|
||||
'TestIntervalSet.cpp',
|
||||
'TestMediaEventSource.cpp',
|
||||
'TestMozPromise.cpp',
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define PlatformDecoderModule_h_
|
||||
|
||||
#include "MediaDecoderReader.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/layers/LayersTypes.h"
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
@ -207,16 +208,24 @@ protected:
|
|||
virtual ~MediaDataDecoder() {};
|
||||
|
||||
public:
|
||||
enum DecoderFailureReason {
|
||||
INIT_ERROR,
|
||||
CANCELED
|
||||
};
|
||||
|
||||
typedef TrackInfo::TrackType TrackType;
|
||||
typedef MozPromise<TrackType, DecoderFailureReason, /* IsExclusive = */ true> InitPromise;
|
||||
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDataDecoder)
|
||||
|
||||
// Initialize the decoder. The decoder should be ready to decode after
|
||||
// this returns. The decoder should do any initialization here, rather
|
||||
// Initialize the decoder. The decoder should be ready to decode once
|
||||
// promise resolves. The decoder should do any initialization here, rather
|
||||
// than in its constructor or PlatformDecoderModule::Create*Decoder(),
|
||||
// so that if the MP4Reader needs to shutdown during initialization,
|
||||
// it can call Shutdown() to cancel this operation. Any initialization
|
||||
// that requires blocking the calling thread in this function *must*
|
||||
// be done here so that it can be canceled by calling Shutdown()!
|
||||
virtual nsresult Init() = 0;
|
||||
virtual nsRefPtr<InitPromise> Init() = 0;
|
||||
|
||||
// Inserts a sample into the decoder's decode pipeline.
|
||||
virtual nsresult Input(MediaRawData* aSample) = 0;
|
||||
|
@ -251,6 +260,8 @@ public:
|
|||
virtual nsresult Shutdown() = 0;
|
||||
|
||||
// Called from the state machine task queue or main thread.
|
||||
// Decoder needs to decide whether or not hardware accelearation is supported
|
||||
// after creating. It doesn't need to call Init() before calling this function.
|
||||
virtual bool IsHardwareAccelerated() const { return false; }
|
||||
|
||||
// ConfigurationChanged will be called to inform the video or audio decoder
|
||||
|
|
|
@ -73,6 +73,7 @@ SharedDecoderManager::SharedDecoderManager()
|
|||
: mTaskQueue(new FlushableTaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER)))
|
||||
, mActiveProxy(nullptr)
|
||||
, mActiveCallback(nullptr)
|
||||
, mInit(false)
|
||||
, mWaitForInternalDrain(false)
|
||||
, mMonitor("SharedDecoderManager")
|
||||
, mDecoderReleasedResources(false)
|
||||
|
@ -111,11 +112,7 @@ SharedDecoderManager::CreateVideoDecoder(
|
|||
mPDM = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
nsresult rv = mDecoder->Init();
|
||||
if (NS_FAILED(rv)) {
|
||||
mDecoder = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mPDM = aPDM;
|
||||
}
|
||||
|
||||
|
@ -143,8 +140,8 @@ SharedDecoderManager::Recreate(const VideoInfo& aConfig)
|
|||
if (!mDecoder) {
|
||||
return false;
|
||||
}
|
||||
nsresult rv = mDecoder->Init();
|
||||
return rv == NS_OK;
|
||||
mInit = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -181,6 +178,35 @@ SharedDecoderManager::SetIdle(MediaDataDecoder* aProxy)
|
|||
}
|
||||
}
|
||||
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
SharedDecoderManager::InitDecoder()
|
||||
{
|
||||
if (!mInit && mDecoder) {
|
||||
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
|
||||
|
||||
nsRefPtr<SharedDecoderManager> self = this;
|
||||
nsRefPtr<MediaDataDecoder::InitPromise> p = mDecoderInitPromise.Ensure(__func__);
|
||||
|
||||
// The mTaskQueue is flushable which can't be used in MediaPromise. So we get
|
||||
// the current AbstractThread instead of it. The MOZ_ASSERT above ensures
|
||||
// we are running in AbstractThread so we won't get a nullptr.
|
||||
mDecoderInitPromiseRequest.Begin(
|
||||
mDecoder->Init()->Then(AbstractThread::GetCurrent(), __func__,
|
||||
[self] (TrackInfo::TrackType aType) -> void {
|
||||
self->mDecoderInitPromiseRequest.Complete();
|
||||
self->mInit = true;
|
||||
self->mDecoderInitPromise.ResolveIfExists(aType, __func__);
|
||||
},
|
||||
[self] (MediaDataDecoder::DecoderFailureReason aReason) -> void {
|
||||
self->mDecoderInitPromiseRequest.Complete();
|
||||
self->mDecoderInitPromise.RejectIfExists(aReason, __func__);
|
||||
}));
|
||||
return p;
|
||||
}
|
||||
|
||||
return MediaDataDecoder::InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
|
||||
}
|
||||
|
||||
void
|
||||
SharedDecoderManager::DrainComplete()
|
||||
{
|
||||
|
@ -211,6 +237,7 @@ SharedDecoderManager::Shutdown()
|
|||
mTaskQueue->AwaitShutdownAndIdle();
|
||||
mTaskQueue = nullptr;
|
||||
}
|
||||
mDecoderInitPromiseRequest.DisconnectIfExists();
|
||||
}
|
||||
|
||||
SharedDecoderProxy::SharedDecoderProxy(SharedDecoderManager* aManager,
|
||||
|
@ -225,10 +252,14 @@ SharedDecoderProxy::~SharedDecoderProxy()
|
|||
Shutdown();
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
SharedDecoderProxy::Init()
|
||||
{
|
||||
return NS_OK;
|
||||
if (mManager->mActiveProxy != this) {
|
||||
mManager->Select(this);
|
||||
}
|
||||
|
||||
return mManager->InitDecoder();
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -48,6 +48,8 @@ private:
|
|||
virtual ~SharedDecoderManager();
|
||||
void DrainComplete();
|
||||
|
||||
nsRefPtr<MediaDataDecoder::InitPromise> InitDecoder();
|
||||
|
||||
nsRefPtr<PlatformDecoderModule> mPDM;
|
||||
nsRefPtr<MediaDataDecoder> mDecoder;
|
||||
layers::LayersBackend mLayersBackend;
|
||||
|
@ -56,6 +58,9 @@ private:
|
|||
SharedDecoderProxy* mActiveProxy;
|
||||
MediaDataDecoderCallback* mActiveCallback;
|
||||
nsAutoPtr<MediaDataDecoderCallback> mCallback;
|
||||
MozPromiseHolder<MediaDataDecoder::InitPromise> mDecoderInitPromise;
|
||||
MozPromiseRequestHolder<MediaDataDecoder::InitPromise> mDecoderInitPromiseRequest;
|
||||
bool mInit;
|
||||
// access protected by mMonitor
|
||||
bool mWaitForInternalDrain;
|
||||
Monitor mMonitor;
|
||||
|
@ -69,7 +74,7 @@ public:
|
|||
MediaDataDecoderCallback* aCallback);
|
||||
virtual ~SharedDecoderProxy();
|
||||
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<MediaDataDecoder::InitPromise> Init() override;
|
||||
virtual nsresult Input(MediaRawData* aSample) override;
|
||||
virtual nsresult Flush() override;
|
||||
virtual nsresult Drain() override;
|
||||
|
|
|
@ -25,15 +25,17 @@ public:
|
|||
|
||||
BlankMediaDataDecoder(BlankMediaDataCreator* aCreator,
|
||||
FlushableTaskQueue* aTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
MediaDataDecoderCallback* aCallback,
|
||||
TrackInfo::TrackType aType)
|
||||
: mCreator(aCreator)
|
||||
, mTaskQueue(aTaskQueue)
|
||||
, mCallback(aCallback)
|
||||
, mType(aType)
|
||||
{
|
||||
}
|
||||
|
||||
virtual nsresult Init() override {
|
||||
return NS_OK;
|
||||
virtual nsRefPtr<InitPromise> Init() override {
|
||||
return InitPromise::CreateAndResolve(mType, __func__);
|
||||
}
|
||||
|
||||
virtual nsresult Shutdown() override {
|
||||
|
@ -89,6 +91,7 @@ private:
|
|||
nsAutoPtr<BlankMediaDataCreator> mCreator;
|
||||
RefPtr<FlushableTaskQueue> mTaskQueue;
|
||||
MediaDataDecoderCallback* mCallback;
|
||||
TrackInfo::TrackType mType;
|
||||
};
|
||||
|
||||
class BlankVideoDataCreator {
|
||||
|
@ -221,7 +224,8 @@ public:
|
|||
nsRefPtr<MediaDataDecoder> decoder =
|
||||
new BlankMediaDataDecoder<BlankVideoDataCreator>(creator,
|
||||
aVideoTaskQueue,
|
||||
aCallback);
|
||||
aCallback,
|
||||
TrackInfo::kVideoTrack);
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
|
@ -236,7 +240,8 @@ public:
|
|||
nsRefPtr<MediaDataDecoder> decoder =
|
||||
new BlankMediaDataDecoder<BlankAudioDataCreator>(creator,
|
||||
aAudioTaskQueue,
|
||||
aCallback);
|
||||
aCallback,
|
||||
TrackInfo::kAudioTrack);
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
|
|
|
@ -47,19 +47,19 @@ OpusDataDecoder::Shutdown()
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
OpusDataDecoder::Init()
|
||||
{
|
||||
size_t length = mInfo.mCodecSpecificConfig->Length();
|
||||
uint8_t *p = mInfo.mCodecSpecificConfig->Elements();
|
||||
if (length < sizeof(uint64_t)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
int64_t codecDelay = BigEndian::readUint64(p);
|
||||
length -= sizeof(uint64_t);
|
||||
p += sizeof(uint64_t);
|
||||
if (NS_FAILED(DecodeHeader(p, length))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
int r;
|
||||
|
@ -75,7 +75,7 @@ OpusDataDecoder::Init()
|
|||
if (codecDelay != FramesToUsecs(mOpusParser->mPreSkip,
|
||||
mOpusParser->mRate).value()) {
|
||||
NS_WARNING("Invalid Opus header: CodecDelay and pre-skip do not match!");
|
||||
return NS_ERROR_FAILURE;
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
if (mInfo.mRate != (uint32_t)mOpusParser->mRate) {
|
||||
|
@ -85,7 +85,8 @@ OpusDataDecoder::Init()
|
|||
NS_WARNING("Invalid Opus header: container and codec channels do not match!");
|
||||
}
|
||||
|
||||
return r == OPUS_OK ? NS_OK : NS_ERROR_FAILURE;
|
||||
return r == OPUS_OK ? InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__)
|
||||
: InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -21,7 +21,7 @@ public:
|
|||
MediaDataDecoderCallback* aCallback);
|
||||
~OpusDataDecoder();
|
||||
|
||||
nsresult Init() override;
|
||||
nsRefPtr<InitPromise> Init() override;
|
||||
nsresult Input(MediaRawData* aSample) override;
|
||||
nsresult Flush() override;
|
||||
nsresult Drain() override;
|
||||
|
|
|
@ -55,7 +55,7 @@ VPXDecoder::Shutdown()
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
VPXDecoder::Init()
|
||||
{
|
||||
vpx_codec_iface_t* dx = nullptr;
|
||||
|
@ -65,9 +65,9 @@ VPXDecoder::Init()
|
|||
dx = vpx_codec_vp9_dx();
|
||||
}
|
||||
if (!dx || vpx_codec_dec_init(&mVPX, dx, nullptr, 0)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
return NS_OK;
|
||||
return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -28,7 +28,7 @@ public:
|
|||
|
||||
~VPXDecoder();
|
||||
|
||||
nsresult Init() override;
|
||||
nsRefPtr<InitPromise> Init() override;
|
||||
nsresult Input(MediaRawData* aSample) override;
|
||||
nsresult Flush() override;
|
||||
nsresult Drain() override;
|
||||
|
|
|
@ -63,7 +63,7 @@ VorbisDataDecoder::Shutdown()
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
VorbisDataDecoder::Init()
|
||||
{
|
||||
vorbis_info_init(&mVorbisInfo);
|
||||
|
@ -75,17 +75,17 @@ VorbisDataDecoder::Init()
|
|||
uint8_t *p = mInfo.mCodecSpecificConfig->Elements();
|
||||
for(int i = 0; i < 3; i++) {
|
||||
if (available < 2) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
available -= 2;
|
||||
size_t length = BigEndian::readUint16(p);
|
||||
p += 2;
|
||||
if (available < length) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
available -= length;
|
||||
if (NS_FAILED(DecodeHeader((const unsigned char*)p, length))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
p += length;
|
||||
}
|
||||
|
@ -94,12 +94,12 @@ VorbisDataDecoder::Init()
|
|||
|
||||
int r = vorbis_synthesis_init(&mVorbisDsp, &mVorbisInfo);
|
||||
if (r) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
r = vorbis_block_init(&mVorbisDsp, &mVorbisBlock);
|
||||
if (r) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
if (mInfo.mRate != (uint32_t)mVorbisDsp.vi->rate) {
|
||||
|
@ -111,7 +111,7 @@ VorbisDataDecoder::Init()
|
|||
("Invalid Vorbis header: container and codec channels do not match!"));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -24,7 +24,7 @@ public:
|
|||
MediaDataDecoderCallback* aCallback);
|
||||
~VorbisDataDecoder();
|
||||
|
||||
nsresult Init() override;
|
||||
nsRefPtr<InitPromise> Init() override;
|
||||
nsresult Input(MediaRawData* aSample) override;
|
||||
nsresult Flush() override;
|
||||
nsresult Drain() override;
|
||||
|
|
|
@ -45,7 +45,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual nsresult Init() override {
|
||||
virtual nsRefPtr<InitPromise> Init() override {
|
||||
MOZ_ASSERT(!mIsShutdown);
|
||||
return mDecoder->Init();
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ GMPAudioDecoder::GMPInitDone(GMPAudioDecoderProxy* aGMP)
|
|||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
GMPAudioDecoder::Init()
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
|
@ -188,7 +188,8 @@ GMPAudioDecoder::Init()
|
|||
NS_ProcessNextEvent(gmpThread, true);
|
||||
}
|
||||
|
||||
return mGMP ? NS_OK : NS_ERROR_FAILURE;
|
||||
return mGMP ? InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__)
|
||||
: InitPromise::CreateAndReject(MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -69,7 +69,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<InitPromise> Init() override;
|
||||
virtual nsresult Input(MediaRawData* aSample) override;
|
||||
virtual nsresult Flush() override;
|
||||
virtual nsresult Drain() override;
|
||||
|
|
|
@ -211,7 +211,7 @@ GMPVideoDecoder::GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost)
|
|||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
GMPVideoDecoder::Init()
|
||||
{
|
||||
MOZ_ASSERT(IsOnGMPThread());
|
||||
|
@ -232,7 +232,8 @@ GMPVideoDecoder::Init()
|
|||
NS_ProcessNextEvent(gmpThread, true);
|
||||
}
|
||||
|
||||
return mGMP ? NS_OK : NS_ERROR_FAILURE;
|
||||
return mGMP ? InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__)
|
||||
: InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -84,7 +84,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<InitPromise> Init() override;
|
||||
virtual nsresult Input(MediaRawData* aSample) override;
|
||||
virtual nsresult Flush() override;
|
||||
virtual nsresult Drain() override;
|
||||
|
|
|
@ -21,16 +21,21 @@ MediaDataDecoderCallbackProxy::FlushComplete()
|
|||
mProxyDecoder->FlushComplete();
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
MediaDataDecoderProxy::InternalInit()
|
||||
{
|
||||
MOZ_ASSERT(!mIsShutdown);
|
||||
|
||||
return mProxyDecoder->Init();
|
||||
}
|
||||
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
MediaDataDecoderProxy::Init()
|
||||
{
|
||||
MOZ_ASSERT(!mIsShutdown);
|
||||
nsRefPtr<InitTask> task(new InitTask(mProxyDecoder));
|
||||
nsresult rv = mProxyThread->Dispatch(task, NS_DISPATCH_SYNC);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_SUCCESS(task->Result(), task->Result());
|
||||
|
||||
return NS_OK;
|
||||
return ProxyMediaCall(mProxyThreadWrapper, this, __func__,
|
||||
&MediaDataDecoderProxy::InternalInit);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -32,30 +32,6 @@ private:
|
|||
nsRefPtr<MediaRawData> mSample;
|
||||
};
|
||||
|
||||
class InitTask : public nsRunnable {
|
||||
public:
|
||||
explicit InitTask(MediaDataDecoder* aDecoder)
|
||||
: mDecoder(aDecoder)
|
||||
, mResultValid(false)
|
||||
{}
|
||||
|
||||
NS_IMETHOD Run() {
|
||||
mResult = mDecoder->Init();
|
||||
mResultValid = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult Result() {
|
||||
MOZ_ASSERT(mResultValid);
|
||||
return mResult;
|
||||
}
|
||||
|
||||
private:
|
||||
MediaDataDecoder* mDecoder;
|
||||
nsresult mResult;
|
||||
bool mResultValid;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class Condition {
|
||||
public:
|
||||
|
@ -132,6 +108,7 @@ public:
|
|||
, mIsShutdown(false)
|
||||
#endif
|
||||
{
|
||||
mProxyThreadWrapper = CreateXPCOMAbstractThreadWrapper(aProxyThread, false);
|
||||
}
|
||||
|
||||
// Ideally, this would return a regular MediaDataDecoderCallback pointer
|
||||
|
@ -155,7 +132,7 @@ public:
|
|||
// Init and Shutdown run synchronously on the proxy thread, all others are
|
||||
// asynchronously and responded to via the MediaDataDecoderCallback.
|
||||
// Note: the nsresults returned by the proxied decoder are lost.
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<InitPromise> Init() override;
|
||||
virtual nsresult Input(MediaRawData* aSample) override;
|
||||
virtual nsresult Flush() override;
|
||||
virtual nsresult Drain() override;
|
||||
|
@ -165,6 +142,8 @@ public:
|
|||
void FlushComplete();
|
||||
|
||||
private:
|
||||
nsRefPtr<InitPromise> InternalInit();
|
||||
|
||||
#ifdef DEBUG
|
||||
bool IsOnProxyThread() {
|
||||
return NS_GetCurrentThread() == mProxyThread;
|
||||
|
@ -176,6 +155,7 @@ private:
|
|||
|
||||
nsRefPtr<MediaDataDecoder> mProxyDecoder;
|
||||
nsCOMPtr<nsIThread> mProxyThread;
|
||||
nsRefPtr<AbstractThread> mProxyThreadWrapper;
|
||||
|
||||
MediaDataDecoderCallbackProxy mProxyCallback;
|
||||
|
||||
|
|
|
@ -53,14 +53,18 @@ public:
|
|||
|
||||
}
|
||||
|
||||
nsresult Init() override {
|
||||
nsRefPtr<InitPromise> Init() override {
|
||||
mSurfaceTexture = AndroidSurfaceTexture::Create();
|
||||
if (!mSurfaceTexture) {
|
||||
NS_WARNING("Failed to create SurfaceTexture for video decode\n");
|
||||
return NS_ERROR_FAILURE;
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
return InitDecoder(mSurfaceTexture->JavaSurface());
|
||||
if (NS_FAILED(InitDecoder(mSurfaceTexture->JavaSurface()))) {
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
|
||||
}
|
||||
|
||||
void Cleanup() override {
|
||||
|
@ -349,9 +353,17 @@ MediaCodecDataDecoder::~MediaCodecDataDecoder()
|
|||
Shutdown();
|
||||
}
|
||||
|
||||
nsresult MediaCodecDataDecoder::Init()
|
||||
nsRefPtr<MediaDataDecoder::InitPromise> MediaCodecDataDecoder::Init()
|
||||
{
|
||||
return InitDecoder(nullptr);
|
||||
nsresult rv = InitDecoder(nullptr);
|
||||
|
||||
TrackInfo::TrackType type =
|
||||
(mType == MediaData::AUDIO_DATA ? TrackInfo::TrackType::kAudioTrack
|
||||
: TrackInfo::TrackType::kVideoTrack);
|
||||
|
||||
return NS_SUCCEEDED(rv) ?
|
||||
InitPromise::CreateAndResolve(type, __func__) :
|
||||
InitPromise::CreateAndReject(MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
nsresult MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface)
|
||||
|
|
|
@ -52,7 +52,7 @@ public:
|
|||
|
||||
virtual ~MediaCodecDataDecoder();
|
||||
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<MediaDataDecoder::InitPromise> Init() override;
|
||||
virtual nsresult Flush() override;
|
||||
virtual nsresult Drain() override;
|
||||
virtual nsresult Shutdown() override;
|
||||
|
|
|
@ -50,14 +50,15 @@ AppleATDecoder::~AppleATDecoder()
|
|||
MOZ_ASSERT(!mConverter);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
AppleATDecoder::Init()
|
||||
{
|
||||
if (!mFormatID) {
|
||||
NS_ERROR("Non recognised format");
|
||||
return NS_ERROR_FAILURE;
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
return NS_OK;
|
||||
|
||||
return InitPromise::CreateAndResolve(TrackType::kAudioTrack, __func__);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
MediaDataDecoderCallback* aCallback);
|
||||
virtual ~AppleATDecoder();
|
||||
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<InitPromise> Init() override;
|
||||
virtual nsresult Input(MediaRawData* aSample) override;
|
||||
virtual nsresult Flush() override;
|
||||
virtual nsresult Drain() override;
|
||||
|
|
|
@ -73,19 +73,10 @@ AppleVDADecoder::~AppleVDADecoder()
|
|||
MOZ_COUNT_DTOR(AppleVDADecoder);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
AppleVDADecoder::Init()
|
||||
{
|
||||
if (!gfxPlatform::GetPlatform()->CanUseHardwareVideoDecoding()) {
|
||||
// This GPU is blacklisted for hardware decoding.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (mDecoder) {
|
||||
return NS_OK;
|
||||
}
|
||||
nsresult rv = InitializeSession();
|
||||
return rv;
|
||||
return InitPromise::CreateAndResolve(TrackType::kVideoTrack, __func__);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -580,11 +571,18 @@ AppleVDADecoder::CreateVDADecoder(
|
|||
MediaDataDecoderCallback* aCallback,
|
||||
layers::ImageContainer* aImageContainer)
|
||||
{
|
||||
nsRefPtr<AppleVDADecoder> decoder =
|
||||
new AppleVDADecoder(aConfig, aVideoTaskQueue, aCallback, aImageContainer);
|
||||
if (NS_FAILED(decoder->Init())) {
|
||||
if (!gfxPlatform::GetPlatform()->CanUseHardwareVideoDecoding()) {
|
||||
// This GPU is blacklisted for hardware decoding.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<AppleVDADecoder> decoder =
|
||||
new AppleVDADecoder(aConfig, aVideoTaskQueue, aCallback, aImageContainer);
|
||||
|
||||
if (NS_FAILED(decoder->InitializeSession())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ public:
|
|||
MediaDataDecoderCallback* aCallback,
|
||||
layers::ImageContainer* aImageContainer);
|
||||
virtual ~AppleVDADecoder();
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<InitPromise> Init() override;
|
||||
virtual nsresult Input(MediaRawData* aSample) override;
|
||||
virtual nsresult Flush() override;
|
||||
virtual nsresult Drain() override;
|
||||
|
@ -83,11 +83,15 @@ public:
|
|||
nsresult OutputFrame(CVPixelBufferRef aImage,
|
||||
nsAutoPtr<AppleFrameRef> aFrameRef);
|
||||
|
||||
// Method to set up the decompression session.
|
||||
nsresult InitializeSession();
|
||||
|
||||
protected:
|
||||
AppleFrameRef* CreateAppleFrameRef(const MediaRawData* aSample);
|
||||
void DrainReorderedFrames();
|
||||
void ClearReorderedFrames();
|
||||
CFDictionaryRef CreateOutputConfiguration();
|
||||
nsresult InitDecoder();
|
||||
|
||||
nsRefPtr<MediaByteBuffer> mExtraData;
|
||||
nsRefPtr<FlushableTaskQueue> mTaskQueue;
|
||||
|
@ -107,8 +111,6 @@ private:
|
|||
|
||||
// Method to pass a frame to VideoToolbox for decoding.
|
||||
nsresult SubmitFrame(MediaRawData* aSample);
|
||||
// Method to set up the decompression session.
|
||||
nsresult InitializeSession();
|
||||
CFDictionaryRef CreateDecoderSpecification();
|
||||
};
|
||||
|
||||
|
|
|
@ -51,11 +51,16 @@ AppleVTDecoder::~AppleVTDecoder()
|
|||
MOZ_COUNT_DTOR(AppleVTDecoder);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
AppleVTDecoder::Init()
|
||||
{
|
||||
nsresult rv = InitializeSession();
|
||||
return rv;
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
return InitPromise::CreateAndResolve(TrackType::kVideoTrack, __func__);
|
||||
}
|
||||
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -20,7 +20,7 @@ public:
|
|||
MediaDataDecoderCallback* aCallback,
|
||||
layers::ImageContainer* aImageContainer);
|
||||
virtual ~AppleVTDecoder();
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<InitPromise> Init() override;
|
||||
virtual nsresult Input(MediaRawData* aSample) override;
|
||||
virtual nsresult Flush() override;
|
||||
virtual nsresult Drain() override;
|
||||
|
|
|
@ -27,13 +27,13 @@ FFmpegAudioDecoder<LIBAV_VER>::FFmpegAudioDecoder(
|
|||
mExtraData->AppendElements(*aConfig.mCodecSpecificConfig);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
FFmpegAudioDecoder<LIBAV_VER>::Init()
|
||||
{
|
||||
nsresult rv = FFmpegDataDecoder::Init();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsresult rv = InitDecoder();
|
||||
|
||||
return NS_OK;
|
||||
return rv == NS_OK ? InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__)
|
||||
: InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
static AudioDataValue*
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
const AudioInfo& aConfig);
|
||||
virtual ~FFmpegAudioDecoder();
|
||||
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<InitPromise> Init() override;
|
||||
virtual nsresult Input(MediaRawData* aSample) override;
|
||||
virtual nsresult Drain() override;
|
||||
static AVCodecID GetCodecId(const nsACString& aMimeType);
|
||||
|
|
|
@ -58,7 +58,7 @@ ChoosePixelFormat(AVCodecContext* aCodecContext, const PixelFormat* aFormats)
|
|||
}
|
||||
|
||||
nsresult
|
||||
FFmpegDataDecoder<LIBAV_VER>::Init()
|
||||
FFmpegDataDecoder<LIBAV_VER>::InitDecoder()
|
||||
{
|
||||
StaticMutexAutoLock mon(sMonitor);
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ public:
|
|||
|
||||
static bool Link();
|
||||
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<InitPromise> Init() override = 0;
|
||||
virtual nsresult Input(MediaRawData* aSample) override = 0;
|
||||
virtual nsresult Flush() override;
|
||||
virtual nsresult Drain() override = 0;
|
||||
|
@ -36,6 +36,7 @@ public:
|
|||
|
||||
protected:
|
||||
AVFrame* PrepareFrame();
|
||||
nsresult InitDecoder();
|
||||
|
||||
FlushableTaskQueue* mTaskQueue;
|
||||
AVCodecContext* mCodecContext;
|
||||
|
|
|
@ -39,16 +39,17 @@ FFmpegH264Decoder<LIBAV_VER>::FFmpegH264Decoder(
|
|||
mExtraData->AppendElements(*aConfig.mExtraData);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
FFmpegH264Decoder<LIBAV_VER>::Init()
|
||||
{
|
||||
nsresult rv = FFmpegDataDecoder::Init();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_FAILED(InitDecoder())) {
|
||||
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
mCodecContext->get_buffer = AllocateBufferCb;
|
||||
mCodecContext->release_buffer = ReleaseBufferCb;
|
||||
|
||||
return NS_OK;
|
||||
return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
|
||||
}
|
||||
|
||||
int64_t
|
||||
|
|
|
@ -36,7 +36,7 @@ public:
|
|||
ImageContainer* aImageContainer);
|
||||
virtual ~FFmpegH264Decoder();
|
||||
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<InitPromise> Init() override;
|
||||
virtual nsresult Input(MediaRawData* aSample) override;
|
||||
virtual nsresult Drain() override;
|
||||
virtual nsresult Flush() override;
|
||||
|
|
|
@ -37,6 +37,8 @@ public:
|
|||
|
||||
virtual bool HasQueuedSample() override;
|
||||
|
||||
virtual TrackType GetTrackType() override { return TrackType::kAudioTrack; }
|
||||
|
||||
private:
|
||||
nsresult CreateAudioData(int64_t aStreamOffset,
|
||||
AudioData** aOutData);
|
||||
|
|
|
@ -37,7 +37,7 @@ GonkMediaDataDecoder::~GonkMediaDataDecoder()
|
|||
MOZ_COUNT_DTOR(GonkMediaDataDecoder);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
GonkMediaDataDecoder::Init()
|
||||
{
|
||||
sp<MediaCodecProxy> decoder;
|
||||
|
@ -45,7 +45,7 @@ GonkMediaDataDecoder::Init()
|
|||
mDecoder = decoder;
|
||||
mDrainComplete = false;
|
||||
|
||||
return NS_OK;
|
||||
return InitPromise::CreateAndResolve(mManager->GetTrackType(), __func__);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -18,6 +18,8 @@ class MediaRawData;
|
|||
// Manage the data flow from inputting encoded data and outputting decode data.
|
||||
class GonkDecoderManager {
|
||||
public:
|
||||
typedef TrackInfo::TrackType TrackType;
|
||||
|
||||
virtual ~GonkDecoderManager() {}
|
||||
|
||||
// Creates and initializs the GonkDecoder.
|
||||
|
@ -42,6 +44,8 @@ public:
|
|||
// True if sample is queued.
|
||||
virtual bool HasQueuedSample() = 0;
|
||||
|
||||
virtual TrackType GetTrackType() = 0;
|
||||
|
||||
protected:
|
||||
nsRefPtr<MediaByteBuffer> mCodecSpecificData;
|
||||
|
||||
|
@ -61,9 +65,9 @@ public:
|
|||
|
||||
~GonkMediaDataDecoder();
|
||||
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<InitPromise> Init() override;
|
||||
|
||||
virtual nsresult Input(MediaRawData* aSample);
|
||||
virtual nsresult Input(MediaRawData* aSample) override;
|
||||
|
||||
virtual nsresult Flush() override;
|
||||
|
||||
|
|
|
@ -55,6 +55,8 @@ public:
|
|||
|
||||
virtual bool HasQueuedSample() override;
|
||||
|
||||
virtual TrackType GetTrackType() override { return TrackType::kVideoTrack; }
|
||||
|
||||
static void RecycleCallback(TextureClient* aClient, void* aClosure);
|
||||
|
||||
private:
|
||||
|
|
|
@ -31,7 +31,7 @@ const GUID MF_XVP_PLAYBACK_MODE =
|
|||
{ 0xaf, 0x12, 0xcf, 0x3e, 0x23, 0x8a, 0xcc, 0xe9 }
|
||||
};
|
||||
|
||||
DEFINE_GUID(MF_LOW_LATENCY,
|
||||
DEFINE_GUID(MF_LOW_LATENCY,
|
||||
0x9c27891a, 0xed7a, 0x40e1, 0x88, 0xe8, 0xb2, 0x27, 0x27, 0xa0, 0x24, 0xee);
|
||||
|
||||
namespace mozilla {
|
||||
|
|
|
@ -31,6 +31,10 @@ public:
|
|||
|
||||
virtual void Shutdown() override;
|
||||
|
||||
virtual TrackInfo::TrackType GetType() override {
|
||||
return TrackInfo::kAudioTrack;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
HRESULT UpdateOutputType();
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "WMFDecoderModule.h"
|
||||
#include "WMFVideoMFTManager.h"
|
||||
#include "WMFAudioMFTManager.h"
|
||||
#include "MFTDecoder.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/Services.h"
|
||||
|
@ -97,13 +98,21 @@ WMFDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
|
|||
FlushableTaskQueue* aVideoTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
{
|
||||
nsAutoPtr<WMFVideoMFTManager> manager =
|
||||
new WMFVideoMFTManager(aConfig,
|
||||
aLayersBackend,
|
||||
aImageContainer,
|
||||
sDXVAEnabled && ShouldUseDXVA(aConfig));
|
||||
|
||||
nsRefPtr<MFTDecoder> mft = manager->Init();
|
||||
|
||||
if (!mft) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<MediaDataDecoder> decoder =
|
||||
new WMFMediaDataDecoder(new WMFVideoMFTManager(aConfig,
|
||||
aLayersBackend,
|
||||
aImageContainer,
|
||||
sDXVAEnabled && ShouldUseDXVA(aConfig)),
|
||||
aVideoTaskQueue,
|
||||
aCallback);
|
||||
new WMFMediaDataDecoder(manager.forget(), mft, aVideoTaskQueue, aCallback);
|
||||
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
|
@ -112,10 +121,15 @@ WMFDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
|
|||
FlushableTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
{
|
||||
nsAutoPtr<WMFAudioMFTManager> manager = new WMFAudioMFTManager(aConfig);
|
||||
nsRefPtr<MFTDecoder> mft = manager->Init();
|
||||
|
||||
if (!mft) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<MediaDataDecoder> decoder =
|
||||
new WMFMediaDataDecoder(new WMFAudioMFTManager(aConfig),
|
||||
aAudioTaskQueue,
|
||||
aCallback);
|
||||
new WMFMediaDataDecoder(manager.forget(), mft, aAudioTaskQueue, aCallback);
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,12 @@ PRLogModuleInfo* GetDemuxerLog();
|
|||
namespace mozilla {
|
||||
|
||||
WMFMediaDataDecoder::WMFMediaDataDecoder(MFTManager* aMFTManager,
|
||||
MFTDecoder* aDecoder,
|
||||
FlushableTaskQueue* aTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
: mTaskQueue(aTaskQueue)
|
||||
, mCallback(aCallback)
|
||||
, mDecoder(aDecoder)
|
||||
, mMFTManager(aMFTManager)
|
||||
, mMonitor("WMFMediaDataDecoder")
|
||||
, mIsFlushing(false)
|
||||
|
@ -33,16 +35,14 @@ WMFMediaDataDecoder::~WMFMediaDataDecoder()
|
|||
{
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
WMFMediaDataDecoder::Init()
|
||||
{
|
||||
MOZ_ASSERT(!mDecoder);
|
||||
MOZ_ASSERT(!mIsShutDown);
|
||||
|
||||
mDecoder = mMFTManager->Init();
|
||||
NS_ENSURE_TRUE(mDecoder, NS_ERROR_FAILURE);
|
||||
|
||||
return NS_OK;
|
||||
return mDecoder ?
|
||||
InitPromise::CreateAndResolve(mMFTManager->GetType(), __func__) :
|
||||
InitPromise::CreateAndReject(MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
// A single telemetry sample is reported for each MediaDataDecoder object
|
||||
|
|
|
@ -45,6 +45,8 @@ public:
|
|||
|
||||
virtual bool IsHardwareAccelerated() const { return false; }
|
||||
|
||||
virtual TrackInfo::TrackType GetType() = 0;
|
||||
|
||||
};
|
||||
|
||||
// Decodes audio and video using Windows Media Foundation. Samples are decoded
|
||||
|
@ -55,11 +57,12 @@ public:
|
|||
class WMFMediaDataDecoder : public MediaDataDecoder {
|
||||
public:
|
||||
WMFMediaDataDecoder(MFTManager* aOutputSource,
|
||||
MFTDecoder* aDecoder,
|
||||
FlushableTaskQueue* aAudioTaskQueue,
|
||||
MediaDataDecoderCallback* aCallback);
|
||||
~WMFMediaDataDecoder();
|
||||
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<MediaDataDecoder::InitPromise> Init() override;
|
||||
|
||||
virtual nsresult Input(MediaRawData* aSample);
|
||||
|
||||
|
|
|
@ -36,6 +36,10 @@ public:
|
|||
|
||||
virtual bool IsHardwareAccelerated() const override;
|
||||
|
||||
virtual TrackInfo::TrackType GetType() override {
|
||||
return TrackInfo::kVideoTrack;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool InitializeDXVA(bool aForceD3D9);
|
||||
|
|
|
@ -29,6 +29,7 @@ H264Converter::H264Converter(PlatformDecoderModule* aPDM,
|
|||
, mCallback(aCallback)
|
||||
, mDecoder(nullptr)
|
||||
, mNeedAVCC(aPDM->DecoderNeedsConversion(aConfig) == PlatformDecoderModule::kNeedAVCC)
|
||||
, mDecoderInitializing(false)
|
||||
, mLastError(NS_OK)
|
||||
{
|
||||
CreateDecoder();
|
||||
|
@ -38,13 +39,15 @@ H264Converter::~H264Converter()
|
|||
{
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsRefPtr<MediaDataDecoder::InitPromise>
|
||||
H264Converter::Init()
|
||||
{
|
||||
if (mDecoder) {
|
||||
return mDecoder->Init();
|
||||
}
|
||||
return mLastError;
|
||||
|
||||
return MediaDataDecoder::InitPromise::CreateAndReject(
|
||||
MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -59,6 +62,12 @@ H264Converter::Input(MediaRawData* aSample)
|
|||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (mDecoderInitializing) {
|
||||
mMediaRawSamples.AppendElement(aSample);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
if (!mDecoder) {
|
||||
// It is not possible to create an AVCC H264 decoder without SPS.
|
||||
|
@ -104,6 +113,7 @@ H264Converter::Shutdown()
|
|||
{
|
||||
if (mDecoder) {
|
||||
nsresult rv = mDecoder->Shutdown();
|
||||
mInitPromiseRequest.DisconnectIfExists();
|
||||
mDecoder = nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
@ -151,8 +161,40 @@ H264Converter::CreateDecoderAndInit(MediaRawData* aSample)
|
|||
UpdateConfigFromExtraData(extra_data);
|
||||
|
||||
nsresult rv = CreateDecoder();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return Init();
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
mDecoderInitializing = true;
|
||||
nsRefPtr<H264Converter> self = this;
|
||||
|
||||
// The mVideoTaskQueue is flushable which can't be used in MediaPromise. So
|
||||
// we get the current AbstractThread instead of it. The MOZ_ASSERT above
|
||||
// ensures we are running in AbstractThread so we won't get a nullptr.
|
||||
mInitPromiseRequest.Begin(mDecoder->Init()
|
||||
->Then(AbstractThread::GetCurrent(), __func__, this,
|
||||
&H264Converter::OnDecoderInitDone,
|
||||
&H264Converter::OnDecoderInitFailed));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
H264Converter::OnDecoderInitDone(const TrackType aTrackType)
|
||||
{
|
||||
mInitPromiseRequest.Complete();
|
||||
for (uint32_t i = 0 ; i < mMediaRawSamples.Length(); i++) {
|
||||
if (NS_FAILED(mDecoder->Input(mMediaRawSamples[i]))) {
|
||||
mCallback->Error();
|
||||
}
|
||||
}
|
||||
mMediaRawSamples.Clear();
|
||||
mDecoderInitializing = false;
|
||||
}
|
||||
|
||||
void
|
||||
H264Converter::OnDecoderInitFailed(MediaDataDecoder::DecoderFailureReason aReason)
|
||||
{
|
||||
mInitPromiseRequest.Complete();
|
||||
mCallback->Error();
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
MediaDataDecoderCallback* aCallback);
|
||||
virtual ~H264Converter();
|
||||
|
||||
virtual nsresult Init() override;
|
||||
virtual nsRefPtr<InitPromise> Init() override;
|
||||
virtual nsresult Input(MediaRawData* aSample) override;
|
||||
virtual nsresult Flush() override;
|
||||
virtual nsresult Drain() override;
|
||||
|
@ -48,14 +48,20 @@ private:
|
|||
nsresult CheckForSPSChange(MediaRawData* aSample);
|
||||
void UpdateConfigFromExtraData(MediaByteBuffer* aExtraData);
|
||||
|
||||
void OnDecoderInitDone(const TrackType aTrackType);
|
||||
void OnDecoderInitFailed(MediaDataDecoder::DecoderFailureReason aReason);
|
||||
|
||||
nsRefPtr<PlatformDecoderModule> mPDM;
|
||||
VideoInfo mCurrentConfig;
|
||||
layers::LayersBackend mLayersBackend;
|
||||
nsRefPtr<layers::ImageContainer> mImageContainer;
|
||||
nsRefPtr<FlushableTaskQueue> mVideoTaskQueue;
|
||||
nsTArray<nsRefPtr<MediaRawData>> mMediaRawSamples;
|
||||
MediaDataDecoderCallback* mCallback;
|
||||
nsRefPtr<MediaDataDecoder> mDecoder;
|
||||
MozPromiseRequestHolder<InitPromise> mInitPromiseRequest;
|
||||
bool mNeedAVCC;
|
||||
bool mDecoderInitializing;
|
||||
nsresult mLastError;
|
||||
};
|
||||
|
||||
|
|
|
@ -173,6 +173,8 @@ AudioNode::DisconnectFromGraph()
|
|||
// It doesn't matter which one we remove, since we're going to remove all
|
||||
// entries for this node anyway.
|
||||
output->mInputNodes.RemoveElementAt(inputIndex);
|
||||
// This effects of this connection will remain.
|
||||
output->NotifyHasPhantomInput();
|
||||
}
|
||||
|
||||
while (!mOutputParams.IsEmpty()) {
|
||||
|
@ -227,6 +229,7 @@ AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput,
|
|||
static_cast<uint16_t>(aInput),
|
||||
static_cast<uint16_t>(aOutput));
|
||||
}
|
||||
aDestination.NotifyInputsChanged();
|
||||
|
||||
// This connection may have connected a panner and a source.
|
||||
Context()->UpdatePannerSource();
|
||||
|
@ -351,6 +354,7 @@ AudioNode::Disconnect(uint32_t aOutput, ErrorResult& aRv)
|
|||
// could be for different output ports.
|
||||
nsRefPtr<AudioNode> output = mOutputNodes[i].forget();
|
||||
mOutputNodes.RemoveElementAt(i);
|
||||
output->NotifyInputsChanged();
|
||||
if (mStream) {
|
||||
nsRefPtr<nsIRunnable> runnable = new RunnableRelease(output.forget());
|
||||
mStream->RunAfterPendingUpdates(runnable.forget());
|
||||
|
|
|
@ -97,6 +97,15 @@ public:
|
|||
|
||||
virtual void Disconnect(uint32_t aOutput, ErrorResult& aRv);
|
||||
|
||||
// Called after input nodes have been explicitly added or removed through
|
||||
// the Connect() or Disconnect() methods.
|
||||
virtual void NotifyInputsChanged() {}
|
||||
// Indicate that the node should continue indefinitely to behave as if an
|
||||
// input is connected, even though there is no longer a corresponding entry
|
||||
// in mInputNodes. Called after an input node has been removed because it
|
||||
// is being garbage collected.
|
||||
virtual void NotifyHasPhantomInput() {}
|
||||
|
||||
// The following two virtual methods must be implemented by each node type
|
||||
// to provide their number of input and output ports. These numbers are
|
||||
// constant for the lifetime of the node. Both default to 1.
|
||||
|
|
|
@ -268,6 +268,21 @@ public:
|
|||
return mSharedBuffers;
|
||||
}
|
||||
|
||||
enum {
|
||||
IS_CONNECTED,
|
||||
};
|
||||
|
||||
virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override
|
||||
{
|
||||
switch (aIndex) {
|
||||
case IS_CONNECTED:
|
||||
mIsConnected = aParam;
|
||||
break;
|
||||
default:
|
||||
NS_ERROR("Bad Int32Parameter");
|
||||
} // End index switch.
|
||||
}
|
||||
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
|
@ -276,8 +291,7 @@ public:
|
|||
// This node is not connected to anything. Per spec, we don't fire the
|
||||
// onaudioprocess event. We also want to clear out the input and output
|
||||
// buffer queue, and output a null buffer.
|
||||
if (!(aStream->ConsumerCount() ||
|
||||
aStream->AsProcessedStream()->InputPortCount())) {
|
||||
if (!mIsConnected) {
|
||||
aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
mSharedBuffers->Reset();
|
||||
mSeenNonSilenceInput = false;
|
||||
|
@ -379,19 +393,42 @@ private:
|
|||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
nsRefPtr<ScriptProcessorNode> node = static_cast<ScriptProcessorNode*>
|
||||
(mStream->Engine()->NodeMainThread());
|
||||
if (!node) {
|
||||
return NS_OK;
|
||||
nsRefPtr<ThreadSharedFloatArrayBufferList> output;
|
||||
|
||||
auto engine =
|
||||
static_cast<ScriptProcessorNodeEngine*>(mStream->Engine());
|
||||
{
|
||||
auto node = static_cast<ScriptProcessorNode*>
|
||||
(engine->NodeMainThread());
|
||||
if (!node) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (node->HasListenersFor(nsGkAtoms::onaudioprocess)) {
|
||||
output = DispatchAudioProcessEvent(node);
|
||||
}
|
||||
// The node may have been destroyed during event dispatch.
|
||||
}
|
||||
AudioContext* context = node->Context();
|
||||
|
||||
// Append it to our output buffer queue
|
||||
engine->GetSharedBuffers()->
|
||||
FinishProducingOutputBuffer(output, engine->mBufferSize);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Returns the output buffers if set in event handlers.
|
||||
ThreadSharedFloatArrayBufferList*
|
||||
DispatchAudioProcessEvent(ScriptProcessorNode* aNode)
|
||||
{
|
||||
AudioContext* context = aNode->Context();
|
||||
if (!context) {
|
||||
return NS_OK;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(node->GetOwner()))) {
|
||||
return NS_OK;
|
||||
if (NS_WARN_IF(!jsapi.Init(aNode->GetOwner()))) {
|
||||
return nullptr;
|
||||
}
|
||||
JSContext* cx = jsapi.cx();
|
||||
|
||||
|
@ -401,10 +438,10 @@ private:
|
|||
ErrorResult rv;
|
||||
inputBuffer =
|
||||
AudioBuffer::Create(context, mInputChannels.Length(),
|
||||
node->BufferSize(),
|
||||
aNode->BufferSize(),
|
||||
context->SampleRate(), cx, rv);
|
||||
if (rv.Failed()) {
|
||||
return NS_OK;
|
||||
return nullptr;
|
||||
}
|
||||
// Put the channel data inside it
|
||||
for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
|
||||
|
@ -417,33 +454,27 @@ private:
|
|||
// avoid creating the input buffer as well. The AudioProcessingEvent class
|
||||
// knows how to lazily create them if needed once the script tries to access
|
||||
// them. Otherwise, we may be able to get away without creating them!
|
||||
nsRefPtr<AudioProcessingEvent> event = new AudioProcessingEvent(node, nullptr, nullptr);
|
||||
nsRefPtr<AudioProcessingEvent> event =
|
||||
new AudioProcessingEvent(aNode, nullptr, nullptr);
|
||||
event->InitEvent(inputBuffer,
|
||||
mInputChannels.Length(),
|
||||
context->StreamTimeToDOMTime(mPlaybackTime));
|
||||
node->DispatchTrustedEvent(event);
|
||||
aNode->DispatchTrustedEvent(event);
|
||||
|
||||
// Steal the output buffers if they have been set.
|
||||
// Don't create a buffer if it hasn't been used to return output;
|
||||
// FinishProducingOutputBuffer() will optimize output = null.
|
||||
// GetThreadSharedChannelsForRate() may also return null after OOM.
|
||||
nsRefPtr<ThreadSharedFloatArrayBufferList> output;
|
||||
if (event->HasOutputBuffer()) {
|
||||
ErrorResult rv;
|
||||
AudioBuffer* buffer = event->GetOutputBuffer(rv);
|
||||
// HasOutputBuffer() returning true means that GetOutputBuffer()
|
||||
// will not fail.
|
||||
MOZ_ASSERT(!rv.Failed());
|
||||
output = buffer->GetThreadSharedChannelsForRate(cx);
|
||||
return buffer->GetThreadSharedChannelsForRate(cx);
|
||||
}
|
||||
|
||||
// Append it to our output buffer queue
|
||||
auto engine =
|
||||
static_cast<ScriptProcessorNodeEngine*>(mStream->Engine());
|
||||
engine->GetSharedBuffers()->
|
||||
FinishProducingOutputBuffer(output, node->BufferSize());
|
||||
|
||||
return NS_OK;
|
||||
return nullptr;
|
||||
}
|
||||
private:
|
||||
nsRefPtr<AudioNodeStream> mStream;
|
||||
|
@ -466,6 +497,7 @@ private:
|
|||
const uint32_t mBufferSize;
|
||||
// The write index into the current input buffer
|
||||
uint32_t mInputWriteIndex;
|
||||
bool mIsConnected = false;
|
||||
bool mSeenNonSilenceInput;
|
||||
};
|
||||
|
||||
|
@ -509,12 +541,49 @@ ScriptProcessorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
|||
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
void
|
||||
ScriptProcessorNode::EventListenerAdded(nsIAtom* aType)
|
||||
{
|
||||
AudioNode::EventListenerAdded(aType);
|
||||
if (aType == nsGkAtoms::onaudioprocess) {
|
||||
UpdateConnectedStatus();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ScriptProcessorNode::EventListenerRemoved(nsIAtom* aType)
|
||||
{
|
||||
AudioNode::EventListenerRemoved(aType);
|
||||
if (aType == nsGkAtoms::onaudioprocess) {
|
||||
UpdateConnectedStatus();
|
||||
}
|
||||
}
|
||||
|
||||
JSObject*
|
||||
ScriptProcessorNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
{
|
||||
return ScriptProcessorNodeBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void
|
||||
ScriptProcessorNode::UpdateConnectedStatus()
|
||||
{
|
||||
bool isConnected = mHasPhantomInput ||
|
||||
!(OutputNodes().IsEmpty() && OutputParams().IsEmpty()
|
||||
&& InputNodes().IsEmpty());
|
||||
|
||||
// Events are queued even when there is no listener because a listener
|
||||
// may be added while events are in the queue.
|
||||
SendInt32ParameterToStream(ScriptProcessorNodeEngine::IS_CONNECTED,
|
||||
isConnected);
|
||||
|
||||
if (isConnected && HasListenersFor(nsGkAtoms::onaudioprocess)) {
|
||||
MarkActive();
|
||||
} else {
|
||||
MarkInactive();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -28,6 +28,9 @@ public:
|
|||
|
||||
IMPL_EVENT_HANDLER(audioprocess)
|
||||
|
||||
virtual void EventListenerAdded(nsIAtom* aType) override;
|
||||
virtual void EventListenerRemoved(nsIAtom* aType) override;
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
virtual void Connect(AudioNode& aDestination, uint32_t aOutput,
|
||||
|
@ -35,7 +38,7 @@ public:
|
|||
{
|
||||
AudioNode::Connect(aDestination, aOutput, aInput, aRv);
|
||||
if (!aRv.Failed()) {
|
||||
MarkActive();
|
||||
UpdateConnectedStatus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,17 +47,27 @@ public:
|
|||
{
|
||||
AudioNode::Connect(aDestination, aOutput, aRv);
|
||||
if (!aRv.Failed()) {
|
||||
MarkActive();
|
||||
UpdateConnectedStatus();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Disconnect(uint32_t aOutput, ErrorResult& aRv) override
|
||||
{
|
||||
AudioNode::Disconnect(aOutput, aRv);
|
||||
if (!aRv.Failed() && OutputNodes().IsEmpty() && OutputParams().IsEmpty()) {
|
||||
MarkInactive();
|
||||
if (!aRv.Failed()) {
|
||||
UpdateConnectedStatus();
|
||||
}
|
||||
}
|
||||
virtual void NotifyInputsChanged() override
|
||||
{
|
||||
UpdateConnectedStatus();
|
||||
}
|
||||
virtual void NotifyHasPhantomInput() override
|
||||
{
|
||||
mHasPhantomInput = true;
|
||||
// No need to UpdateConnectedStatus() because there was previously an
|
||||
// input in InputNodes().
|
||||
}
|
||||
|
||||
virtual void SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) override
|
||||
{
|
||||
|
@ -91,12 +104,14 @@ public:
|
|||
virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
|
||||
virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
|
||||
|
||||
protected:
|
||||
private:
|
||||
virtual ~ScriptProcessorNode();
|
||||
|
||||
private:
|
||||
void UpdateConnectedStatus();
|
||||
|
||||
const uint32_t mBufferSize;
|
||||
const uint32_t mNumberOfOutputChannels;
|
||||
bool mHasPhantomInput = false;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -15,7 +15,7 @@ addLoadEvent(function() {
|
|||
var source = ctx.createBufferSource();
|
||||
var b0 = ctx.createBuffer(32,798,22050);
|
||||
var b1 = ctx.createBuffer(32,28,22050);
|
||||
var sp = ctx.createScriptProcessor();
|
||||
var sp = ctx.createScriptProcessor(0, 2, 0);
|
||||
source.buffer = b0;
|
||||
source.connect(sp);
|
||||
source.start(0);
|
||||
|
@ -25,7 +25,6 @@ addLoadEvent(function() {
|
|||
sp.onaudioprocess = null;
|
||||
SimpleTest.finish();
|
||||
};
|
||||
sp.connect(ctx.destination); // work around bug 916387
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -63,8 +63,6 @@ function startTest() {
|
|||
|
||||
var delayProcessor = ctx.createScriptProcessor(bufferSize, 1, 0);
|
||||
delayProcessor.onaudioprocess = onDelayOutput;
|
||||
// Work around bug 916387.
|
||||
delayProcessor.connect(ctx.destination);
|
||||
|
||||
var delayDuration = delayLength / ctx.sampleRate;
|
||||
for (var i = 0; i < sourceCount; ++i) {
|
||||
|
|
|
@ -111,7 +111,6 @@ function startTest() {
|
|||
var processor1 = ctx.createScriptProcessor(bufferSize, 1, 0);
|
||||
delay.connect(processor1);
|
||||
processor1.onaudioprocess = onDelayOutput;
|
||||
processor1.connect(ctx.destination); // work around bug 916387
|
||||
|
||||
// Signal to trigger initial tail time reference
|
||||
oscillator = ctx.createOscillator();
|
||||
|
@ -128,7 +127,6 @@ function startTest() {
|
|||
var processor2 = ctx.createScriptProcessor(bufferSize, 1, 0);
|
||||
source.connect(processor2);
|
||||
processor2.onaudioprocess = onSourceOutput;
|
||||
processor2.connect(ctx.destination); // guard against bug 916387
|
||||
};
|
||||
|
||||
startTest();
|
||||
|
|
|
@ -15,9 +15,8 @@ function test() {
|
|||
var audio = new Audio("small-shot.ogg");
|
||||
var context = new AudioContext();
|
||||
var node = context.createMediaStreamSource(audio.mozCaptureStreamUntilEnded());
|
||||
var sp = context.createScriptProcessor(2048, 1);
|
||||
var sp = context.createScriptProcessor(2048, 1, 0);
|
||||
node.connect(sp);
|
||||
sp.connect(context.destination); // work around bug 916387
|
||||
var expectedMinNonzeroSampleCount;
|
||||
var expectedMaxNonzeroSampleCount;
|
||||
var nonzeroSampleCount = 0;
|
||||
|
|
|
@ -14,11 +14,9 @@ addLoadEvent(function() {
|
|||
|
||||
var context = new AudioContext();
|
||||
var osc = context.createOscillator();
|
||||
var sp = context.createScriptProcessor();
|
||||
var sp = context.createScriptProcessor(0, 1, 0);
|
||||
|
||||
osc.connect(sp);
|
||||
// Work around bug 916387.
|
||||
sp.connect(context.destination);
|
||||
|
||||
sp.onaudioprocess = function (e) {
|
||||
var input = e.inputBuffer.getChannelData(0);
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче