зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound on a CLOSED TREE
--HG-- extra : amend_source : bfda8e6ef1895166a8cbe1942b205c8b76884722
This commit is contained in:
Коммит
aeff978984
|
@ -12,13 +12,13 @@
|
|||
<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="ac94739a01d64e86890f54f1795ff8e82b8e7939"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87aa8679560ce09f6445621d6f370d9de722cdba"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="fd67604d5932cce3617ff7ce725a0a686d129905"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="788d9ce293a9b44f64536130cf4ad577e8101dbe"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
|
||||
<project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ac94739a01d64e86890f54f1795ff8e82b8e7939"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="fd67604d5932cce3617ff7ce725a0a686d129905"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="788d9ce293a9b44f64536130cf4ad577e8101dbe"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="905bfa3548eb75cf1792d0d8412b92113bbd4318"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="c3d7efc45414f1b44cd9c479bb2758c91c4707c0"/>
|
||||
<!-- Stock Android things -->
|
||||
|
|
|
@ -12,13 +12,13 @@
|
|||
<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="ac94739a01d64e86890f54f1795ff8e82b8e7939"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87aa8679560ce09f6445621d6f370d9de722cdba"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="fd67604d5932cce3617ff7ce725a0a686d129905"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="788d9ce293a9b44f64536130cf4ad577e8101dbe"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
|
||||
<project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"revision": "a4d9c73e176e7dfc9a32d362f0f5e5cb5c21e323",
|
||||
"revision": "cea79abbb7a97c0bd67051087bcdf40d25611930",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
<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="ac94739a01d64e86890f54f1795ff8e82b8e7939"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="fd67604d5932cce3617ff7ce725a0a686d129905"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="788d9ce293a9b44f64536130cf4ad577e8101dbe"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
|
||||
<project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
|
||||
|
|
|
@ -10,7 +10,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="ac94739a01d64e86890f54f1795ff8e82b8e7939"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
<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="ac94739a01d64e86890f54f1795ff8e82b8e7939"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="fd67604d5932cce3617ff7ce725a0a686d129905"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="788d9ce293a9b44f64536130cf4ad577e8101dbe"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
|
||||
<project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
<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="ac94739a01d64e86890f54f1795ff8e82b8e7939"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="fd67604d5932cce3617ff7ce725a0a686d129905"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="788d9ce293a9b44f64536130cf4ad577e8101dbe"/>
|
||||
<project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ac94739a01d64e86890f54f1795ff8e82b8e7939"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="fd67604d5932cce3617ff7ce725a0a686d129905"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="788d9ce293a9b44f64536130cf4ad577e8101dbe"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="905bfa3548eb75cf1792d0d8412b92113bbd4318"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="c3d7efc45414f1b44cd9c479bb2758c91c4707c0"/>
|
||||
<!-- Stock Android things -->
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
<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="ac94739a01d64e86890f54f1795ff8e82b8e7939"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="fd67604d5932cce3617ff7ce725a0a686d129905"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="788d9ce293a9b44f64536130cf4ad577e8101dbe"/>
|
||||
<project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
|
||||
|
|
|
@ -213,7 +213,6 @@ let gFxAccounts = {
|
|||
},
|
||||
|
||||
openSignInAgainPage: function () {
|
||||
// FIXME: This should actually show the pre-filled username version of about:accounts?
|
||||
switchToTabHavingURI("about:accounts?signin=true", true);
|
||||
switchToTabHavingURI("about:accounts?action=reauth", true);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -173,6 +173,7 @@ toolbar[customizing] > .overflow-button {
|
|||
}
|
||||
|
||||
%ifdef CAN_DRAW_IN_TITLEBAR
|
||||
#main-window:not([chromemargin]) > #titlebar,
|
||||
#main-window[inFullscreen] > #titlebar,
|
||||
#main-window[inFullscreen] .titlebar-placeholder,
|
||||
#main-window:not([tabsintitlebar]) .titlebar-placeholder {
|
||||
|
|
|
@ -4481,6 +4481,9 @@ var TabsInTitlebar = {
|
|||
// Try to avoid reflows in this code by calculating dimensions first and
|
||||
// then later set the properties affecting layout together in a batch.
|
||||
|
||||
// Get the full height of the tabs toolbar:
|
||||
let tabsToolbar = $("TabsToolbar");
|
||||
let fullTabsHeight = rect(tabsToolbar).height;
|
||||
// Buttons first:
|
||||
let captionButtonsBoxWidth = rect($("titlebar-buttonbox")).width;
|
||||
#ifdef XP_MACOSX
|
||||
|
@ -4495,11 +4498,9 @@ var TabsInTitlebar = {
|
|||
let menuHeight = rect(menubar).height;
|
||||
let menuStyles = window.getComputedStyle(menubar);
|
||||
let fullMenuHeight = verticalMargins(menuStyles) + menuHeight;
|
||||
#endif
|
||||
// Get the full height of the tabs toolbar:
|
||||
let tabsToolbar = $("TabsToolbar");
|
||||
let tabsStyles = window.getComputedStyle(tabsToolbar);
|
||||
let fullTabsHeight = rect(tabsToolbar).height + verticalMargins(tabsStyles);
|
||||
fullTabsHeight += verticalMargins(tabsStyles);
|
||||
#endif
|
||||
|
||||
// If the navbar overlaps the tabbar using negative margins, we need to take those into
|
||||
// account so we don't overlap it
|
||||
|
@ -4509,16 +4510,6 @@ var TabsInTitlebar = {
|
|||
// And get the height of what's in the titlebar:
|
||||
let titlebarContentHeight = rect(titlebarContent).height;
|
||||
|
||||
// Padding surrounds the tab-view-deck when we are in customization mode,
|
||||
// so take that into account:
|
||||
let areCustomizing = document.documentElement.hasAttribute("customizing") ||
|
||||
document.documentElement.hasAttribute("customize-exiting");
|
||||
let customizePadding = 0;
|
||||
if (areCustomizing) {
|
||||
let deckStyle = window.getComputedStyle($("tab-view-deck"));
|
||||
customizePadding = parseFloat(deckStyle.paddingTop);
|
||||
}
|
||||
|
||||
// Begin setting CSS properties which will cause a reflow
|
||||
|
||||
// If the menubar is around (menuHeight is non-zero), try to adjust
|
||||
|
@ -4551,10 +4542,6 @@ var TabsInTitlebar = {
|
|||
// Next, we calculate how much we need to stretch the titlebar down to
|
||||
// go all the way to the bottom of the tab strip, if necessary.
|
||||
let tabAndMenuHeight = fullTabsHeight + fullMenuHeight;
|
||||
// Oh, and don't forget customization mode:
|
||||
if (areCustomizing) {
|
||||
tabAndMenuHeight += customizePadding;
|
||||
}
|
||||
|
||||
if (tabAndMenuHeight > titlebarContentHeight) {
|
||||
// We need to increase the titlebar content's outer height (ie including margins)
|
||||
|
@ -4566,12 +4553,6 @@ var TabsInTitlebar = {
|
|||
// On non-OSX, we can just use bottom margin:
|
||||
#ifndef XP_MACOSX
|
||||
titlebarContent.style.marginBottom = extraMargin + "px";
|
||||
#else
|
||||
// Otherwise, center the content. This means taking the titlebar's
|
||||
// padding into account:
|
||||
let halfMargin = (extraMargin - titlebarPadding) / 2;
|
||||
titlebarContent.style.marginTop = halfMargin + "px";
|
||||
titlebarContent.style.marginBottom = (titlebarPadding + halfMargin) + "px";
|
||||
#endif
|
||||
titlebarContentHeight += extraMargin;
|
||||
}
|
||||
|
@ -4606,6 +4587,7 @@ var TabsInTitlebar = {
|
|||
updateTitlebarDisplay();
|
||||
|
||||
// Reset the margins and padding that might have been modified:
|
||||
titlebarContent.style.marginTop = "";
|
||||
titlebarContent.style.marginBottom = "";
|
||||
titlebar.style.marginBottom = "";
|
||||
menubar.style.paddingBottom = "";
|
||||
|
@ -4630,16 +4612,37 @@ var TabsInTitlebar = {
|
|||
|
||||
#ifdef CAN_DRAW_IN_TITLEBAR
|
||||
function updateTitlebarDisplay() {
|
||||
document.getElementById("titlebar").hidden = !TabsInTitlebar.enabled;
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
// OS X and the other platforms differ enough to necessitate this kind of
|
||||
// special-casing. Like the other platforms where we CAN_DRAW_IN_TITLEBAR,
|
||||
// we draw in the OS X titlebar when putting the tabs up there. However, OS X
|
||||
// also draws in the titlebar when a lightweight theme is applied, regardless
|
||||
// of whether or not the tabs are drawn in the titlebar.
|
||||
if (TabsInTitlebar.enabled) {
|
||||
document.documentElement.setAttribute("chromemargin-nonlwtheme", "0,-1,-1,-1");
|
||||
document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1");
|
||||
document.documentElement.removeAttribute("drawtitle");
|
||||
} else {
|
||||
// We set chromemargin-nonlwtheme to "" instead of removing it as a way of
|
||||
// making sure that LightweightThemeConsumer doesn't take it upon itself to
|
||||
// detect this value again if and when we do a lwtheme state change.
|
||||
document.documentElement.setAttribute("chromemargin-nonlwtheme", "");
|
||||
let isCustomizing = document.documentElement.hasAttribute("customizing");
|
||||
let hasLWTheme = document.documentElement.hasAttribute("lwtheme");
|
||||
if (!hasLWTheme || isCustomizing) {
|
||||
document.documentElement.removeAttribute("chromemargin");
|
||||
}
|
||||
document.documentElement.setAttribute("drawtitle", "true");
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
if (TabsInTitlebar.enabled)
|
||||
#ifdef XP_WIN
|
||||
document.documentElement.setAttribute("chromemargin", "0,2,2,2");
|
||||
#else
|
||||
document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1");
|
||||
#endif
|
||||
else
|
||||
document.documentElement.removeAttribute("chromemargin");
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
frame1.docShell.chromeEventHandler.removeAttribute("crashedPageTitle");
|
||||
|
||||
SimpleTest.is(frame1.contentDocument.documentURI,
|
||||
"about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/1&c=UTF-8&d=pageTitle&f=regular",
|
||||
"about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/1&c=UTF-8&f=regular&d=pageTitle",
|
||||
"Correct about:tabcrashed displayed for page with title.");
|
||||
|
||||
errorPageReady = waitForErrorPage(frame2);
|
||||
|
@ -74,7 +74,7 @@
|
|||
yield errorPageReady;
|
||||
|
||||
SimpleTest.is(frame2.contentDocument.documentURI,
|
||||
"about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/2&c=UTF-8&d=%20&f=regular",
|
||||
"about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/2&c=UTF-8&f=regular&d=%20",
|
||||
"Correct about:tabcrashed displayed for page with no title.");
|
||||
|
||||
SimpleTest.finish();
|
||||
|
|
|
@ -313,7 +313,8 @@ skip-if = true # disabled until the tree view is added
|
|||
[browser_tab_dragdrop.js]
|
||||
[browser_tab_dragdrop2.js]
|
||||
[browser_tabbar_big_widgets.js]
|
||||
skip-if = os == "linux" # No tabs in titlebar on linux
|
||||
skip-if = os == "linux" || os == "mac" # No tabs in titlebar on linux
|
||||
# Disabled on OS X because of bug 967917
|
||||
[browser_tabfocus.js]
|
||||
[browser_tabopen_reflows.js]
|
||||
[browser_tabs_isActive.js]
|
||||
|
|
|
@ -123,9 +123,6 @@ const CustomizableWidgets = [{
|
|||
item.setAttribute("label", title || uri);
|
||||
item.setAttribute("targetURI", uri);
|
||||
item.setAttribute("class", "subviewbutton");
|
||||
item.addEventListener("command", function (aEvent) {
|
||||
onHistoryVisit(uri, aEvent, item);
|
||||
});
|
||||
item.addEventListener("click", function (aEvent) {
|
||||
onHistoryVisit(uri, aEvent, item);
|
||||
});
|
||||
|
|
|
@ -13,6 +13,8 @@ support-files =
|
|||
# Bug 916763 - too many intermittent failures
|
||||
skip-if = true
|
||||
[browser_inspector_markup_edit.js]
|
||||
# Bug 904953 - too many intermittent failures on Linux
|
||||
skip-if = os == "linux"
|
||||
[browser_inspector_markup_edit_outerhtml.js]
|
||||
[browser_inspector_markup_edit_outerhtml2.js]
|
||||
[browser_inspector_markup_mutation.js]
|
||||
|
|
|
@ -1143,8 +1143,7 @@ var BrowserUI = {
|
|||
confirmSanitizeDialog: function () {
|
||||
let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
let title = bundle.GetStringFromName("clearPrivateData.title2");
|
||||
let options = bundle.GetStringFromName("optionsCharm");
|
||||
let message = bundle.GetStringFromName("clearPrivateData.message2").replace("#1", options);
|
||||
let message = bundle.GetStringFromName("clearPrivateData.message3");
|
||||
let clearbutton = bundle.GetStringFromName("clearPrivateData.clearButton");
|
||||
|
||||
let prefsClearButton = document.getElementById("prefs-clear-data");
|
||||
|
|
|
@ -9,16 +9,18 @@ function test() {
|
|||
runTests();
|
||||
}
|
||||
|
||||
function getSimpleMeasurementsFromTelemetryPing() {
|
||||
function getTelemetryPayload() {
|
||||
return Cu.import("resource://gre/modules/TelemetryPing.jsm", {}).
|
||||
TelemetryPing.getPayload().simpleMeasurements;
|
||||
TelemetryPing.getPayload();
|
||||
}
|
||||
|
||||
gTests.push({
|
||||
desc: "Test browser-ui telemetry",
|
||||
run: function testBrowserUITelemetry() {
|
||||
// startup should have registered simple measures function
|
||||
let simpleMeasurements = getSimpleMeasurementsFromTelemetryPing();
|
||||
is(getTelemetryPayload().info.appName, "MetroFirefox");
|
||||
|
||||
let simpleMeasurements = getTelemetryPayload().simpleMeasurements;
|
||||
ok(simpleMeasurements, "simpleMeasurements are truthy");
|
||||
ok(simpleMeasurements.UITelemetry["metro-ui"]["window-width"], "window-width measurement was captured");
|
||||
ok(simpleMeasurements.UITelemetry["metro-ui"]["window-height"], "window-height measurement was captured");
|
||||
|
|
|
@ -39,8 +39,8 @@ contextAppbar2.clear=Clear selection
|
|||
# Clear private data
|
||||
clearPrivateData.clearButton=Clear
|
||||
clearPrivateData.title2=Clear private data
|
||||
# LOCALIZATION NOTE (clearPrivateData.message2): #1 is optionsCharm
|
||||
clearPrivateData.message2=This will permanently delete the private data you have selected in #1
|
||||
# LOCALIZATION NOTE (clearPrivateData.message3): "Options" is the optionsCharm.
|
||||
clearPrivateData.message3=This will permanently delete the private data you have selected in "Options".
|
||||
|
||||
# Settings Charms
|
||||
aboutCharm1=About
|
||||
|
|
|
@ -73,6 +73,7 @@ browser.jar:
|
|||
skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png)
|
||||
skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png)
|
||||
skin/classic/browser/customizableui/customizeFavicon.ico (../shared/customizableui/customizeFavicon.ico)
|
||||
skin/classic/browser/customizableui/menuPanel-customizeFinish.png (../shared/customizableui/menuPanel-customizeFinish.png)
|
||||
* skin/classic/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css)
|
||||
skin/classic/browser/customizableui/subView-arrow-back-inverted.png (../shared/customizableui/subView-arrow-back-inverted.png)
|
||||
skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
#main-window[chromehidden~="toolbar"] > #titlebar {
|
||||
#main-window[chromehidden~="toolbar"]:not(:-moz-lwtheme) > #titlebar {
|
||||
padding-top: 22px;
|
||||
}
|
||||
|
||||
|
@ -2669,7 +2669,7 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
|||
box-shadow: @focusRingShadow@;
|
||||
}
|
||||
|
||||
#titlebar {
|
||||
#main-window:not(:-moz-lwtheme) > #titlebar {
|
||||
padding-top: @spaceAboveTabbar@;
|
||||
min-height: @tabHeight@;
|
||||
}
|
||||
|
@ -4104,13 +4104,26 @@ window > chatbox {
|
|||
|
||||
%include ../shared/customizableui/customizeMode.inc.css
|
||||
|
||||
#main-window[customize-entered] #titlebar {
|
||||
#main-window[customize-entered] > #titlebar {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#main-window[tabsintitlebar][customize-entered] #titlebar-content {
|
||||
margin-bottom: 0px !important;
|
||||
margin-top: 11px !important;
|
||||
#main-window[tabsintitlebar]:not([customizing]):not(:-moz-lwtheme) > #titlebar > #titlebar-content,
|
||||
#main-window[tabsintitlebar][customize-entering] > #titlebar > #titlebar-content,
|
||||
#main-window[tabsintitlebar][customize-exiting] > #titlebar > #titlebar-content {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
|
||||
#main-window[tabsintitlebar][customize-entered] > #titlebar > #titlebar-content,
|
||||
#main-window:not([tabsintitlebar]):not(:-moz-lwtheme) > #titlebar > #titlebar-content {
|
||||
margin-top: 11px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
#main-window[tabsintitlebar]:-moz-lwtheme > #titlebar > #titlebar-content {
|
||||
margin-top: 11px;
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
|
||||
#main-window[customize-entered] #tab-view-deck {
|
||||
|
@ -4135,8 +4148,11 @@ window > chatbox {
|
|||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
#main-window[customize-entered] #TabsToolbar {
|
||||
#main-window[tabsintitlebar][customize-entered] #TabsToolbar {
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
#main-window[customize-entered] #TabsToolbar {
|
||||
background-clip: padding-box;
|
||||
border-right: 3px solid transparent;
|
||||
border-left: 3px solid transparent;
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
list-style-image: url(chrome://browser/skin/menuPanel-customize@2x.png);
|
||||
}
|
||||
|
||||
#main-window[customize-entered] #PanelUI-customize {
|
||||
list-style-image: url(chrome://browser/skin/customizableui/menuPanel-customizeFinish@2x.png);
|
||||
}
|
||||
|
||||
#PanelUI-help {
|
||||
list-style-image: url(chrome://browser/skin/menuPanel-help@2x.png);
|
||||
}
|
||||
|
|
|
@ -120,6 +120,8 @@ browser.jar:
|
|||
skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png)
|
||||
skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png)
|
||||
skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png)
|
||||
skin/classic/browser/customizableui/menuPanel-customizeFinish.png (../shared/customizableui/menuPanel-customizeFinish.png)
|
||||
skin/classic/browser/customizableui/menuPanel-customizeFinish@2x.png (../shared/customizableui/menuPanel-customizeFinish@2x.png)
|
||||
skin/classic/browser/customizableui/subView-arrow-back-inverted.png (../shared/customizableui/subView-arrow-back-inverted.png)
|
||||
skin/classic/browser/customizableui/subView-arrow-back-inverted@2x.png (../shared/customizableui/subView-arrow-back-inverted@2x.png)
|
||||
* skin/classic/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css)
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 236 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 399 B |
|
@ -322,6 +322,10 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
list-style-image: url(chrome://browser/skin/menuPanel-customize.png);
|
||||
}
|
||||
|
||||
#customization-panelHolder #PanelUI-customize {
|
||||
list-style-image: url(chrome://browser/skin/customizableui/menuPanel-customizeFinish.png);
|
||||
}
|
||||
|
||||
#PanelUI-help {
|
||||
list-style-image: url(chrome://browser/skin/menuPanel-help.png);
|
||||
}
|
||||
|
@ -378,16 +382,15 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
background-color: #ad3434;
|
||||
}
|
||||
|
||||
#main-window[customize-entered] #PanelUI-customize {
|
||||
#customization-panelHolder #PanelUI-customize {
|
||||
color: white;
|
||||
background-image: linear-gradient(rgb(41,123,204), rgb(40,133,203));
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.5), 0 2px rgba(255,255,255,0.2);
|
||||
text-shadow: 0 1px 0 rgba(0,0,0,0.4);
|
||||
background-color: rgb(116,191,67);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
#main-window[customize-entered] #PanelUI-customize:hover,
|
||||
#main-window[customize-entered] #PanelUI-customize:hover:active {
|
||||
background-image: linear-gradient(rgb(38,115,191), rgb(38,125,191));
|
||||
#customization-panelHolder #PanelUI-customize:hover,
|
||||
#customization-panelHolder #PanelUI-customize:hover:active {
|
||||
background-color: rgb(105,173,61);
|
||||
}
|
||||
|
||||
#customization-palette .toolbarbutton-multiline-text,
|
||||
|
|
|
@ -92,6 +92,7 @@ browser.jar:
|
|||
skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png)
|
||||
skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png)
|
||||
skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png)
|
||||
skin/classic/browser/customizableui/menuPanel-customizeFinish.png (../shared/customizableui/menuPanel-customizeFinish.png)
|
||||
* skin/classic/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css)
|
||||
skin/classic/browser/customizableui/subView-arrow-back-inverted.png (../shared/customizableui/subView-arrow-back-inverted.png)
|
||||
* skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
|
||||
|
@ -404,6 +405,7 @@ browser.jar:
|
|||
skin/classic/aero/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png)
|
||||
skin/classic/aero/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png)
|
||||
skin/classic/aero/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png)
|
||||
skin/classic/aero/browser/customizableui/menuPanel-customizeFinish.png (../shared/customizableui/menuPanel-customizeFinish.png)
|
||||
* skin/classic/aero/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css)
|
||||
skin/classic/aero/browser/customizableui/subView-arrow-back-inverted.png (../shared/customizableui/subView-arrow-back-inverted.png)
|
||||
* skin/classic/aero/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay-aero.css)
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/* -*- 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 "AudioCompactor.h"
|
||||
#if defined(MOZ_MEMORY)
|
||||
# include "mozmemory.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
static size_t
|
||||
MallocGoodSize(size_t aSize)
|
||||
{
|
||||
# if defined(MOZ_MEMORY)
|
||||
return malloc_good_size(aSize);
|
||||
# else
|
||||
return aSize;
|
||||
# endif
|
||||
}
|
||||
|
||||
static size_t
|
||||
TooMuchSlop(size_t aSize, size_t aAllocSize, size_t aMaxSlop)
|
||||
{
|
||||
// If the allocated size is less then our target size, then we
|
||||
// are chunking. This means it will be completely filled with
|
||||
// zero slop.
|
||||
size_t slop = (aAllocSize > aSize) ? (aAllocSize - aSize) : 0;
|
||||
return slop > aMaxSlop;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
AudioCompactor::GetChunkSamples(uint32_t aFrames, uint32_t aChannels,
|
||||
size_t aMaxSlop)
|
||||
{
|
||||
size_t size = AudioDataSize(aFrames, aChannels);
|
||||
size_t chunkSize = MallocGoodSize(size);
|
||||
|
||||
// Reduce the chunk size until we meet our slop goal or the chunk
|
||||
// approaches an unreasonably small size.
|
||||
while (chunkSize > 64 && TooMuchSlop(size, chunkSize, aMaxSlop)) {
|
||||
chunkSize = MallocGoodSize(chunkSize / 2);
|
||||
}
|
||||
|
||||
// Calculate the number of samples based on expected malloc size
|
||||
// in order to allow as many frames as possible to be packed.
|
||||
return chunkSize / sizeof(AudioDataValue);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
AudioCompactor::NativeCopy::operator()(AudioDataValue *aBuffer, size_t aSamples)
|
||||
{
|
||||
NS_ASSERTION(aBuffer, "cannot copy to null buffer pointer");
|
||||
NS_ASSERTION(aSamples, "cannot copy zero values");
|
||||
|
||||
size_t bufferBytes = aSamples * sizeof(AudioDataValue);
|
||||
size_t maxBytes = std::min(bufferBytes, mSourceBytes - mNextByte);
|
||||
uint32_t frames = maxBytes / BytesPerFrame(mChannels);
|
||||
size_t bytes = frames * BytesPerFrame(mChannels);
|
||||
|
||||
NS_ASSERTION((mNextByte + bytes) <= mSourceBytes,
|
||||
"tried to copy beyond source buffer");
|
||||
NS_ASSERTION(bytes <= bufferBytes, "tried to copy beyond destination buffer");
|
||||
|
||||
memcpy(aBuffer, mSource + mNextByte, bytes);
|
||||
|
||||
mNextByte += bytes;
|
||||
return frames;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,121 @@
|
|||
/* -*- 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/. */
|
||||
#if !defined(AudioCompactor_h)
|
||||
#define AudioCompactor_h
|
||||
|
||||
#include "MediaQueue.h"
|
||||
#include "MediaData.h"
|
||||
#include "VideoUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class AudioCompactor
|
||||
{
|
||||
public:
|
||||
AudioCompactor(MediaQueue<AudioData>& aQueue)
|
||||
: mQueue(aQueue)
|
||||
{ }
|
||||
|
||||
// Push audio data into the underlying queue with minimal heap allocation
|
||||
// slop. This method is responsible for allocating AudioDataValue[] buffers.
|
||||
// The caller must provide a functor to copy the data into the buffers. The
|
||||
// functor must provide the following signature:
|
||||
//
|
||||
// uint32_t operator()(AudioDataValue *aBuffer, size_t aSamples);
|
||||
//
|
||||
// The functor must copy as many complete frames as possible to the provided
|
||||
// buffer given its length (in AudioDataValue elements). The number of frames
|
||||
// copied must be returned. This copy functor must support being called
|
||||
// multiple times in order to copy the audio data fully. The copy functor
|
||||
// must copy full frames as partial frames will be ignored.
|
||||
template<typename CopyFunc>
|
||||
bool Push(int64_t aOffset, int64_t aTime, int32_t aSampleRate,
|
||||
uint32_t aFrames, uint32_t aChannels, CopyFunc aCopyFunc)
|
||||
{
|
||||
// If we are losing more than a reasonable amount to padding, try to chunk
|
||||
// the data.
|
||||
size_t maxSlop = AudioDataSize(aFrames, aChannels) / MAX_SLOP_DIVISOR;
|
||||
|
||||
while (aFrames > 0) {
|
||||
uint32_t samples = GetChunkSamples(aFrames, aChannels, maxSlop);
|
||||
nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[samples]);
|
||||
|
||||
// Copy audio data to buffer using caller-provided functor.
|
||||
uint32_t framesCopied = aCopyFunc(buffer, samples);
|
||||
|
||||
NS_ASSERTION(framesCopied <= aFrames, "functor copied too many frames");
|
||||
|
||||
CheckedInt64 duration = FramesToUsecs(framesCopied, aSampleRate);
|
||||
if (!duration.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mQueue.Push(new AudioData(aOffset,
|
||||
aTime,
|
||||
duration.value(),
|
||||
framesCopied,
|
||||
buffer.forget(),
|
||||
aChannels));
|
||||
|
||||
// Remove the frames we just pushed into the queue and loop if there is
|
||||
// more to be done.
|
||||
aTime += duration.value();
|
||||
aFrames -= framesCopied;
|
||||
|
||||
// NOTE: No need to update aOffset as its only an approximation anyway.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Copy functor suitable for copying audio samples already in the
|
||||
// AudioDataValue format/layout expected by AudioStream on this platform.
|
||||
class NativeCopy
|
||||
{
|
||||
public:
|
||||
NativeCopy(const uint8_t* aSource, size_t aSourceBytes,
|
||||
uint32_t aChannels)
|
||||
: mSource(aSource)
|
||||
, mSourceBytes(aSourceBytes)
|
||||
, mChannels(aChannels)
|
||||
, mNextByte(0)
|
||||
{ }
|
||||
|
||||
uint32_t operator()(AudioDataValue *aBuffer, size_t aSamples);
|
||||
|
||||
private:
|
||||
const uint8_t* const mSource;
|
||||
const size_t mSourceBytes;
|
||||
const uint32_t mChannels;
|
||||
size_t mNextByte;
|
||||
};
|
||||
|
||||
// Allow 12.5% slop before chunking kicks in. Public so that the gtest can
|
||||
// access it.
|
||||
static const size_t MAX_SLOP_DIVISOR = 8;
|
||||
|
||||
private:
|
||||
// Compute the number of AudioDataValue samples that will be fit the most
|
||||
// frames while keeping heap allocation slop less than the given threshold.
|
||||
static uint32_t
|
||||
GetChunkSamples(uint32_t aFrames, uint32_t aChannels, size_t aMaxSlop);
|
||||
|
||||
static size_t BytesPerFrame(uint32_t aChannels)
|
||||
{
|
||||
return sizeof(AudioDataValue) * aChannels;
|
||||
}
|
||||
|
||||
static size_t AudioDataSize(uint32_t aFrames, uint32_t aChannels)
|
||||
{
|
||||
return aFrames * BytesPerFrame(aChannels);
|
||||
}
|
||||
|
||||
MediaQueue<AudioData> &mQueue;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // AudioCompactor_h
|
|
@ -45,7 +45,8 @@ void* MediaDecoderReader::VideoQueueMemoryFunctor::operator()(void* anObject) {
|
|||
}
|
||||
|
||||
MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder)
|
||||
: mDecoder(aDecoder),
|
||||
: mAudioCompactor(mAudioQueue),
|
||||
mDecoder(aDecoder),
|
||||
mIgnoreAudioOutputFormat(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(MediaDecoderReader);
|
||||
|
@ -280,4 +281,3 @@ MediaDecoderReader::GetBuffered(mozilla::dom::TimeRanges* aBuffered,
|
|||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "MediaInfo.h"
|
||||
#include "MediaData.h"
|
||||
#include "MediaQueue.h"
|
||||
#include "AudioCompactor.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -105,6 +106,12 @@ protected:
|
|||
// the decoder, state machine, and main threads.
|
||||
MediaQueue<VideoData> mVideoQueue;
|
||||
|
||||
// An adapter to the audio queue which first copies data to buffers with
|
||||
// minimal allocation slop and then pushes them to the queue. This is
|
||||
// useful for decoders working with formats that give awkward numbers of
|
||||
// frames such as mp3.
|
||||
AudioCompactor mAudioCompactor;
|
||||
|
||||
public:
|
||||
// Populates aBuffered with the time ranges which are buffered. aStartTime
|
||||
// must be the presentation time of the first frame in the media, e.g.
|
||||
|
|
|
@ -45,6 +45,13 @@ extern PRLogModuleInfo* gMediaDecoderLog;
|
|||
#define DECODER_LOG(type, msg)
|
||||
#endif
|
||||
|
||||
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
|
||||
// GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
|
||||
// implementation. With unified builds, putting this in headers is not enough.
|
||||
#ifdef GetCurrentTime
|
||||
#undef GetCurrentTime
|
||||
#endif
|
||||
|
||||
// Wait this number of seconds when buffering, then leave and play
|
||||
// as best as we can if the required amount of data hasn't been
|
||||
// retrieved.
|
||||
|
|
|
@ -92,6 +92,13 @@ namespace mozilla {
|
|||
class AudioSegment;
|
||||
class VideoSegment;
|
||||
|
||||
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
|
||||
// GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
|
||||
// implementation.
|
||||
#ifdef GetCurrentTime
|
||||
#undef GetCurrentTime
|
||||
#endif
|
||||
|
||||
/*
|
||||
The state machine class. This manages the decoding and seeking in the
|
||||
MediaDecoderReader on the decode thread, and A/V sync on the shared
|
||||
|
|
|
@ -12,8 +12,12 @@
|
|||
#define AUDIO_READ_BYTES 4096
|
||||
|
||||
// Maximum number of audio frames we will accept from the audio decoder in one
|
||||
// go.
|
||||
#define MAX_AUDIO_FRAMES 4096
|
||||
// go. Carefully select this to work well with both the mp3 1152 max frames
|
||||
// per block and power-of-2 allocation sizes. Since we must pre-allocate the
|
||||
// buffer we cannot use AudioCompactor without paying for an additional
|
||||
// allocation and copy. Therefore, choosing a value that divides exactly into
|
||||
// 1152 is most memory efficient.
|
||||
#define MAX_AUDIO_FRAMES 128
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -201,7 +205,8 @@ AppleMP3Reader::AudioSampleCallback(UInt32 aNumBytes,
|
|||
LOGD("got %u bytes, %u packets\n", aNumBytes, aNumPackets);
|
||||
|
||||
// 1 frame per packet * num channels * 32-bit float
|
||||
uint32_t decodedSize = MAX_AUDIO_FRAMES * mAudioChannels * 4;
|
||||
uint32_t decodedSize = MAX_AUDIO_FRAMES * mAudioChannels *
|
||||
sizeof(AudioDataValue);
|
||||
|
||||
// descriptions for _decompressed_ audio packets. ignored.
|
||||
nsAutoArrayPtr<AudioStreamPacketDescription>
|
||||
|
@ -238,6 +243,14 @@ AppleMP3Reader::AudioSampleCallback(UInt32 aNumBytes,
|
|||
break;
|
||||
}
|
||||
|
||||
// If we decoded zero frames then AudiOConverterFillComplexBuffer is out
|
||||
// of data to provide. We drained its internal buffer completely on the
|
||||
// last pass.
|
||||
if (numFrames == 0 && rv == kNeedMoreData) {
|
||||
LOGD("FillComplexBuffer out of data exactly\n");
|
||||
break;
|
||||
}
|
||||
|
||||
int64_t time = FramesToUsecs(mCurrentAudioFrame, mAudioSampleRate).value();
|
||||
int64_t duration = FramesToUsecs(numFrames, mAudioSampleRate).value();
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* -*- 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/. */
|
||||
|
@ -247,6 +248,48 @@ DirectShowReader::Finish(HRESULT aStatus)
|
|||
return false;
|
||||
}
|
||||
|
||||
class DirectShowCopy
|
||||
{
|
||||
public:
|
||||
DirectShowCopy(uint8_t *aSource, uint32_t aBytesPerSample,
|
||||
uint32_t aSamples, uint32_t aChannels)
|
||||
: mSource(aSource)
|
||||
, mBytesPerSample(aBytesPerSample)
|
||||
, mSamples(aSamples)
|
||||
, mChannels(aChannels)
|
||||
, mNextSample(0)
|
||||
{ }
|
||||
|
||||
uint32_t operator()(AudioDataValue *aBuffer, size_t aSamples)
|
||||
{
|
||||
size_t maxSamples = std::min(aSamples, mSamples - mNextSample);
|
||||
uint32_t frames = maxSamples / mChannels;
|
||||
size_t byteOffset = mNextSample * mBytesPerSample;
|
||||
if (mBytesPerSample == 1) {
|
||||
for (uint32_t i = 0; i < maxSamples; ++i) {
|
||||
uint8_t *sample = mSource + byteOffset;
|
||||
aBuffer[i] = UnsignedByteToAudioSample(*sample);
|
||||
byteOffset += mBytesPerSample;
|
||||
}
|
||||
} else if (mBytesPerSample == 2) {
|
||||
for (uint32_t i = 0; i < maxSamples; ++i) {
|
||||
int16_t *sample = reinterpret_cast<int16_t *>(mSource + byteOffset);
|
||||
aBuffer[i] = AudioSampleToFloat(*sample);
|
||||
byteOffset += mBytesPerSample;
|
||||
}
|
||||
}
|
||||
mNextSample = maxSamples;
|
||||
return frames;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t * const mSource;
|
||||
const uint32_t mBytesPerSample;
|
||||
const uint32_t mSamples;
|
||||
const uint32_t mChannels;
|
||||
uint32_t mNextSample;
|
||||
};
|
||||
|
||||
bool
|
||||
DirectShowReader::DecodeAudioData()
|
||||
{
|
||||
|
@ -281,26 +324,15 @@ DirectShowReader::DecodeAudioData()
|
|||
hr = sample->GetPointer(&data);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), Finish(hr));
|
||||
|
||||
nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[numSamples]);
|
||||
AudioDataValue* dst = buffer.get();
|
||||
if (mBytesPerSample == 1) {
|
||||
uint8_t* src = reinterpret_cast<uint8_t*>(data);
|
||||
for (int32_t i = 0; i < numSamples; ++i) {
|
||||
dst[i] = UnsignedByteToAudioSample(src[i]);
|
||||
}
|
||||
} else if (mBytesPerSample == 2) {
|
||||
int16_t* src = reinterpret_cast<int16_t*>(data);
|
||||
for (int32_t i = 0; i < numSamples; ++i) {
|
||||
dst[i] = AudioSampleToFloat(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(),
|
||||
RefTimeToUsecs(start),
|
||||
RefTimeToUsecs(end - start),
|
||||
numFrames,
|
||||
buffer.forget(),
|
||||
mNumChannels));
|
||||
mAudioCompactor.Push(mDecoder->GetResource()->Tell(),
|
||||
RefTimeToUsecs(start),
|
||||
mInfo.mAudio.mRate,
|
||||
numFrames,
|
||||
mNumChannels,
|
||||
DirectShowCopy(reinterpret_cast<uint8_t *>(data),
|
||||
mBytesPerSample,
|
||||
numSamples,
|
||||
mNumChannels));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -532,20 +532,20 @@ bool GStreamerReader::DecodeAudioData()
|
|||
timestamp = gst_segment_to_stream_time(&mAudioSegment,
|
||||
GST_FORMAT_TIME, timestamp);
|
||||
timestamp = GST_TIME_AS_USECONDS(timestamp);
|
||||
int64_t duration = 0;
|
||||
if (GST_CLOCK_TIME_IS_VALID(GST_BUFFER_DURATION(buffer)))
|
||||
duration = GST_TIME_AS_USECONDS(GST_BUFFER_DURATION(buffer));
|
||||
|
||||
int64_t offset = GST_BUFFER_OFFSET(buffer);
|
||||
unsigned int size = GST_BUFFER_SIZE(buffer);
|
||||
int32_t frames = (size / sizeof(AudioDataValue)) / mInfo.mAudio.mChannels;
|
||||
ssize_t outSize = static_cast<size_t>(size / sizeof(AudioDataValue));
|
||||
nsAutoArrayPtr<AudioDataValue> data(new AudioDataValue[outSize]);
|
||||
memcpy(data, GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE(buffer));
|
||||
AudioData* audio = new AudioData(offset, timestamp, duration,
|
||||
frames, data.forget(), mInfo.mAudio.mChannels);
|
||||
|
||||
mAudioQueue.Push(audio);
|
||||
typedef AudioCompactor::NativeCopy GstCopy;
|
||||
mAudioCompactor.Push(offset,
|
||||
timestamp,
|
||||
mInfo.mAudio.mRate,
|
||||
frames,
|
||||
mInfo.mAudio.mChannels,
|
||||
GstCopy(GST_BUFFER_DATA(buffer),
|
||||
size,
|
||||
mInfo.mAudio.mChannels));
|
||||
gst_buffer_unref(buffer);
|
||||
|
||||
return true;
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/* -*- 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 "AudioCompactor.h"
|
||||
#include "MediaDecoderReader.h"
|
||||
|
||||
using mozilla::AudioCompactor;
|
||||
using mozilla::AudioData;
|
||||
using mozilla::AudioDataValue;
|
||||
using mozilla::MediaDecoderReader;
|
||||
using mozilla::MediaQueue;
|
||||
|
||||
class TestCopy
|
||||
{
|
||||
public:
|
||||
TestCopy(uint32_t aFrames, uint32_t aChannels,
|
||||
uint32_t &aCallCount, uint32_t &aFrameCount)
|
||||
: mFrames(aFrames)
|
||||
, mChannels(aChannels)
|
||||
, mCallCount(aCallCount)
|
||||
, mFrameCount(aFrameCount)
|
||||
{ }
|
||||
|
||||
uint32_t operator()(AudioDataValue *aBuffer, uint32_t aSamples)
|
||||
{
|
||||
mCallCount += 1;
|
||||
uint32_t frames = std::min(mFrames - mFrameCount, aSamples / mChannels);
|
||||
mFrameCount += frames;
|
||||
return frames;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint32_t mFrames;
|
||||
const uint32_t mChannels;
|
||||
uint32_t &mCallCount;
|
||||
uint32_t &mFrameCount;
|
||||
};
|
||||
|
||||
static void TestAudioCompactor(size_t aBytes)
|
||||
{
|
||||
MediaQueue<AudioData> queue;
|
||||
AudioCompactor compactor(queue);
|
||||
|
||||
uint64_t offset = 0;
|
||||
uint64_t time = 0;
|
||||
uint32_t sampleRate = 44000;
|
||||
uint32_t channels = 2;
|
||||
uint32_t frames = aBytes / (channels * sizeof(AudioDataValue));
|
||||
size_t maxSlop = aBytes / AudioCompactor::MAX_SLOP_DIVISOR;
|
||||
|
||||
uint32_t callCount = 0;
|
||||
uint32_t frameCount = 0;
|
||||
|
||||
compactor.Push(offset, time, sampleRate, frames, channels,
|
||||
TestCopy(frames, channels, callCount, frameCount));
|
||||
|
||||
EXPECT_GT(callCount, 0U) << "copy functor never called";
|
||||
EXPECT_EQ(frames, frameCount) << "incorrect number of frames copied";
|
||||
|
||||
MediaDecoderReader::AudioQueueMemoryFunctor memoryFunc;
|
||||
queue.LockedForEach(memoryFunc);
|
||||
size_t allocSize = memoryFunc.mSize - (callCount * sizeof(AudioData));
|
||||
size_t slop = allocSize - aBytes;
|
||||
EXPECT_LE(slop, maxSlop) << "allowed too much allocation slop";
|
||||
}
|
||||
|
||||
TEST(Media, AudioCompactor_4000)
|
||||
{
|
||||
TestAudioCompactor(4000);
|
||||
}
|
||||
|
||||
TEST(Media, AudioCompactor_4096)
|
||||
{
|
||||
TestAudioCompactor(4096);
|
||||
}
|
||||
|
||||
TEST(Media, AudioCompactor_5000)
|
||||
{
|
||||
TestAudioCompactor(5000);
|
||||
}
|
||||
|
||||
TEST(Media, AudioCompactor_5256)
|
||||
{
|
||||
TestAudioCompactor(5256);
|
||||
}
|
||||
|
||||
TEST(Media, AudioCompactor_NativeCopy)
|
||||
{
|
||||
const uint32_t channels = 2;
|
||||
const size_t srcBytes = 32;
|
||||
const uint32_t srcSamples = srcBytes / sizeof(AudioDataValue);
|
||||
const uint32_t srcFrames = srcSamples / channels;
|
||||
uint8_t src[srcBytes];
|
||||
|
||||
for (uint32_t i = 0; i < srcBytes; ++i) {
|
||||
src[i] = i;
|
||||
}
|
||||
|
||||
AudioCompactor::NativeCopy copy(src, srcBytes, channels);
|
||||
|
||||
const uint32_t dstSamples = srcSamples * 2;
|
||||
AudioDataValue dst[dstSamples];
|
||||
|
||||
const AudioDataValue notCopied = 0xffff;
|
||||
for (uint32_t i = 0; i < dstSamples; ++i) {
|
||||
dst[i] = notCopied;
|
||||
}
|
||||
|
||||
const uint32_t copyCount = 8;
|
||||
uint32_t copiedFrames = 0;
|
||||
uint32_t nextSample = 0;
|
||||
for (uint32_t i = 0; i < copyCount; ++i) {
|
||||
uint32_t copySamples = dstSamples / copyCount;
|
||||
copiedFrames += copy(dst + nextSample, copySamples);
|
||||
nextSample += copySamples;
|
||||
}
|
||||
|
||||
EXPECT_EQ(srcFrames, copiedFrames) << "copy exact number of source frames";
|
||||
|
||||
// Verify that the only the correct bytes were copied.
|
||||
for (uint32_t i = 0; i < dstSamples; ++i) {
|
||||
if (i < srcSamples) {
|
||||
EXPECT_NE(notCopied, dst[i]) << "should have copied over these bytes";
|
||||
} else {
|
||||
EXPECT_EQ(notCopied, dst[i]) << "should not have copied over these bytes";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
LIBRARY_NAME = 'media_gtest'
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'TestAudioCompactor.cpp',
|
||||
'TestTrackEncoder.cpp',
|
||||
]
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ EXPORTS += [
|
|||
'AbstractMediaDecoder.h',
|
||||
'AudioAvailableEventManager.h',
|
||||
'AudioChannelFormat.h',
|
||||
'AudioCompactor.h',
|
||||
'AudioEventTimeline.h',
|
||||
'AudioNodeEngine.h',
|
||||
'AudioNodeExternalInputStream.h',
|
||||
|
@ -115,6 +116,7 @@ EXPORTS.mozilla.dom += [
|
|||
UNIFIED_SOURCES += [
|
||||
'AudioAvailableEventManager.cpp',
|
||||
'AudioChannelFormat.cpp',
|
||||
'AudioCompactor.cpp',
|
||||
'AudioNodeEngine.cpp',
|
||||
'AudioNodeExternalInputStream.cpp',
|
||||
'AudioNodeStream.cpp',
|
||||
|
|
|
@ -312,33 +312,29 @@ bool MediaOmxReader::DecodeAudioData()
|
|||
int64_t pos = mDecoder->GetResource()->Tell();
|
||||
|
||||
// Read next frame
|
||||
MPAPI::AudioFrame frame;
|
||||
if (!mOmxDecoder->ReadAudio(&frame, mAudioSeekTimeUs)) {
|
||||
MPAPI::AudioFrame source;
|
||||
if (!mOmxDecoder->ReadAudio(&source, mAudioSeekTimeUs)) {
|
||||
return false;
|
||||
}
|
||||
mAudioSeekTimeUs = -1;
|
||||
|
||||
// Ignore empty buffer which stagefright media read will sporadically return
|
||||
if (frame.mSize == 0) {
|
||||
if (source.mSize == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frame.mSize/2] );
|
||||
memcpy(buffer.get(), frame.mData, frame.mSize);
|
||||
uint32_t frames = source.mSize / (source.mAudioChannels *
|
||||
sizeof(AudioDataValue));
|
||||
|
||||
uint32_t frames = frame.mSize / (2 * frame.mAudioChannels);
|
||||
CheckedInt64 duration = FramesToUsecs(frames, frame.mAudioSampleRate);
|
||||
if (!duration.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mAudioQueue.Push(new AudioData(pos,
|
||||
frame.mTimeUs,
|
||||
duration.value(),
|
||||
frames,
|
||||
buffer.forget(),
|
||||
frame.mAudioChannels));
|
||||
return true;
|
||||
typedef AudioCompactor::NativeCopy OmxCopy;
|
||||
return mAudioCompactor.Push(pos,
|
||||
source.mTimeUs,
|
||||
source.mAudioSampleRate,
|
||||
frames,
|
||||
source.mAudioChannels,
|
||||
OmxCopy(static_cast<uint8_t *>(source.mData),
|
||||
source.mSize,
|
||||
source.mAudioChannels));
|
||||
}
|
||||
|
||||
nsresult MediaOmxReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
|
||||
|
|
|
@ -4776,8 +4776,10 @@ nsDocShell::LoadErrorPage(nsIURI *aURI, const char16_t *aURL,
|
|||
}
|
||||
errorPageUrl.AppendLiteral("&c=");
|
||||
errorPageUrl.AppendASCII(escapedCharset.get());
|
||||
errorPageUrl.AppendLiteral("&d=");
|
||||
errorPageUrl.AppendASCII(escapedDescription.get());
|
||||
|
||||
nsAutoCString frameType(FrameTypeToString(mFrameType));
|
||||
errorPageUrl.AppendLiteral("&f=");
|
||||
errorPageUrl.AppendASCII(frameType.get());
|
||||
|
||||
// Append the manifest URL if the error comes from an app.
|
||||
nsString manifestURL;
|
||||
|
@ -4791,9 +4793,10 @@ nsDocShell::LoadErrorPage(nsIURI *aURI, const char16_t *aURL,
|
|||
errorPageUrl.AppendASCII(manifestParam.get());
|
||||
}
|
||||
|
||||
nsAutoCString frameType(FrameTypeToString(mFrameType));
|
||||
errorPageUrl.AppendLiteral("&f=");
|
||||
errorPageUrl.AppendASCII(frameType.get());
|
||||
// netError.xhtml's getDescription only handles the "d" parameter at the
|
||||
// end of the URL, so append it last.
|
||||
errorPageUrl.AppendLiteral("&d=");
|
||||
errorPageUrl.AppendASCII(escapedDescription.get());
|
||||
|
||||
nsCOMPtr<nsIURI> errorPageURI;
|
||||
rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
||||
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.gfx.GeckoLayerClient;
|
||||
import org.mozilla.gecko.gfx.LayerView;
|
||||
|
@ -771,51 +772,67 @@ public class GeckoAppShell
|
|||
createShortcut(aTitle, aURI, aURI, aIconData, aType);
|
||||
}
|
||||
|
||||
// for non-webapps
|
||||
// For non-webapps.
|
||||
public static void createShortcut(String aTitle, String aURI, Bitmap aBitmap, String aType) {
|
||||
createShortcut(aTitle, aURI, aURI, aBitmap, aType);
|
||||
}
|
||||
|
||||
// internal, for webapps
|
||||
static void createShortcut(String aTitle, String aURI, String aUniqueURI, String aIconData, String aType) {
|
||||
createShortcut(aTitle, aURI, aUniqueURI, BitmapUtils.getBitmapFromDataURI(aIconData), aType);
|
||||
}
|
||||
|
||||
public static void createShortcut(final String aTitle, final String aURI, final String aUniqueURI,
|
||||
final Bitmap aIcon, final String aType)
|
||||
{
|
||||
// Internal, for webapps.
|
||||
static void createShortcut(final String aTitle, final String aURI, final String aUniqueURI, final String aIconData, final String aType) {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// the intent to be launched by the shortcut
|
||||
Intent shortcutIntent;
|
||||
if (aType.equalsIgnoreCase(SHORTCUT_TYPE_WEBAPP)) {
|
||||
shortcutIntent = getWebAppIntent(aURI, aUniqueURI, aTitle, aIcon);
|
||||
} else {
|
||||
shortcutIntent = new Intent();
|
||||
shortcutIntent.setAction(GeckoApp.ACTION_BOOKMARK);
|
||||
shortcutIntent.setData(Uri.parse(aURI));
|
||||
shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
|
||||
AppConstants.BROWSER_INTENT_CLASS);
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
|
||||
if (aTitle != null)
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
|
||||
else
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI);
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, getLauncherIcon(aIcon, aType));
|
||||
|
||||
// Do not allow duplicate items
|
||||
intent.putExtra("duplicate", false);
|
||||
|
||||
intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
|
||||
getContext().sendBroadcast(intent);
|
||||
// TODO: use the cache. Bug 961600.
|
||||
Bitmap icon = FaviconDecoder.getMostSuitableBitmapFromDataURI(aIconData, getPreferredIconSize());
|
||||
GeckoAppShell.doCreateShortcut(aTitle, aURI, aURI, icon, aType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void createShortcut(final String aTitle, final String aURI, final String aUniqueURI,
|
||||
final Bitmap aIcon, final String aType) {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GeckoAppShell.doCreateShortcut(aTitle, aURI, aUniqueURI, aIcon, aType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method only on the background thread.
|
||||
*/
|
||||
private static void doCreateShortcut(final String aTitle, final String aURI, final String aUniqueURI,
|
||||
final Bitmap aIcon, final String aType) {
|
||||
// The intent to be launched by the shortcut.
|
||||
Intent shortcutIntent;
|
||||
if (aType.equalsIgnoreCase(SHORTCUT_TYPE_WEBAPP)) {
|
||||
shortcutIntent = getWebAppIntent(aURI, aUniqueURI, aTitle, aIcon);
|
||||
} else {
|
||||
shortcutIntent = new Intent();
|
||||
shortcutIntent.setAction(GeckoApp.ACTION_BOOKMARK);
|
||||
shortcutIntent.setData(Uri.parse(aURI));
|
||||
shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
|
||||
AppConstants.BROWSER_INTENT_CLASS);
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, getLauncherIcon(aIcon, aType));
|
||||
|
||||
if (aTitle != null) {
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
|
||||
} else {
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI);
|
||||
}
|
||||
|
||||
// Do not allow duplicate items.
|
||||
intent.putExtra("duplicate", false);
|
||||
|
||||
intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
|
||||
getContext().sendBroadcast(intent);
|
||||
}
|
||||
|
||||
public static void removeShortcut(final String aTitle, final String aURI, final String aType) {
|
||||
removeShortcut(aTitle, aURI, null, aType);
|
||||
}
|
||||
|
|
|
@ -538,7 +538,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
this.fieldID = integerQuery("named_fields", "field_id",
|
||||
"measurement_name = ? AND measurement_version = ? AND field_name = ?",
|
||||
new String[] {measurementName, measurementVersion, fieldName},
|
||||
-1);
|
||||
UNKNOWN_TYPE_OR_FIELD_ID);
|
||||
if (this.fieldID == UNKNOWN_TYPE_OR_FIELD_ID) {
|
||||
throw new IllegalStateException("No field with name " + fieldName +
|
||||
" (" + measurementName + ", " + measurementVersion + ")");
|
||||
|
@ -552,7 +552,9 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
// store differently stable kinds of data, hence type difference.
|
||||
// Note that we don't pre-populate the environment cache. We'll typically only
|
||||
// handle one per session.
|
||||
private final ConcurrentHashMap<String, Integer> envs = new ConcurrentHashMap<String, Integer>();
|
||||
//
|
||||
// protected for testing purposes only.
|
||||
protected final ConcurrentHashMap<String, Integer> envs = new ConcurrentHashMap<String, Integer>();
|
||||
|
||||
/**
|
||||
* An {@link Environment} that knows how to persist to and from our database.
|
||||
|
@ -855,6 +857,12 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
private HashMap<String, Field> fields = new HashMap<String, Field>();
|
||||
private boolean fieldsCacheUpdated = false;
|
||||
|
||||
private void invalidateFieldsCache() {
|
||||
synchronized (this.fields) {
|
||||
fieldsCacheUpdated = false;
|
||||
}
|
||||
}
|
||||
|
||||
private String getFieldKey(String mName, int mVersion, String fieldName) {
|
||||
return mVersion + "." + mName + "/" + fieldName;
|
||||
}
|
||||
|
@ -1014,9 +1022,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
notifyMeasurementVersionUpdated(measurement, version);
|
||||
|
||||
// Let's be easy for now.
|
||||
synchronized (fields) {
|
||||
fieldsCacheUpdated = false;
|
||||
}
|
||||
invalidateFieldsCache();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1152,10 +1158,16 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
|
||||
final SQLiteDatabase db = this.helper.getWritableDatabase();
|
||||
putValue(v, value);
|
||||
try {
|
||||
db.insertOrThrow(table, null, v);
|
||||
} catch (SQLiteConstraintException e) {
|
||||
throw new IllegalStateException("Event did not reference existing an environment or field.", e);
|
||||
|
||||
// Using SQLiteDatabase.insertOrThrow throws SQLiteConstraintException we cannot catch for
|
||||
// unknown reasons (bug 961526 comment 13). We believe these are thrown because we attempt to
|
||||
// record events using environment IDs removed from the database by the prune service. We
|
||||
// invalidate the currentEnvironment ID after pruning, preventing further propagation,
|
||||
// however, any event recording waiting for the prune service to complete on the background
|
||||
// thread may carry an invalid ID: we expect an insertion failure and drop these events here.
|
||||
final long res = db.insert(table, null, v);
|
||||
if (res == -1) {
|
||||
Logger.error(LOG_TAG, "Unable to record daily discrete event. Ignoring.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1516,6 +1528,11 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
try {
|
||||
// Cascade will clear the rest.
|
||||
db.delete("measurements", null, null);
|
||||
|
||||
// Clear measurements and fields cache, because some of their IDs are now invalid.
|
||||
invalidateFieldsCache(); // Let it repopulate on its own.
|
||||
populateMeasurementVersionsCache(db); // Performed at Storage init so repopulate now.
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
@ -1524,7 +1541,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
|
||||
/**
|
||||
* Prunes the given number of least-recently used environments. Note that orphaned environments
|
||||
* are not removed.
|
||||
* are not removed and the environment cache is cleared.
|
||||
*/
|
||||
public void pruneEnvironments(final int numToPrune) {
|
||||
final SQLiteDatabase db = this.helper.getWritableDatabase();
|
||||
|
@ -1538,6 +1555,9 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
" LIMIT " + numToPrune + ")",
|
||||
null);
|
||||
db.setTransactionSuccessful();
|
||||
|
||||
// Clear environment cache, because some of their IDs are now invalid.
|
||||
this.envs.clear();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
|
|
@ -43,6 +43,11 @@ public class PrunePolicyDatabaseStorage implements PrunePolicyStorage {
|
|||
|
||||
public void pruneEnvironments(final int count) {
|
||||
getStorage().pruneEnvironments(count);
|
||||
|
||||
// Re-populate the DB and environment cache with the current environment in the unlikely event
|
||||
// that it was deleted.
|
||||
this.currentEnvironmentID = -1;
|
||||
getCurrentEnvironmentID();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.mozilla.gecko.favicons;
|
|||
|
||||
import org.mozilla.gecko.AboutPages;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
|
@ -465,15 +466,19 @@ public class Favicons {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sidestep the cache and get, from either the database or the internet, the largest available
|
||||
* Favicon for the given page URL. Useful for creating homescreen shortcuts without being limited
|
||||
* by possibly low-resolution values in the cache.
|
||||
* Deduces the favicon URL from the history database and, ultimately, guesses.
|
||||
* Sidestep the cache and get, from either the database or the internet, a favicon
|
||||
* suitable for use as an app icon for the provided URL.
|
||||
*
|
||||
* @param url Page URL to get a large favicon image fro.
|
||||
* @param onFaviconLoadedListener Listener to call back with the result.
|
||||
* Useful for creating homescreen shortcuts without being limited
|
||||
* by possibly low-resolution values in the cache.
|
||||
*
|
||||
* Deduces the favicon URL from the browser database, guessing if necessary.
|
||||
*
|
||||
* @param url page URL to get a large favicon image for.
|
||||
* @param onFaviconLoadedListener listener to call back with the result.
|
||||
*/
|
||||
public static void getLargestFaviconForPage(String url, OnFaviconLoadedListener onFaviconLoadedListener) {
|
||||
loadUncachedFavicon(url, null, 0, -1, onFaviconLoadedListener);
|
||||
public static void getPreferredSizeFaviconForPage(String url, OnFaviconLoadedListener onFaviconLoadedListener) {
|
||||
int preferredSize = GeckoAppShell.getPreferredIconSize();
|
||||
loadUncachedFavicon(url, null, 0, preferredSize, onFaviconLoadedListener);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -474,7 +474,6 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
|
||||
private void processResult(Bitmap image) {
|
||||
Favicons.removeLoadTask(mId);
|
||||
|
||||
Bitmap scaled = image;
|
||||
|
||||
// Notify listeners, scaling if required.
|
||||
|
|
|
@ -332,7 +332,8 @@ public class FaviconCache {
|
|||
|
||||
FaviconCacheElement cacheElement;
|
||||
|
||||
int cacheElementIndex = container.getNextHighestIndex(targetSize);
|
||||
// If targetSize is -1, it means we want the largest possible icon.
|
||||
int cacheElementIndex = (targetSize == -1) ? -1 : container.getNextHighestIndex(targetSize);
|
||||
|
||||
// cacheElementIndex now holds either the index of the next least largest bitmap from
|
||||
// targetSize, or -1 if targetSize > all bitmaps.
|
||||
|
@ -362,12 +363,16 @@ public class FaviconCache {
|
|||
// If there is no such primary, we'll upscale the next least smaller one instead.
|
||||
cacheElement = container.getNextPrimary(cacheElementIndex);
|
||||
|
||||
|
||||
if (cacheElement == null) {
|
||||
// The primary has been invalidated! Fail! Need to get it back from the database.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (targetSize == -1) {
|
||||
// We got the biggest primary, so that's what we'll return.
|
||||
return cacheElement.mFaviconPayload;
|
||||
}
|
||||
|
||||
// Having got this far, we'll be needing to write the new secondary to the cache, which
|
||||
// involves us falling through to the next try block. This flag lets us do this (Other
|
||||
// paths prior to this end in returns.)
|
||||
|
|
|
@ -145,6 +145,50 @@ public class FaviconDecoder {
|
|||
return decodeFavicon(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the smallest bitmap in the icon represented by the provided
|
||||
* data: URI that's larger than the desired width, or the largest if
|
||||
* there is no larger icon.
|
||||
*
|
||||
* Returns null if no bitmap could be extracted.
|
||||
*
|
||||
* Bug 961600: we shouldn't be doing all of this work. The favicon cache
|
||||
* should be used, and will give us the right size icon.
|
||||
*/
|
||||
public static Bitmap getMostSuitableBitmapFromDataURI(String iconURI, int desiredWidth) {
|
||||
LoadFaviconResult result = FaviconDecoder.decodeDataURI(iconURI);
|
||||
if (result == null) {
|
||||
// Nothing we can do.
|
||||
Log.w(LOG_TAG, "Unable to decode icon URI.");
|
||||
return null;
|
||||
}
|
||||
|
||||
final Iterator<Bitmap> bitmaps = result.getBitmaps();
|
||||
if (!bitmaps.hasNext()) {
|
||||
Log.w(LOG_TAG, "No bitmaps in decoded icon.");
|
||||
return null;
|
||||
}
|
||||
|
||||
Bitmap bitmap = bitmaps.next();
|
||||
if (!bitmaps.hasNext()) {
|
||||
// We're done! There was only one, so this is as big as it gets.
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
// Find a bitmap of the most suitable size.
|
||||
int currentWidth = bitmap.getWidth();
|
||||
while ((currentWidth < desiredWidth) &&
|
||||
bitmaps.hasNext()) {
|
||||
final Bitmap b = bitmaps.next();
|
||||
if (b.getWidth() > currentWidth) {
|
||||
currentWidth = b.getWidth();
|
||||
bitmap = b;
|
||||
}
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator to hold a single bitmap.
|
||||
*/
|
||||
|
|
|
@ -37,9 +37,13 @@ import java.io.File;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
|
@ -758,11 +762,11 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
public static final String MEASUREMENT_NAME_SEARCH_COUNTS = "org.mozilla.searches.counts";
|
||||
public static final int MEASUREMENT_VERSION_SEARCH_COUNTS = 5;
|
||||
|
||||
public static final String[] SEARCH_LOCATIONS = {
|
||||
public static final Set<String> SEARCH_LOCATIONS = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(new String[] {
|
||||
"barkeyword",
|
||||
"barsuggest",
|
||||
"bartext",
|
||||
};
|
||||
})));
|
||||
|
||||
private void initializeSearchProvider() {
|
||||
this.storage.ensureMeasurementInitialized(
|
||||
|
@ -771,7 +775,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
new MeasurementFields() {
|
||||
@Override
|
||||
public Iterable<FieldSpec> getFields() {
|
||||
ArrayList<FieldSpec> out = new ArrayList<FieldSpec>(SEARCH_LOCATIONS.length);
|
||||
ArrayList<FieldSpec> out = new ArrayList<FieldSpec>(SEARCH_LOCATIONS.size());
|
||||
for (String location : SEARCH_LOCATIONS) {
|
||||
// We're not using a counter, because the set of engine
|
||||
// identifiers is potentially unbounded, and thus our
|
||||
|
@ -803,19 +807,31 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
return;
|
||||
}
|
||||
|
||||
final int env = this.env;
|
||||
|
||||
if (env == -1) {
|
||||
Log.d(LOG_TAG, "No environment: not recording search.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (location == null) {
|
||||
throw new IllegalArgumentException("location must be provided for search.");
|
||||
}
|
||||
|
||||
// Ensure that we don't throw when trying to look up the field for an
|
||||
// unknown location. If you add a search location, you must extend the
|
||||
// list of search locations *and update the measurement version*.
|
||||
if (!SEARCH_LOCATIONS.contains(location)) {
|
||||
throw new IllegalArgumentException("Unexpected location: " + location);
|
||||
}
|
||||
|
||||
final int day = storage.getDay();
|
||||
final int env = this.env;
|
||||
final String key = (engineID == null) ? "other" : engineID;
|
||||
final BrowserHealthRecorder self = this;
|
||||
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HealthReportDatabaseStorage storage = self.storage;
|
||||
final HealthReportDatabaseStorage storage = BrowserHealthRecorder.this.storage;
|
||||
if (storage == null) {
|
||||
Log.d(LOG_TAG, "No storage: not recording search. Shutting down?");
|
||||
return;
|
||||
|
|
|
@ -137,8 +137,8 @@ abstract class HomeFragment extends Fragment {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Fetch the largest cacheable icon size.
|
||||
Favicons.getLargestFaviconForPage(info.url, new GeckoAppShell.CreateShortcutFaviconLoadedListener(info.url, info.getDisplayTitle()));
|
||||
// Fetch an icon big enough for use as a home screen icon.
|
||||
Favicons.getPreferredSizeFaviconForPage(info.url, new GeckoAppShell.CreateShortcutFaviconLoadedListener(info.url, info.getDisplayTitle()));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -381,8 +381,8 @@ public class TopSitesPanel extends HomeFragment {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Fetch the largest cacheable icon size.
|
||||
Favicons.getLargestFaviconForPage(info.url, new GeckoAppShell.CreateShortcutFaviconLoadedListener(info.url, info.getDisplayTitle()));
|
||||
// Fetch an icon big enough for use as a home screen icon.
|
||||
Favicons.getPreferredSizeFaviconForPage(info.url, new GeckoAppShell.CreateShortcutFaviconLoadedListener(info.url, info.getDisplayTitle()));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -135,31 +135,6 @@ public class SearchEnginePreference extends CustomListPreference {
|
|||
final String iconURI = geckoEngineJSON.getString("iconURI");
|
||||
// Keep a reference to the bitmap - we'll need it later in onBindView.
|
||||
try {
|
||||
|
||||
// Bug 961600: we shouldn't be doing all of this work. The favicon cache
|
||||
// should be used, and will give us the right size icon.
|
||||
|
||||
LoadFaviconResult result = FaviconDecoder.decodeDataURI(iconURI);
|
||||
if (result == null) {
|
||||
// Nothing we can do.
|
||||
Log.w(LOGTAG, "Unable to decode icon URI.");
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator<Bitmap> bitmaps = result.getBitmaps();
|
||||
if (!bitmaps.hasNext()) {
|
||||
Log.w(LOGTAG, "No bitmaps in decoded icon.");
|
||||
return;
|
||||
}
|
||||
|
||||
mIconBitmap = bitmaps.next();
|
||||
|
||||
if (!bitmaps.hasNext()) {
|
||||
// We're done! There was only one, so this is as big as it gets.
|
||||
return;
|
||||
}
|
||||
|
||||
// Find a bitmap of a more suitable size.
|
||||
final int desiredWidth;
|
||||
if (mFaviconView != null) {
|
||||
desiredWidth = mFaviconView.getWidth();
|
||||
|
@ -174,15 +149,8 @@ public class SearchEnginePreference extends CustomListPreference {
|
|||
}
|
||||
}
|
||||
|
||||
int currentWidth = mIconBitmap.getWidth();
|
||||
while ((currentWidth < desiredWidth) &&
|
||||
bitmaps.hasNext()) {
|
||||
Bitmap b = bitmaps.next();
|
||||
if (b.getWidth() > currentWidth) {
|
||||
currentWidth = b.getWidth();
|
||||
mIconBitmap = b;
|
||||
}
|
||||
}
|
||||
// TODO: use the cache. Bug 961600.
|
||||
mIconBitmap = FaviconDecoder.getMostSuitableBitmapFromDataURI(iconURI, desiredWidth);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(LOGTAG, "IllegalArgumentException creating Bitmap. Most likely a zero-length bitmap.", e);
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.mozilla.gecko.webapp;
|
|||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.util.ActivityResultHandler;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
|
@ -114,7 +115,9 @@ public class EventListener implements GeckoEventListener {
|
|||
int index = allocator.getIndexForApp(aOriginalOrigin);
|
||||
|
||||
assert aIconURL != null;
|
||||
Bitmap icon = BitmapUtils.getBitmapFromDataURI(aIconURL);
|
||||
|
||||
final int preferredSize = GeckoAppShell.getPreferredIconSize();
|
||||
Bitmap icon = FaviconDecoder.getMostSuitableBitmapFromDataURI(aIconURL, preferredSize);
|
||||
|
||||
assert aOrigin != null && index != -1;
|
||||
allocator.updateAppAllocation(aOrigin, index, icon);
|
||||
|
|
|
@ -19,7 +19,7 @@ interface nsIShellService : nsISupports
|
|||
*
|
||||
* @param aTitle the user-friendly name of the shortcut.
|
||||
* @param aURI the URI to open.
|
||||
* @param aIconData a base64 encoded representation of the shortcut's icon.
|
||||
* @param aIconData a base64-encoded data: URI representation of the shortcut's icon, as accepted by the favicon decoder.
|
||||
* @param aIntent how the URI should be opened. Examples: "default", "bookmark" and "webapp"
|
||||
*/
|
||||
void createShortcut(in AString aTitle, in AString aURI, in AString aIconData, in AString aIntent);
|
||||
|
|
|
@ -5,6 +5,7 @@ package org.mozilla.gecko.background.healthreport;
|
|||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.background.common.GlobalConstants;
|
||||
|
@ -42,6 +43,10 @@ public class MockHealthReportDatabaseStorage extends HealthReportDatabaseStorage
|
|||
return now - numDays * GlobalConstants.MILLISECONDS_PER_DAY;
|
||||
}
|
||||
|
||||
public ConcurrentHashMap<String, Integer> getEnvironmentCache() {
|
||||
return this.envs;
|
||||
}
|
||||
|
||||
public MockHealthReportDatabaseStorage(Context context, File fakeProfileDirectory) {
|
||||
super(context, fakeProfileDirectory);
|
||||
}
|
||||
|
|
|
@ -326,10 +326,6 @@ public class TestHealthReportDatabaseStorage extends FakeProfileTestCase {
|
|||
storage.incrementDailyCount(nonExistentEnvID, storage.getToday(), counterFieldID);
|
||||
fail("Should throw - event_integer(env) references environments(id), which is given as a non-existent value.");
|
||||
} catch (IllegalStateException e) { }
|
||||
try {
|
||||
storage.recordDailyDiscrete(nonExistentEnvID, storage.getToday(), discreteFieldID, "iu");
|
||||
fail("Should throw - event_textual(env) references environments(id), which is given as a non-existent value.");
|
||||
} catch (IllegalStateException e) { }
|
||||
try {
|
||||
storage.recordDailyLast(nonExistentEnvID, storage.getToday(), discreteFieldID, "iu");
|
||||
fail("Should throw - event_textual(env) references environments(id), which is given as a non-existent value.");
|
||||
|
@ -339,14 +335,30 @@ public class TestHealthReportDatabaseStorage extends FakeProfileTestCase {
|
|||
storage.incrementDailyCount(envID, storage.getToday(), nonExistentFieldID);
|
||||
fail("Should throw - event_integer(field) references fields(id), which is given as a non-existent value.");
|
||||
} catch (IllegalStateException e) { }
|
||||
try {
|
||||
storage.recordDailyDiscrete(envID, storage.getToday(), nonExistentFieldID, "iu");
|
||||
fail("Should throw - event_textual(field) references fields(id), which is given as a non-existent value.");
|
||||
} catch (IllegalStateException e) { }
|
||||
try {
|
||||
storage.recordDailyLast(envID, storage.getToday(), nonExistentFieldID, "iu");
|
||||
fail("Should throw - event_textual(field) references fields(id), which is given as a non-existent value.");
|
||||
} catch (IllegalStateException e) { }
|
||||
|
||||
// Test dropped events due to constraint violations that do not throw (see bug 961526).
|
||||
final String eventValue = "a value not in the database";
|
||||
assertFalse(isEventInDB(db, eventValue)); // Better safe than sorry.
|
||||
|
||||
storage.recordDailyDiscrete(nonExistentEnvID, storage.getToday(), discreteFieldID, eventValue);
|
||||
assertFalse(isEventInDB(db, eventValue));
|
||||
|
||||
storage.recordDailyDiscrete(envID, storage.getToday(), nonExistentFieldID, "iu");
|
||||
assertFalse(isEventInDB(db, eventValue));
|
||||
}
|
||||
|
||||
private static boolean isEventInDB(final SQLiteDatabase db, final String value) {
|
||||
final Cursor c = db.query("events_textual", new String[] {"value"}, "value = ?",
|
||||
new String[] {value}, null, null, null);
|
||||
try {
|
||||
return c.getCount() > 0;
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Largely taken from testDeleteEnvAndEventsBefore and testDeleteOrphanedAddons.
|
||||
|
@ -553,7 +565,10 @@ public class TestHealthReportDatabaseStorage extends FakeProfileTestCase {
|
|||
new PrepopulatedMockHealthReportDatabaseStorage(context, fakeProfileDirectory, 2);
|
||||
final SQLiteDatabase db = storage.getDB();
|
||||
assertEquals(5, DBHelpers.getRowCount(db, "environments"));
|
||||
assertEquals(5, storage.getEnvironmentCache().size());
|
||||
|
||||
storage.pruneEnvironments(1);
|
||||
assertEquals(0, storage.getEnvironmentCache().size());
|
||||
assertTrue(!getEnvAppVersions(db).contains("v3"));
|
||||
storage.pruneEnvironments(2);
|
||||
assertTrue(!getEnvAppVersions(db).contains("v2"));
|
||||
|
|
|
@ -45,7 +45,7 @@ Cu.import("resource://gre/modules/Promise.jsm");
|
|||
* @param host
|
||||
* The url of the host
|
||||
*/
|
||||
function HawkClient(host) {
|
||||
this.HawkClient = function(host) {
|
||||
this.host = host;
|
||||
|
||||
// Clock offset in milliseconds between our client's clock and the date
|
||||
|
@ -53,7 +53,7 @@ function HawkClient(host) {
|
|||
this._localtimeOffsetMsec = 0;
|
||||
}
|
||||
|
||||
HawkClient.prototype = {
|
||||
this.HawkClient.prototype = {
|
||||
|
||||
/*
|
||||
* Construct an error message for a response. Private.
|
||||
|
|
|
@ -381,6 +381,7 @@
|
|||
"dom/tests/mochitest/localstorage/test_localStorageReplace.html":"",
|
||||
"dom/tests/mochitest/sessionstorage/test_cookieSession.html":"",
|
||||
"dom/tests/mochitest/sessionstorage/test_sessionStorageBaseSessionOnly.html":"",
|
||||
"dom/tests/mochitest/sessionstorage/test_sessionStorageClone.html":"bug 968051",
|
||||
|
||||
"dom/tests/mochitest/pointerlock/test_pointerlock-api.html":"window.open focus issues (using fullscreen)",
|
||||
"dom/tests/mochitest/sessionstorage/test_sessionStorageBase.html":"",
|
||||
|
|
|
@ -358,6 +358,7 @@
|
|||
"dom/tests/mochitest/localstorage/test_localStorageQuotaSessionOnly2.html":"needs https support",
|
||||
"dom/tests/mochitest/sessionstorage/test_cookieSession.html":"4 failures",
|
||||
"dom/tests/mochitest/sessionstorage/test_sessionStorageBase.html":"no storage chrome event received",
|
||||
"dom/tests/mochitest/sessionstorage/test_sessionStorageBaseSessionOnly.html":"bug 967737",
|
||||
"dom/tests/mochitest/sessionstorage/test_sessionStorageHttpHttps.html":"needs https to work",
|
||||
|
||||
"dom/tests/mochitest/pointerlock/test_pointerlock-api.html":"window.open focus issues (using fullscreen)",
|
||||
|
|
|
@ -457,6 +457,14 @@ let Impl = {
|
|||
revision: HISTOGRAMS_FILE_VERSION,
|
||||
locale: getLocale()
|
||||
};
|
||||
|
||||
// In order to share profile data, the appName used for Metro Firefox is "Firefox",
|
||||
// (the same as desktop Firefox). We set it to "MetroFirefox" here in order to
|
||||
// differentiate telemetry pings sent by desktop vs. metro Firefox.
|
||||
if(Services.metro && Services.metro.immersive) {
|
||||
ret.appName = "MetroFirefox";
|
||||
}
|
||||
|
||||
if (this._previousBuildID) {
|
||||
ret.previousBuildID = this._previousBuildID;
|
||||
}
|
||||
|
|
|
@ -42,9 +42,7 @@ LightweightThemeConsumer.prototype = {
|
|||
_lastScreenWidth: null,
|
||||
_lastScreenHeight: null,
|
||||
_enabled: true,
|
||||
#ifdef XP_MACOSX
|
||||
_chromemarginDefault: undefined,
|
||||
#endif
|
||||
_active: false,
|
||||
|
||||
enable: function() {
|
||||
this._enabled = true;
|
||||
|
@ -100,8 +98,9 @@ LightweightThemeConsumer.prototype = {
|
|||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
var root = this._doc.documentElement;
|
||||
var active = !!aData.headerURL;
|
||||
let root = this._doc.documentElement;
|
||||
let active = !!aData.headerURL;
|
||||
let stateChanging = (active != this._active);
|
||||
|
||||
if (active) {
|
||||
root.style.color = aData.textcolor || "black";
|
||||
|
@ -117,6 +116,8 @@ LightweightThemeConsumer.prototype = {
|
|||
root.removeAttribute("lwtheme");
|
||||
}
|
||||
|
||||
this._active = active;
|
||||
|
||||
_setImage(root, active, aData.headerURL);
|
||||
if (this._footerId) {
|
||||
let footer = this._doc.getElementById(this._footerId);
|
||||
|
@ -129,20 +130,26 @@ LightweightThemeConsumer.prototype = {
|
|||
}
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
// Sample whether or not we draw in the titlebar by default the first time we update.
|
||||
// If the root has no chromemargin attribute, getAttribute will return null, and
|
||||
// we'll remove the attribute when the lw-theme is deactivated.
|
||||
if (this._chromemarginDefault === undefined)
|
||||
this._chromemarginDefault = root.getAttribute("chromemargin");
|
||||
// On OS X, we extend the lightweight theme into the titlebar, which means setting
|
||||
// the chromemargin attribute. Some XUL applications already draw in the titlebar,
|
||||
// so we need to save the chromemargin value before we overwrite it with the value
|
||||
// that lets us draw in the titlebar. We stash this value on the root attribute so
|
||||
// that XUL applications have the ability to invalidate the saved value.
|
||||
if (stateChanging) {
|
||||
if (!root.hasAttribute("chromemargin-nonlwtheme")) {
|
||||
root.setAttribute("chromemargin-nonlwtheme", root.getAttribute("chromemargin"));
|
||||
}
|
||||
|
||||
if (active) {
|
||||
root.setAttribute("chromemargin", "0,-1,-1,-1");
|
||||
}
|
||||
else {
|
||||
if (this._chromemarginDefault)
|
||||
root.setAttribute("chromemargin", this._chromemarginDefault);
|
||||
else
|
||||
root.removeAttribute("chromemargin");
|
||||
if (active) {
|
||||
root.setAttribute("chromemargin", "0,-1,-1,-1");
|
||||
} else {
|
||||
let defaultChromemargin = root.getAttribute("chromemargin-nonlwtheme");
|
||||
if (defaultChromemargin) {
|
||||
root.setAttribute("chromemargin", defaultChromemargin);
|
||||
} else {
|
||||
root.removeAttribute("chromemargin");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче