Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-06-08 12:02:40 +02:00
commit c5b4041c32
77 changed files with 2020 additions and 996 deletions

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ea27c4ed5b6083c9e21d233d4804372ac4d5d353"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="d172b02d2d43774b934a866912b9a170c7f495df"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ea27c4ed5b6083c9e21d233d4804372ac4d5d353"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="d172b02d2d43774b934a866912b9a170c7f495df"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

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

View File

@ -17,10 +17,10 @@
</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="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ea27c4ed5b6083c9e21d233d4804372ac4d5d353"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="d172b02d2d43774b934a866912b9a170c7f495df"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ea27c4ed5b6083c9e21d233d4804372ac4d5d353"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="d172b02d2d43774b934a866912b9a170c7f495df"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ea27c4ed5b6083c9e21d233d4804372ac4d5d353"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="d172b02d2d43774b934a866912b9a170c7f495df"/>
<!-- Stock Android things -->
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>

View File

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

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ea27c4ed5b6083c9e21d233d4804372ac4d5d353"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="d172b02d2d43774b934a866912b9a170c7f495df"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

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

View File

@ -17,10 +17,10 @@
</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="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ea27c4ed5b6083c9e21d233d4804372ac4d5d353"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="d172b02d2d43774b934a866912b9a170c7f495df"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1d62b32408567f9f7cf1c71c1e5a0c6593be757b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ea27c4ed5b6083c9e21d233d4804372ac4d5d353"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="d172b02d2d43774b934a866912b9a170c7f495df"/>
<!-- Stock Android things -->
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1432051555000">
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1433264296000">
<emItems>
<emItem blockID="i58" id="webmaster@buzzzzvideos.info">
<versionRange minVersion="0" maxVersion="*">
@ -282,6 +282,15 @@
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i914" id="{0153E448-190B-4987-BDE1-F256CADA672F}">
<versionRange minVersion="0" maxVersion="15.0.6" severity="1">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="39.0a1" maxVersion="*" />
</targetApplication>
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i678" id="{C4A4F5A0-4B89-4392-AFAC-D58010E349AF}">
<versionRange minVersion="0" maxVersion="*" severity="1">
@ -463,8 +472,13 @@
<prefs>
</prefs>
</emItem>
<emItem blockID="i106" os="WINNT" id="{97E22097-9A2F-45b1-8DAF-36AD648C7EF4}">
<emItem blockID="i916" os="WINNT" id="{97E22097-9A2F-45b1-8DAF-36AD648C7EF4}">
<versionRange minVersion="0" maxVersion="15.0.5" severity="1">
</versionRange>
<versionRange minVersion="0" maxVersion="15.0.4" severity="1">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="39.0a1" maxVersion="*" />
</targetApplication>
</versionRange>
<prefs>
</prefs>
@ -786,8 +800,13 @@
<prefs>
</prefs>
</emItem>
<emItem blockID="i111" os="WINNT" id="{C3949AC2-4B17-43ee-B4F1-D26B9D42404D}">
<emItem blockID="i918" os="WINNT" id="{C3949AC2-4B17-43ee-B4F1-D26B9D42404D}">
<versionRange minVersion="0" maxVersion="15.0.5" severity="1">
</versionRange>
<versionRange minVersion="0" maxVersion="15.0.5" severity="1">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="39.0a1" maxVersion="*" />
</targetApplication>
</versionRange>
<prefs>
</prefs>

View File

@ -28,6 +28,159 @@ const gXPInstallObserver = {
return null;
},
pendingInstalls: new WeakMap(),
showInstallConfirmation: function(browser, installInfo, height = undefined) {
// If the confirmation notification is already open cache the installInfo
// and the new confirmation will be shown later
if (PopupNotifications.getNotification("addon-install-confirmation", browser)) {
let pending = this.pendingInstalls.get(browser);
if (pending) {
pending.push(installInfo);
} else {
this.pendingInstalls.set(browser, [installInfo]);
}
return;
}
const anchorID = "addons-notification-icon";
// Make notifications persist a minimum of 30 seconds
var options = {
timeout: Date.now() + 30000
};
try {
options.displayOrigin = installInfo.originatingURI.host;
} catch (e) {
// originatingURI might be missing or 'host' might throw for non-nsStandardURL nsIURIs.
}
let cancelInstallation = () => {
if (installInfo) {
for (let install of installInfo.installs)
install.cancel();
}
this.acceptInstallation = null;
let tab = gBrowser.getTabForBrowser(browser);
if (tab)
tab.removeEventListener("TabClose", cancelInstallation);
window.removeEventListener("unload", cancelInstallation);
// Make sure the browser is still alive.
if (gBrowser.browsers.indexOf(browser) == -1)
return;
let pending = this.pendingInstalls.get(browser);
if (pending && pending.length)
this.showInstallConfirmation(browser, pending.shift());
};
let unsigned = installInfo.installs.filter(i => i.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING);
let someUnsigned = unsigned.length > 0 && unsigned.length < installInfo.installs.length;
options.eventCallback = (aEvent) => {
switch (aEvent) {
case "removed":
cancelInstallation();
break;
case "shown":
let addonList = document.getElementById("addon-install-confirmation-content");
while (addonList.firstChild)
addonList.firstChild.remove();
for (let install of installInfo.installs) {
let container = document.createElement("hbox");
let name = document.createElement("label");
name.setAttribute("value", install.addon.name);
name.setAttribute("class", "addon-install-confirmation-name");
container.appendChild(name);
if (someUnsigned && install.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
let unsigned = document.createElement("label");
unsigned.setAttribute("value", gNavigatorBundle.getString("addonInstall.unsigned"));
unsigned.setAttribute("class", "addon-install-confirmation-unsigned");
container.appendChild(unsigned);
}
addonList.appendChild(container);
}
this.acceptInstallation = () => {
for (let install of installInfo.installs)
install.install();
installInfo = null;
Services.telemetry
.getHistogramById("SECURITY_UI")
.add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH);
};
break;
}
};
options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") +
"find-and-install-add-ons";
let messageString;
let notification = document.getElementById("addon-install-confirmation-notification");
if (unsigned.length == installInfo.installs.length) {
// None of the add-ons are verified
messageString = gNavigatorBundle.getString("addonConfirmInstallUnsigned.message");
notification.setAttribute("warning", "true");
}
else if (unsigned.length == 0) {
// All add-ons are verified or don't need to be verified
messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
notification.removeAttribute("warning");
}
else {
// Some of the add-ons are unverified, the list of names will indicate
// which
messageString = gNavigatorBundle.getString("addonConfirmInstallSomeUnsigned.message");
notification.setAttribute("warning", "true");
}
let brandBundle = document.getElementById("bundle_brand");
let brandShortName = brandBundle.getString("brandShortName");
messageString = PluralForm.get(installInfo.installs.length, messageString);
messageString = messageString.replace("#1", brandShortName);
messageString = messageString.replace("#2", installInfo.installs.length);
let cancelButton = document.getElementById("addon-install-confirmation-cancel");
cancelButton.label = gNavigatorBundle.getString("addonInstall.cancelButton.label");
cancelButton.accessKey = gNavigatorBundle.getString("addonInstall.cancelButton.accesskey");
let acceptButton = document.getElementById("addon-install-confirmation-accept");
acceptButton.label = gNavigatorBundle.getString("addonInstall.acceptButton.label");
acceptButton.accessKey = gNavigatorBundle.getString("addonInstall.acceptButton.accesskey");
if (height) {
let notification = document.getElementById("addon-install-confirmation-notification");
notification.style.minHeight = height + "px";
}
let tab = gBrowser.getTabForBrowser(browser);
if (tab) {
gBrowser.selectedTab = tab;
tab.addEventListener("TabClose", cancelInstallation);
}
window.addEventListener("unload", cancelInstallation);
PopupNotifications.show(browser, "addon-install-confirmation", messageString,
anchorID, null, null, options);
Services.telemetry
.getHistogramById("SECURITY_UI")
.add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
},
observe: function (aSubject, aTopic, aData)
{
var brandBundle = document.getElementById("bundle_brand");
@ -54,22 +207,6 @@ const gXPInstallObserver = {
// originatingURI might be missing or 'host' might throw for non-nsStandardURL nsIURIs.
}
let cancelInstallation = () => {
if (installInfo) {
for (let install of installInfo.installs)
install.cancel();
}
if (aTopic == "addon-install-confirmation")
this.acceptInstallation = null;
let tab = gBrowser.getTabForBrowser(browser);
if (tab)
tab.removeEventListener("TabClose", cancelInstallation);
window.removeEventListener("unload", cancelInstallation);
};
switch (aTopic) {
case "addon-install-disabled": {
notificationID = "xpinstall-disabled";
@ -188,106 +325,16 @@ const gXPInstallObserver = {
this._removeProgressNotification(browser);
break; }
case "addon-install-confirmation": {
let unsigned = installInfo.installs.filter(i => i.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING);
let someUnsigned = unsigned.length > 0 && unsigned.length < installInfo.installs.length;
options.eventCallback = (aEvent) => {
switch (aEvent) {
case "removed":
cancelInstallation();
break;
case "shown":
let addonList = document.getElementById("addon-install-confirmation-content");
while (addonList.firstChild)
addonList.firstChild.remove();
for (let install of installInfo.installs) {
let container = document.createElement("hbox");
let name = document.createElement("label");
name.setAttribute("value", install.addon.name);
name.setAttribute("class", "addon-install-confirmation-name");
container.appendChild(name);
if (someUnsigned && install.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
let unsigned = document.createElement("label");
unsigned.setAttribute("value", gNavigatorBundle.getString("addonInstall.unsigned"));
unsigned.setAttribute("class", "addon-install-confirmation-unsigned");
container.appendChild(unsigned);
}
addonList.appendChild(container);
}
this.acceptInstallation = () => {
for (let install of installInfo.installs)
install.install();
installInfo = null;
Services.telemetry
.getHistogramById("SECURITY_UI")
.add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH);
};
break;
}
};
options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") +
"find-and-install-add-ons";
let notification = document.getElementById("addon-install-confirmation-notification");
if (unsigned.length == installInfo.installs.length) {
// None of the add-ons are verified
messageString = gNavigatorBundle.getString("addonConfirmInstallUnsigned.message");
notification.setAttribute("warning", "true");
}
else if (unsigned.length == 0) {
// All add-ons are verified or don't need to be verified
messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
notification.removeAttribute("warning");
}
else {
// Some of the add-ons are unverified, the list of names will indicate
// which
messageString = gNavigatorBundle.getString("addonConfirmInstallSomeUnsigned.message");
notification.setAttribute("warning", "true");
}
messageString = PluralForm.get(installInfo.installs.length, messageString);
messageString = messageString.replace("#1", brandShortName);
messageString = messageString.replace("#2", installInfo.installs.length);
let cancelButton = document.getElementById("addon-install-confirmation-cancel");
cancelButton.label = gNavigatorBundle.getString("addonInstall.cancelButton.label");
cancelButton.accessKey = gNavigatorBundle.getString("addonInstall.cancelButton.accesskey");
let acceptButton = document.getElementById("addon-install-confirmation-accept");
acceptButton.label = gNavigatorBundle.getString("addonInstall.acceptButton.label");
acceptButton.accessKey = gNavigatorBundle.getString("addonInstall.acceptButton.accesskey");
let showNotification = () => {
let tab = gBrowser.getTabForBrowser(browser);
if (tab) {
gBrowser.selectedTab = tab;
tab.addEventListener("TabClose", cancelInstallation);
}
window.addEventListener("unload", cancelInstallation);
let height = undefined;
if (PopupNotifications.isPanelOpen) {
let rect = document.getElementById("addon-progress-notification").getBoundingClientRect();
let notification = document.getElementById("addon-install-confirmation-notification");
notification.style.minHeight = rect.height + "px";
height = rect.height;
}
PopupNotifications.show(browser, notificationID, messageString, anchorID,
action, null, options);
this._removeProgressNotification(browser);
Services.telemetry
.getHistogramById("SECURITY_UI")
.add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
this.showInstallConfirmation(browser, installInfo, height);
};
let progressNotification = PopupNotifications.getNotification("addon-progress", browser);

View File

@ -61,8 +61,20 @@ var FullScreen = {
this._fullScrToggler.addEventListener("dragenter", this._expandCallback, false);
}
if (enterFS) {
gNavToolbox.setAttribute("inFullscreen", true);
document.documentElement.setAttribute("inFullscreen", true);
} else {
gNavToolbox.removeAttribute("inFullscreen");
document.documentElement.removeAttribute("inFullscreen");
}
// show/hide menubars, toolbars (except the full screen toolbar)
this.showXULChrome("toolbar", !enterFS);
// On OS X Lion, we don't want to hide toolbars when entering
// fullscreen, unless we're entering DOM fullscreen.
if (document.mozFullScreen || !this.useLionFullScreen) {
this.showXULChrome("toolbar", !enterFS);
}
if (enterFS) {
document.addEventListener("keypress", this._keyToggleCallback, false);
@ -557,14 +569,6 @@ var FullScreen = {
}
}
if (aShow) {
gNavToolbox.removeAttribute("inFullscreen");
document.documentElement.removeAttribute("inFullscreen");
} else {
gNavToolbox.setAttribute("inFullscreen", true);
document.documentElement.setAttribute("inFullscreen", true);
}
ToolbarIconColor.inferFromText();
// For Lion fullscreen, all fullscreen controls are hidden, don't

View File

@ -1181,7 +1181,7 @@ var gBrowserInit = {
break;
case "restoreAll":
for (let browserWin of browserWindows()) {
for (let tab of window.gBrowser.tabs) {
for (let tab of browserWin.gBrowser.tabs) {
SessionStore.reviveCrashedTab(tab);
}
}

View File

@ -34,11 +34,11 @@ function get_observer_topic(aNotificationId) {
return topic;
}
function wait_for_progress_notification(aCallback) {
wait_for_notification(PROGRESS_NOTIFICATION, aCallback, "popupshowing");
function wait_for_progress_notification(aCallback, aExpectedCount = 1) {
wait_for_notification(PROGRESS_NOTIFICATION, aCallback, aExpectedCount, "popupshowing");
}
function wait_for_notification(aId, aCallback, aEvent = "popupshown") {
function wait_for_notification(aId, aCallback, aExpectedCount = 1, aEvent = "popupshown") {
info("Waiting for " + aId + " notification");
let topic = get_observer_topic(aId);
@ -70,10 +70,11 @@ function wait_for_notification(aId, aCallback, aEvent = "popupshown") {
function verify() {
info("Saw a notification");
ok(PopupNotifications.isPanelOpen, "Panel should be open");
is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
is(PopupNotifications.panel.childNodes.length, aExpectedCount, "Should be the right number of notifications");
if (PopupNotifications.panel.childNodes.length) {
is(PopupNotifications.panel.childNodes[0].id,
aId + "-notification", "Should have seen the right notification");
let nodes = Array.from(PopupNotifications.panel.childNodes);
let notification = nodes.find(n => n.id == aId + "-notification");
ok(notification, "Should have seen the right notification");
}
aCallback(PopupNotifications.panel);
}
@ -135,6 +136,15 @@ function accept_install_dialog() {
}
}
function cancel_install_dialog() {
if (Preferences.get("xpinstall.customConfirmationUI", false)) {
document.getElementById("addon-install-confirmation-cancel").click();
} else {
let win = Services.wm.getMostRecentWindow("Addons:Install");
win.document.documentElement.cancelDialog();
}
}
function wait_for_single_notification(aCallback) {
function inner_waiter() {
info("Waiting for single notification");
@ -456,6 +466,84 @@ function test_multiple() {
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
},
function test_sequential() {
// This test is only relevant if using the new doorhanger UI
if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
runNextTest();
return;
}
// Wait for the progress notification
wait_for_progress_notification(function(aPanel) {
// Wait for the install confirmation dialog
wait_for_install_dialog(function() {
// Wait for the progress notification
// Should see the right add-on
let container = document.getElementById("addon-install-confirmation-content");
is(container.childNodes.length, 1, "Should be one item listed");
is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
wait_for_progress_notification(function(aPanel) {
// Should still have the right add-on in the confirmation notification
is(container.childNodes.length, 1, "Should be one item listed");
is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
// Wait for the install to complete, we won't see a new confirmation
// notification
Services.obs.addObserver(function observer() {
Services.obs.removeObserver(observer, "addon-install-confirmation");
// Make sure browser-addons.js executes first
executeSoon(function () {
// Should have dropped the progress notification
is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications");
is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification",
"Should only be showing one install confirmation");
// Should still have the right add-on in the confirmation notification
is(container.childNodes.length, 1, "Should be one item listed");
is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
cancel_install_dialog();
ok(PopupNotifications.isPanelOpen, "Panel should still be open");
is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications");
is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification",
"Should still have an install confirmation open");
// Should have the next add-on's confirmation dialog
is(container.childNodes.length, 1, "Should be one item listed");
is(container.childNodes[0].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on");
Services.perms.remove("example.com", "install");
wait_for_notification_close(() => {
gBrowser.removeTab(gBrowser.selectedTab);
runNextTest();
});
cancel_install_dialog();
});
}, "addon-install-confirmation", false);
}, 2);
var triggers = encodeURIComponent(JSON.stringify({
"Theme XPI": "theme.xpi"
}));
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
});
});
var pm = Services.perms;
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
var triggers = encodeURIComponent(JSON.stringify({
"Restartless XPI": "restartless.xpi"
}));
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
},
function test_someunverified() {
// This test is only relevant if using the new doorhanger UI and allowing
// unsigned add-ons

View File

@ -48,7 +48,6 @@ support-files =
[browser_bug818118.js]
[browser_bug820497.js]
[browser_clearplugindata.js]
skip-if = e10s # bug 1149253
[browser_CTP_context_menu.js]
skip-if = toolkit == "gtk2" || toolkit == "gtk3" # fails intermittently on Linux (bug 909342)
[browser_CTP_crashreporting.js]

View File

@ -25,7 +25,7 @@
}
.preview-input-toolbar {
display: -moz-box;
display: flex;
width: 100%;
}
@ -39,7 +39,7 @@
margin-bottom: 1px;
padding-top: 0;
padding-bottom: 0;
-moz-box-flex: 1;
flex: 1;
}
:root {

View File

@ -52,6 +52,7 @@ support-files =
[browser_perf-details-04.js]
[browser_perf-details-05.js]
[browser_perf-details-06.js]
[browser_perf-details-07.js]
[browser_perf-events-calltree.js]
[browser_perf-front-basic-profiler-01.js]
[browser_perf-front-basic-timeline-01.js]

View File

@ -0,0 +1,63 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that when flame chart views scroll to change selection,
* other detail views are rerendered
*/
let HORIZONTAL_AXIS = 1;
let VERTICAL_AXIS = 2;
function* spawnTest() {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, PerformanceController, OverviewView, DetailsView, WaterfallView, JsCallTreeView, JsFlameGraphView } = panel.panelWin;
yield startRecording(panel);
yield stopRecording(panel);
let waterfallRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
let calltreeRendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
let flamegraphRendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
OverviewView.setTimeInterval({ startTime: 10, endTime: 20 });
DetailsView.selectView("waterfall");
yield waterfallRendered;
DetailsView.selectView("js-calltree");
yield calltreeRendered;
DetailsView.selectView("js-flamegraph");
yield flamegraphRendered;
waterfallRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
calltreeRendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
let overviewRangeSelected = once(OverviewView, EVENTS.OVERVIEW_RANGE_SELECTED);
once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED).then(() =>
ok(false, "FlameGraphView should not rerender, but be handled via its graph widget"));
// Reset the range to full view, trigger a "selection" event as if
// our mouse has done this
scroll(JsFlameGraphView.graph, 200, HORIZONTAL_AXIS, 10);
DetailsView.selectView("waterfall");
yield waterfallRendered;
ok(true, "Waterfall rerendered by flame graph changing interval");
DetailsView.selectView("js-calltree");
yield calltreeRendered;
ok(true, "CallTree rerendered by flame graph changing interval");
yield teardown(panel);
finish();
}
// EventUtils just doesn't work!
function scroll(graph, wheel, axis, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseWheel({ testX: x, testY: y, axis, detail: wheel, axis,
HORIZONTAL_AXIS,
VERTICAL_AXIS
});
}

View File

@ -9,7 +9,6 @@
function* spawnTest() {
let { target, panel } = yield initPerformance(MARKERS_URL);
let { $, $$, EVENTS, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
let { L10N } = devtools.require("devtools/performance/global");
// Hijack the markers massaging part of creating the waterfall view,
// to prevent collapsing markers and allowing this test to verify
@ -45,9 +44,9 @@ function* spawnTest() {
const tests = {
ConsoleTime: function (marker) {
shouldHaveLabel($, L10N.getStr("timeline.markerDetail.consoleTimerName"), "!!!", marker);
shouldHaveStack($, "startStack", marker);
shouldHaveStack($, "endStack", marker);
shouldHaveLabel($, "Timer Name:", "!!!", marker);
return true;
},
TimeStamp: function (marker) {
@ -83,10 +82,12 @@ function* spawnTest() {
let m = markers[i];
EventUtils.sendMouseEvent({ type: "mousedown" }, bar);
if (testsDone.indexOf(m.name) === -1 && tests[m.name]) {
let fullTestComplete = tests[m.name](m);
if (fullTestComplete) {
testsDone.push(m.name);
if (tests[m.name]) {
if (testsDone.indexOf(m.name) === -1) {
let fullTestComplete = tests[m.name](m);
if (fullTestComplete) {
testsDone.push(m.name);
}
}
} else {
info(`TODO: Need to add marker details tests for ${m.name}`);

View File

@ -44,6 +44,15 @@ let DetailsSubview = {
*/
rangeChangeDebounceTime: 0,
/**
* When the overview range changes, all details views will require a
* rerendering at a later point, determined by `shouldUpdateWhenShown` and
* `canUpdateWhileHidden` and whether or not its the current view.
* Set `requiresUpdateOnRangeChange` to false to not invalidate the view
* when the range changes.
*/
requiresUpdateOnRangeChange: true,
/**
* Flag specifying if this view should be updated when selected. This will
* be set to true, for example, when the range changes in the overview and
@ -93,6 +102,9 @@ let DetailsSubview = {
* Fired when a range is selected or cleared in the OverviewView.
*/
_onOverviewRangeChange: function (_, interval) {
if (!this.requiresUpdateOnRangeChange) {
return;
}
if (DetailsView.isViewSelected(this)) {
let debounced = () => {
if (!this.shouldUpdateWhileMouseIsActive && OverviewView.isMouseActive) {

View File

@ -86,7 +86,13 @@ let JsFlameGraphView = Heritage.extend(DetailsSubview, {
*/
_onRangeChangeInGraph: function () {
let interval = this.graph.getViewRange();
OverviewView.setTimeInterval(interval, { stopPropagation: true });
// Squelch rerendering this view when we update the range here
// to avoid recursion, as our FlameGraph handles rerendering itself
// when originating from within the graph.
this.requiresUpdateOnRangeChange = false;
OverviewView.setTimeInterval(interval);
this.requiresUpdateOnRangeChange = true;
},
/**

View File

@ -84,7 +84,13 @@ let MemoryFlameGraphView = Heritage.extend(DetailsSubview, {
*/
_onRangeChangeInGraph: function () {
let interval = this.graph.getViewRange();
OverviewView.setTimeInterval(interval, { stopPropagation: true });
// Squelch rerendering this view when we update the range here
// to avoid recursion, as our FlameGraph handles rerendering itself
// when originating from within the graph.
this.requiresUpdateOnRangeChange = false;
OverviewView.setTimeInterval(interval);
this.requiresUpdateOnRangeChange = true;
},
/**

View File

@ -57,6 +57,7 @@ support-files =
[browser_graphs-07b.js]
[browser_graphs-07c.js]
[browser_graphs-07d.js]
[browser_graphs-07e.js]
[browser_graphs-08.js]
[browser_graphs-09a.js]
[browser_graphs-09b.js]

View File

@ -0,0 +1,105 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that selections are drawn onto the canvas.
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
let {LineGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
let CURRENT_ZOOM = 1;
add_task(function*() {
yield promiseTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host, win, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
graph.setData(TEST_DATA);
info("Testing with normal zoom.");
testGraph(graph);
info("Testing while zoomed out.");
setZoom(host.frame, .5);
testGraph(graph);
info("Testing while zoomed in.");
setZoom(host.frame, 2);
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
graph.dropSelection();
info("Making a selection.");
dragStart(graph, 100);
ok(graph.hasSelectionInProgress(),
"The selection should start (1).");
is(graph.getSelection().start, 100,
"The current selection start value is correct (1).");
is(graph.getSelection().end, 100,
"The current selection end value is correct (1).");
hover(graph, 200);
ok(graph.hasSelectionInProgress(),
"The selection should still be in progress (2).");
is(graph.getSelection().start, 100,
"The current selection start value is correct (2).");
is(graph.getSelection().end, 200,
"The current selection end value is correct (2).");
dragStop(graph, 300);
ok(!graph.hasSelectionInProgress(),
"The selection should have stopped (3).");
is(graph.getSelection().start, 100,
"The current selection start value is correct (3).");
is(graph.getSelection().end, 300,
"The current selection end value is correct (3).");
}
function setZoom(frame, zoomValue) {
let contViewer = frame.docShell.contentViewer;
CURRENT_ZOOM = contViewer.fullZoom = zoomValue;
}
// EventUtils just doesn't work!
function dispatchEvent(graph, x, y, fn) {
x *= CURRENT_ZOOM;
y *= CURRENT_ZOOM;
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
let quad = graph._canvas.getBoxQuads({
relativeTo: window.document
})[0];
let screenX = (window.screenX + quad.p1.x + x);
let screenY = (window.screenY + quad.p1.y + y);
fn({
screenX: screenX,
screenY: screenY,
});
}
function hover(graph, x, y = 1) {
dispatchEvent(graph, x, y, graph._onMouseMove);
}
function dragStart(graph, x, y = 1) {
dispatchEvent(graph, x, y, graph._onMouseMove);
dispatchEvent(graph, x, y, graph._onMouseDown);
}
function dragStop(graph, x, y = 1) {
dispatchEvent(graph, x, y, graph._onMouseMove);
dispatchEvent(graph, x, y, graph._onMouseUp);
}

View File

@ -10,6 +10,7 @@ const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
const {DevToolsWorker} = Cu.import("resource://gre/modules/devtools/shared/worker.js", {});
const {LayoutHelpers} = Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", {});
this.EXPORTED_SYMBOLS = [
"GraphCursor",
@ -977,6 +978,12 @@ AbstractCanvasGraph.prototype = {
let mouseX = Math.max(0, Math.min(x, maxX)) * this._pixelRatio;
let mouseY = Math.max(0, Math.min(x, maxY)) * this._pixelRatio;
// The coordinates need to be modified with the current zoom level
// to prevent them from being wrong.
let zoom = LayoutHelpers.getCurrentZoom(this._canvas);
mouseX /= zoom;
mouseY /= zoom;
return {mouseX,mouseY};
},

View File

@ -38,7 +38,7 @@
<body>
<div id="root" class="devtools-monospace">
<div class="devtools-toolbar">
<div id="ruleview-toolbar" class="devtools-toolbar">
<div class="devtools-searchbox">
<input id="ruleview-searchbox"
class="devtools-searchinput devtools-rule-searchbox"
@ -47,6 +47,12 @@
</div>
<!-- TODO : Bug 1165122 : Show this button by default -->
<button hidden="true" id="ruleview-add-rule-button" title="&addRuleButtonTooltip;" class="devtools-button"></button>
<button id="pseudo-class-panel-toggle" class="devtools-button"></button>
</div>
<div id="pseudo-class-panel" class="devtools-toolbar" hidden="true">
<label><input id="pseudo-hover-toggle" type="checkbox" value=":hover" />:hover</label>
<label><input id="pseudo-active-toggle" type="checkbox" value=":active" />:active</label>
<label><input id="pseudo-focus-toggle" type="checkbox" value=":focus" />:focus</label>
</div>
</div>

View File

@ -1130,11 +1130,18 @@ function CssRuleView(aInspector, aDoc, aStore, aPageStyle) {
this._onFilterKeyPress = this._onFilterKeyPress.bind(this);
this._onClearSearch = this._onClearSearch.bind(this);
this._onFilterTextboxContextMenu = this._onFilterTextboxContextMenu.bind(this);
this._onTogglePseudoClassPanel = this._onTogglePseudoClassPanel.bind(this);
this._onTogglePseudoClass = this._onTogglePseudoClass.bind(this);
this.element = this.doc.getElementById("ruleview-container");
this.addRuleButton = this.doc.getElementById("ruleview-add-rule-button");
this.searchField = this.doc.getElementById("ruleview-searchbox");
this.searchClearButton = this.doc.getElementById("ruleview-searchinput-clear");
this.pseudoClassPanel = this.doc.getElementById("pseudo-class-panel");
this.pseudoClassToggle = this.doc.getElementById("pseudo-class-panel-toggle");
this.hoverCheckbox = this.doc.getElementById("pseudo-hover-toggle");
this.activeCheckbox = this.doc.getElementById("pseudo-active-toggle");
this.focusCheckbox = this.doc.getElementById("pseudo-focus-toggle");
this.searchClearButton.hidden = true;
@ -1145,6 +1152,10 @@ function CssRuleView(aInspector, aDoc, aStore, aPageStyle) {
this.searchField.addEventListener("keypress", this._onFilterKeyPress);
this.searchField.addEventListener("contextmenu", this._onFilterTextboxContextMenu);
this.searchClearButton.addEventListener("click", this._onClearSearch);
this.pseudoClassToggle.addEventListener("click", this._onTogglePseudoClassPanel);
this.hoverCheckbox.addEventListener("click", this._onTogglePseudoClass);
this.activeCheckbox.addEventListener("click", this._onTogglePseudoClass);
this.focusCheckbox.addEventListener("click", this._onTogglePseudoClass);
this._handlePrefChange = this._handlePrefChange.bind(this);
this._onSourcePrefChanged = this._onSourcePrefChanged.bind(this);
@ -1823,8 +1834,19 @@ CssRuleView.prototype = {
this.searchField.removeEventListener("contextmenu",
this._onFilterTextboxContextMenu);
this.searchClearButton.removeEventListener("click", this._onClearSearch);
this.pseudoClassToggle.removeEventListener("click",
this._onTogglePseudoClassPanel);
this.hoverCheckbox.removeEventListener("click", this._onTogglePseudoClass);
this.activeCheckbox.removeEventListener("click", this._onTogglePseudoClass);
this.focusCheckbox.removeEventListener("click", this._onTogglePseudoClass);
this.searchField = null;
this.searchClearButton = null;
this.pseudoClassPanel = null;
this.pseudoClassToggle = null;
this.hoverCheckbox = null;
this.activeCheckbox = null;
this.focusCheckbox = null;
if (this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
@ -1849,10 +1871,12 @@ CssRuleView.prototype = {
}
this.clear();
this.clearPseudoClassPanel();
this._viewedElement = aElement;
if (!this._viewedElement) {
this._showEmpty();
this.refreshPseudoClassPanel();
return promise.resolve(undefined);
}
@ -1894,6 +1918,45 @@ CssRuleView.prototype = {
});
},
/**
* Clear the pseudo class options panel by removing the checked and disabled
* attributes for each checkbox.
*/
clearPseudoClassPanel: function() {
this.hoverCheckbox.checked = this.hoverCheckbox.disabled = false;
this.activeCheckbox.checked = this.activeCheckbox.disabled = false;
this.focusCheckbox.checked = this.focusCheckbox.disabled = false;
},
/**
* Update the pseudo class options for the currently highlighted element.
*/
refreshPseudoClassPanel: function() {
if (!this._elementStyle || !this.inspector.selection.isElementNode()) {
this.hoverCheckbox.disabled = true;
this.activeCheckbox.disabled = true;
this.focusCheckbox.disabled = true;
return;
}
for (let pseudoClassLock of this._elementStyle.element.pseudoClassLocks) {
switch (pseudoClassLock) {
case ":hover": {
this.hoverCheckbox.checked = true;
break;
}
case ":active": {
this.activeCheckbox.checked = true;
break;
}
case ":focus": {
this.focusCheckbox.checked = true;
break;
}
}
}
},
_populate: function(clearRules = false) {
let elementStyle = this._elementStyle;
return this._elementStyle.populate().then(() => {
@ -1906,6 +1969,8 @@ CssRuleView.prototype = {
}
this._createEditors();
this.refreshPseudoClassPanel();
// Notify anyone that cares that we refreshed.
this.emit("ruleview-refreshed");
return undefined;
@ -2289,7 +2354,29 @@ CssRuleView.prototype = {
}
this._editorsExpandedForFilter = [];
}
},
/**
* Called when the pseudo class panel button is clicked and toggles
* the display of the pseudo class panel.
*/
_onTogglePseudoClassPanel: function() {
if (this.pseudoClassPanel.hidden) {
this.pseudoClassToggle.setAttribute("checked", "true");
} else {
this.pseudoClassToggle.removeAttribute("checked");
}
this.pseudoClassPanel.hidden = !this.pseudoClassPanel.hidden;
},
/**
* Called when a pseudo class checkbox is clicked and toggles
* the pseudo class for the current selected element.
*/
_onTogglePseudoClass: function(event) {
let target = event.currentTarget;
this.inspector.togglePseudoClass(target.value);
}
};
/**

View File

@ -24,9 +24,28 @@ body {
flex: 1;
}
#root .devtools-toolbar {
.devtools-toolbar {
width: 100%;
display: -moz-box;
display: flex;
}
#pseudo-class-panel {
position: relative;
top: -1px;
overflow-y: hidden;
max-height: 24px;
justify-content: space-around;
transition-property: max-height;
transition-duration: 150ms;
transition-timing-function: ease;
}
#pseudo-class-panel[hidden] {
max-height: 0px;
}
#pseudo-class-panel > label {
-moz-user-select: none;
}
.ruleview {

View File

@ -114,6 +114,7 @@ skip-if = (os == "win" && debug) || e10s # bug 963492: win. bug 1040653: e10s.
[browser_ruleview_pseudo-element_01.js]
[browser_ruleview_pseudo-element_02.js]
skip-if = e10s # Bug 1090340
[browser_ruleview_pseudo_lock_options.js]
[browser_ruleview_refresh-on-attribute-change_01.js]
[browser_ruleview_refresh-on-attribute-change_02.js]
[browser_ruleview_refresh-on-style-change.js]

View File

@ -0,0 +1,104 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the rule view pseudo lock options work properly.
let TEST_URI = [
"<style type='text/css'>",
" div {",
" color: red;",
" }",
" div:hover {",
" color: blue;",
" }",
" div:active {",
" color: yellow;",
" }",
" div:focus {",
" color: green;",
" }",
"</style>",
"<div>test div</div>"
].join("\n");
add_task(function*() {
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {inspector, view} = yield openRuleView();
yield selectNode("div", inspector);
info("Toggle the pseudo class panel open");
ok(view.pseudoClassPanel.hidden, "Pseudo Class Panel Hidden");
view.pseudoClassToggle.click();
ok(!view.pseudoClassPanel.hidden, "Pseudo Class Panel Opened");
ok(!view.hoverCheckbox.disabled, ":hover checkbox is not disabled");
ok(!view.activeCheckbox.disabled, ":active checkbox is not disabled");
ok(!view.focusCheckbox.disabled, ":focus checkbox is not disabled");
info("Toggle each pseudo lock and check that the pseudo lock is added");
yield togglePseudoClass(inspector, view, view.hoverCheckbox);
yield assertPseudoAdded(inspector, view, ":hover", 3, 1);
yield togglePseudoClass(inspector, view, view.hoverCheckbox);
yield assertPseudoRemoved(inspector, view, 2);
yield togglePseudoClass(inspector, view, view.activeCheckbox);
yield assertPseudoAdded(inspector, view, ":active", 3, 1);
yield togglePseudoClass(inspector, view, view.activeCheckbox);
yield assertPseudoRemoved(inspector, view, 2);
yield togglePseudoClass(inspector, view, view.focusCheckbox);
yield assertPseudoAdded(inspector, view, ":focus", 3, 1);
yield togglePseudoClass(inspector, view, view.focusCheckbox);
yield assertPseudoRemoved(inspector, view, 2);
info("Toggle all pseudo lock and check that the pseudo lock is added");
yield togglePseudoClass(inspector, view, view.hoverCheckbox);
yield togglePseudoClass(inspector, view, view.activeCheckbox);
yield togglePseudoClass(inspector, view, view.focusCheckbox);
yield assertPseudoAdded(inspector, view, ":focus", 5, 1);
yield assertPseudoAdded(inspector, view, ":active", 5, 2);
yield assertPseudoAdded(inspector, view, ":hover", 5, 3);
yield togglePseudoClass(inspector, view, view.hoverCheckbox);
yield togglePseudoClass(inspector, view, view.activeCheckbox);
yield togglePseudoClass(inspector, view, view.focusCheckbox);
yield assertPseudoRemoved(inspector, view, 2);
info("Select a null element");
yield view.selectElement(null);
ok(!view.hoverCheckbox.checked && view.hoverCheckbox.disabled,
":hover checkbox is unchecked and disabled");
ok(!view.activeCheckbox.checked && view.activeCheckbox.disabled,
":active checkbox is unchecked and disabled");
ok(!view.focusCheckbox.checked && view.focusCheckbox.disabled,
":focus checkbox is unchecked and disabled");
info("Toggle the pseudo class panel close");
view.pseudoClassToggle.click();
ok(view.pseudoClassPanel.hidden, "Pseudo Class Panel Closed");
});
function* togglePseudoClass(inspector, ruleView, pseudoClassOption) {
info("Toggle the pseudoclass, wait for it to be applied");
let onRefresh = inspector.once("rule-view-refreshed");
pseudoClassOption.click();
yield onRefresh;
}
function* assertPseudoAdded(inspector, ruleView, pseudoClass, numRules,
childIndex) {
info("Check that the ruleview contains the pseudo-class rule");
is(ruleView.element.children.length, numRules,
"Should have " + numRules + " rules.");
is(getRuleViewRuleEditor(ruleView, childIndex).rule.selectorText,
"div" + pseudoClass, "rule view is showing " + pseudoClass + " rule");
}
function* assertPseudoRemoved(inspector, ruleView, numRules) {
info("Check that the ruleview no longer contains the pseudo-class rule");
is(ruleView.element.children.length, numRules,
"Should have " + numRules + " rules.");
is(getRuleViewRuleEditor(ruleView, 1).rule.selectorText, "div",
"Second rule is div");
}

View File

@ -665,8 +665,9 @@
@RESPATH@/browser/chrome/shumway.manifest
@RESPATH@/browser/chrome/shumway/*
#endif
@RESPATH@/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf
@RESPATH@/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.manifest
@RESPATH@/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/icon.png
@RESPATH@/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf
@RESPATH@/chrome/toolkit@JAREXT@
@RESPATH@/chrome/toolkit.manifest
@RESPATH@/chrome/recording.manifest

View File

@ -258,6 +258,7 @@ browser.jar:
skin/classic/browser/devtools/add.svg (../shared/devtools/images/add.svg)
skin/classic/browser/devtools/filters.svg (../shared/devtools/filters.svg)
skin/classic/browser/devtools/filter-swatch.svg (../shared/devtools/images/filter-swatch.svg)
skin/classic/browser/devtools/pseudo-class.svg (../shared/devtools/images/pseudo-class.svg)
skin/classic/browser/devtools/controls.png (../shared/devtools/images/controls.png)
skin/classic/browser/devtools/controls@2x.png (../shared/devtools/images/controls@2x.png)
skin/classic/browser/devtools/performance-icons.svg (../shared/devtools/images/performance-icons.svg)
@ -469,6 +470,7 @@ browser.jar:
skin/classic/browser/warning16.png (../shared/warning16.png)
skin/classic/browser/warning16@2x.png (../shared/warning16@2x.png)
../extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
% override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png
% override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png
% override chrome://browser/skin/feeds/videoFeedIcon.png chrome://browser/skin/feeds/feedIcon.png

View File

@ -373,6 +373,7 @@ browser.jar:
skin/classic/browser/devtools/add.svg (../shared/devtools/images/add.svg)
skin/classic/browser/devtools/filters.svg (../shared/devtools/filters.svg)
skin/classic/browser/devtools/filter-swatch.svg (../shared/devtools/images/filter-swatch.svg)
skin/classic/browser/devtools/pseudo-class.svg (../shared/devtools/images/pseudo-class.svg)
skin/classic/browser/devtools/controls.png (../shared/devtools/images/controls.png)
skin/classic/browser/devtools/controls@2x.png (../shared/devtools/images/controls@2x.png)
skin/classic/browser/devtools/performance-icons.svg (../shared/devtools/images/performance-icons.svg)
@ -613,6 +614,7 @@ browser.jar:
skin/classic/browser/warning16.png (../shared/warning16.png)
skin/classic/browser/warning16@2x.png (../shared/warning16@2x.png)
../extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
% override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png
% override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png
% override chrome://browser/skin/feeds/videoFeedIcon.png chrome://browser/skin/feeds/feedIcon.png

View File

@ -153,7 +153,7 @@ body {
#root .devtools-toolbar {
width: 100%;
display: -moz-box;
display: flex;
}
.link {

View File

@ -0,0 +1,29 @@
<!-- 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/. -->
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style>
use[id^="pseudo-class"]:not(:target) {
display: none;
}
</style>
<rect id="class-block-maskBG" width="8" height="8" fill="#fff"/>
<rect id="class-block" width="8" height="8" rx="1" ry="1"/>
<mask id="mask-block-solid">
<use xlink:href="#class-block-maskBG"/>
<use xlink:href="#class-block" transform="translate(3 3)" fill="#000"/>
</mask>
<g id="pseudo-class-shape">
<rect x=".5" y=".5" width="7" height="7" rx="1" ry="1" mask="url(#mask-block-solid)" fill="none" stroke="currentColor" stroke-width="1"/>
<use xlink:href="#class-block" mask="url(#mask-block-solid)" fill="currentColor" fill-opacity=".4"/>
<use xlink:href="#class-block" mask="url(#mask-block-solid)" fill="currentColor" transform="translate(4 4)"/>
<g transform="translate(8 8)" fill="currentColor">
<path d="M2.5,0C2.2,0,2,0.2,2,0.5C2,0.8,2.2,1,2.5,1C2.8,1,3,0.8,3,0.5 C3,0.2,2.8,0,2.5,0z M4.5,0C4.2,0,4,0.2,4,0.5C4,0.8,4.2,1,4.5,1C4.8,1,5,0.8,5,0.5C5,0.2,4.8,0,4.5,0z M0.5,6C0.8,6,1,5.8,1,5.5 C1,5.2,0.8,5,0.5,5C0.2,5,0,5.2,0,5.5C0,5.8,0.2,6,0.5,6z M0.5,4C0.8,4,1,3.8,1,3.5C1,3.2,0.8,3,0.5,3C0.2,3,0,3.2,0,3.5 C0,3.8,0.2,4,0.5,4z M7.5,2C7.2,2,7,2.2,7,2.5C7,2.8,7.2,3,7.5,3C7.8,3,8,2.8,8,2.5C8,2.2,7.8,2,7.5,2z M7.5,4C7.2,4,7,4.2,7,4.5 C7,4.8,7.2,5,7.5,5C7.8,5,8,4.8,8,4.5C8,4.2,7.8,4,7.5,4z M5.5,7C5.2,7,5,7.2,5,7.5C5,7.8,5.2,8,5.5,8C5.8,8,6,7.8,6,7.5 C6,7.2,5.8,7,5.5,7z M3.5,7C3.2,7,3,7.2,3,7.5C3,7.8,3.2,8,3.5,8C3.8,8,4,7.8,4,7.5C4,7.2,3.8,7,3.5,7z M0.5,2C0.8,2,1,1.8,1,1.5v-1 C1,0.2,0.8,0,0.5,0C0.2,0,0,0.2,0,0.5v1C0,1.8,0.2,2,0.5,2z M8,0.5C8,0.2,7.8,0,7.5,0h-1C6.2,0,6,0.2,6,0.5C6,0.8,6.2,1,6.5,1h1 C7.8,1,8,0.8,8,0.5z M7.5,6C7.2,6,7,6.2,7,6.5v1C7,7.8,7.2,8,7.5,8C7.8,8,8,7.8,8,7.5v-1C8,6.2,7.8,6,7.5,6z M1.5,7h-1 C0.2,7,0,7.2,0,7.5C0,7.8,0.2,8,0.5,8h1C1.8,8,2,7.8,2,7.5C2,7.2,1.8,7,1.5,7z"/>
<use xlink:href="#class-block" fill-opacity=".2"/>
</g>
</g>
</defs>
<use xlink:href="#pseudo-class-shape" id="pseudo-class" color="#edf0f1"/>
<use xlink:href="#pseudo-class-shape" id="pseudo-class-checked" color="#3089C9"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -273,3 +273,11 @@
background-image: url("chrome://browser/skin/devtools/add.svg");
background-size: cover;
}
#pseudo-class-panel-toggle::before {
background-image: url("chrome://browser/skin/devtools/pseudo-class.svg#pseudo-class");
background-size: cover;
}
#pseudo-class-panel-toggle[checked]::before {
background-image: url("chrome://browser/skin/devtools/pseudo-class.svg#pseudo-class-checked");
}

View File

@ -368,8 +368,8 @@
/* Searchbox is a div container element for a search input element */
.devtools-searchbox {
display: -moz-box;
-moz-box-flex: 1;
display: flex;
flex: 1;
position: relative;
}

View File

@ -347,6 +347,7 @@ browser.jar:
skin/classic/browser/devtools/add.svg (../shared/devtools/images/add.svg)
skin/classic/browser/devtools/filters.svg (../shared/devtools/filters.svg)
skin/classic/browser/devtools/filter-swatch.svg (../shared/devtools/images/filter-swatch.svg)
skin/classic/browser/devtools/pseudo-class.svg (../shared/devtools/images/pseudo-class.svg)
skin/classic/browser/devtools/controls.png (../shared/devtools/images/controls.png)
skin/classic/browser/devtools/controls@2x.png (../shared/devtools/images/controls@2x.png)
skin/classic/browser/devtools/performance-icons.svg (../shared/devtools/images/performance-icons.svg)
@ -563,6 +564,7 @@ browser.jar:
skin/classic/browser/warning16.png (../shared/warning16.png)
skin/classic/browser/warning16@2x.png (../shared/warning16@2x.png)
../extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
% override chrome://browser/skin/page-livemarks.png chrome://browser/skin/feeds/feedIcon16.png
% override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png
% override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png

View File

@ -947,6 +947,29 @@ nsChromeRegistryChrome::ManifestOverride(ManifestProcessingContext& cx, int line
return;
}
if (cx.mType == NS_SKIN_LOCATION) {
bool chromeSkinOnly = false;
nsresult rv = chromeuri->SchemeIs("chrome", &chromeSkinOnly);
chromeSkinOnly = chromeSkinOnly && NS_SUCCEEDED(rv);
if (chromeSkinOnly) {
rv = resolveduri->SchemeIs("chrome", &chromeSkinOnly);
chromeSkinOnly = chromeSkinOnly && NS_SUCCEEDED(rv);
}
if (chromeSkinOnly) {
nsAutoCString chromePath, resolvedPath;
chromeuri->GetPath(chromePath);
resolveduri->GetPath(resolvedPath);
chromeSkinOnly = StringBeginsWith(chromePath, NS_LITERAL_CSTRING("/skin/")) &&
StringBeginsWith(resolvedPath, NS_LITERAL_CSTRING("/skin/"));
}
if (!chromeSkinOnly) {
LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
"Cannot register non-chrome://.../skin/ URIs '%s' and '%s' as overrides and/or to be overridden from a skin manifest.",
chrome, resolved);
return;
}
}
if (!CanLoadResource(resolveduri)) {
LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
"Cannot register non-local URI '%s' for an override.", resolved);

View File

@ -70,6 +70,10 @@ public class GlobalConstants {
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", // 20+
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", // 20+
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", // 11+
// For Sync 1.1.
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", // 9+
"TLS_RSA_WITH_AES_128_CBC_SHA", // 9+
};
} else if (Versions.feature11Plus) {
DEFAULT_CIPHER_SUITES = new String[]
@ -77,7 +81,10 @@ public class GlobalConstants {
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", // 11+
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", // 11+
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", // 11+
"TLS_RSA_WITH_AES_256_CBC_SHA", // 9+
// For Sync 1.1.
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", // 9+
"TLS_RSA_WITH_AES_128_CBC_SHA", // 9+
};
} else { // 9+
// Fall back to the only half-decent cipher suites supported on Gingerbread.
@ -91,8 +98,8 @@ public class GlobalConstants {
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
// This is for Sync 1.1.
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", // 9+
"TLS_RSA_WITH_AES_256_CBC_SHA", // 9+
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", // 9+
"TLS_RSA_WITH_AES_128_CBC_SHA", // 9+
};
}

View File

@ -1,11 +1,11 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 21
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
targetSdkVersion 21
targetSdkVersion 22
minSdkVersion 9
}

View File

@ -1,11 +1,11 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 21
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
targetSdkVersion 21
targetSdkVersion 22
minSdkVersion 9
}
@ -47,11 +47,11 @@ android {
}
dependencies {
compile 'com.android.support:support-v4:21.+'
compile 'com.android.support:support-v4:22.+'
if (mozconfig.substs.MOZ_NATIVE_DEVICES) {
compile 'com.android.support:appcompat-v7:21.+'
compile 'com.android.support:mediarouter-v7:21.+'
compile 'com.android.support:appcompat-v7:22.+'
compile 'com.android.support:mediarouter-v7:22.+'
compile 'com.google.android.gms:play-services-base:6.5.+'
compile 'com.google.android.gms:play-services-cast:6.5.+'
}

View File

@ -1,11 +1,11 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 21
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
targetSdkVersion 21
targetSdkVersion 22
minSdkVersion 9
}

View File

@ -1,11 +1,11 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 21
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
targetSdkVersion 21
targetSdkVersion 22
minSdkVersion 9
}

View File

@ -1,11 +1,11 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 21
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
targetSdkVersion 21
targetSdkVersion 22
minSdkVersion 9
}

View File

@ -1,11 +1,11 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 21
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
targetSdkVersion 21
targetSdkVersion 22
minSdkVersion 9
}
@ -29,5 +29,5 @@ android {
}
dependencies {
compile 'com.android.support:support-v4:21.+'
compile 'com.android.support:support-v4:22.+'
}

View File

@ -1,11 +1,11 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 21
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
targetSdkVersion 21
targetSdkVersion 22
minSdkVersion 9
}
@ -28,5 +28,5 @@ android {
}
dependencies {
compile 'com.android.support:support-v4:21.+'
compile 'com.android.support:support-v4:22.+'
}

View File

@ -70,7 +70,7 @@ class JarMaker(object):
'''
ignore = re.compile('\s*(\#.*)?$')
jarline = re.compile('(?:(?P<jarfile>[\w\d.\-\_\\\/]+).jar\:)|(?:\s*(\#.*)?)\s*$')
jarline = re.compile('(?:(?P<jarfile>[\w\d.\-\_\\\/{}]+).jar\:)|(?:\s*(\#.*)?)\s*$')
relsrcline = re.compile('relativesrcdir\s+(?P<relativesrcdir>.+?):')
regline = re.compile('\%\s+(.*)$')
entryre = '(?P<optPreprocess>\*)?(?P<optOverwrite>\+?)\s+'

View File

@ -1018,6 +1018,8 @@ static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = {
{ "goto.google.com", true, false, false, -1, &kPinset_google_root_pems },
{ "groups.google.com", true, false, false, -1, &kPinset_google_root_pems },
{ "gstatic.com", true, false, false, -1, &kPinset_google_root_pems },
{ "gvt2.com", true, false, false, -1, &kPinset_google_root_pems },
{ "gvt3.com", true, false, false, -1, &kPinset_google_root_pems },
{ "hangouts.google.com", true, false, false, -1, &kPinset_google_root_pems },
{ "history.google.com", true, false, false, -1, &kPinset_google_root_pems },
{ "hostedtalkgadget.google.com", true, false, false, -1, &kPinset_google_root_pems },
@ -1089,8 +1091,8 @@ static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = {
{ "ytimg.com", true, false, false, -1, &kPinset_google_root_pems },
};
// Pinning Preload List Length = 350;
// Pinning Preload List Length = 352;
static const int32_t kUnknownId = -1;
static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1441448291452000);
static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1442053104631000);

View File

@ -1,6 +1,6 @@
300651.ru: did not receive HSTS header
56ct.com: could not connect to host
9point6.com: could not connect to host
activiti.alfresco.com: did not receive HSTS header
adamkostecki.de: could not connect to host
admin.google.com: did not receive HSTS header (error ignored - included regardless)
adsfund.org: could not connect to host
@ -25,6 +25,9 @@ atavio.at: could not connect to host
atavio.ch: could not connect to host
atavio.de: did not receive HSTS header
au.search.yahoo.com: did not receive HSTS header
aurainfosec.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
auraredeye.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
auraredshield.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
auth.mail.ru: did not receive HSTS header
auto4trade.nl: did not receive HSTS header
az.search.yahoo.com: did not receive HSTS header
@ -37,7 +40,6 @@ bccx.com: could not connect to host
bcm.com.au: max-age too low: 0
be.search.yahoo.com: did not receive HSTS header
bedeta.de: could not connect to host
bentertain.de: could not connect to host
betnet.fr: could not connect to host
bi.search.yahoo.com: did not receive HSTS header
bidon.ca: did not receive HSTS header
@ -48,12 +50,14 @@ bitgo.com: max-age too low: 0
bizon.sk: did not receive HSTS header
blacklane.com: did not receive HSTS header
blog.lookout.com: did not receive HSTS header
blubbablasen.de: could not connect to host
bonigo.de: did not receive HSTS header
br.search.yahoo.com: did not receive HSTS header
braintreepayments.com: did not receive HSTS header
brainvation.de: did not receive HSTS header
bran.cc: could not connect to host
browserid.org: did not receive HSTS header
brunosouza.org: could not connect to host
business.medbank.com.mt: did not receive HSTS header
buttercoin.com: did not receive HSTS header
ca.search.yahoo.com: did not receive HSTS header
@ -75,7 +79,7 @@ cheesetart.my: did not receive HSTS header
chfr.search.yahoo.com: did not receive HSTS header
chit.search.yahoo.com: did not receive HSTS header
chm.vn: did not receive HSTS header
chontalpa.pw: did not receive HSTS header
chontalpa.pw: could not connect to host
chrome-devtools-frontend.appspot.com: did not receive HSTS header (error ignored - included regardless)
chrome.google.com: did not receive HSTS header (error ignored - included regardless)
cimballa.com: did not receive HSTS header
@ -101,10 +105,10 @@ ct.search.yahoo.com: did not receive HSTS header
cujanovic.com: did not receive HSTS header
cyanogenmod.xxx: could not connect to host
cybershambles.com: could not connect to host
dash-board.jp: could not connect to host
datasnitch.co.uk: could not connect to host
daylightcompany.com: did not receive HSTS header
de.search.yahoo.com: did not receive HSTS header
deadbeef.ninja: could not connect to host
decibelios.li: did not receive HSTS header
destinationbijoux.fr: max-age too low: 2678400
devh.de: did not receive HSTS header
@ -136,6 +140,7 @@ esec.rs: did not receive HSTS header
espanol.search.yahoo.com: did not receive HSTS header
espra.com: could not connect to host
etsysecure.com: could not connect to host
exiahost.com: did not receive HSTS header
ezimoeko.net: did not receive HSTS header
eztv.ch: did not receive HSTS header
fabianfischer.de: did not receive HSTS header
@ -145,6 +150,7 @@ firebaseio.com: could not connect to host
fixingdns.com: did not receive HSTS header
fj.search.yahoo.com: did not receive HSTS header
fleximus.org: max-age too low: 1
floweslawncare.com: could not connect to host
fm83.nl: did not receive HSTS header
fonetiq.io: could not connect to host
fr.search.yahoo.com: did not receive HSTS header
@ -163,14 +169,17 @@ googlemail.com: did not receive HSTS header (error ignored - included regardless
googleplex.com: could not connect to host
googleplex.com: could not connect to host (error ignored - included regardless)
goto.google.com: did not receive HSTS header (error ignored - included regardless)
gotowned.org: did not receive HSTS header
gparent.org: did not receive HSTS header
gr.search.yahoo.com: did not receive HSTS header
grandmascookieblog.com: did not receive HSTS header
greplin.com: could not connect to host
groups.google.com: did not receive HSTS header (error ignored - included regardless)
guidetoiceland.is: did not receive HSTS header
hackerone-user-content.com: could not connect to host
gvt2.com: could not connect to host (error ignored - included regardless)
gvt3.com: could not connect to host (error ignored - included regardless)
hangouts.google.com: did not receive HSTS header (error ignored - included regardless)
hash-list.com: could not connect to host
hatoko.net: could not connect to host
history.google.com: did not receive HSTS header (error ignored - included regardless)
hk.search.yahoo.com: did not receive HSTS header
@ -209,6 +218,7 @@ jkbuster.com: could not connect to host
johners.me: could not connect to host
jottit.com: could not connect to host
julian-kipka.de: did not receive HSTS header
jwilsson.com: did not receive HSTS header
k-dev.de: could not connect to host
kamikano.com: did not receive HSTS header
keeley.gq: could not connect to host
@ -246,13 +256,11 @@ lumi.do: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FA
luxus-russen.de: could not connect to host
lv.search.yahoo.com: did not receive HSTS header
m.gparent.org: could not connect to host
maff.scot: did not receive HSTS header
mail.google.com: did not receive HSTS header (error ignored - included regardless)
maktoob.search.yahoo.com: did not receive HSTS header
malaysia.search.yahoo.com: did not receive HSTS header
manage.zenpayroll.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
market.android.com: did not receive HSTS header (error ignored - included regardless)
markusueberallassetmanagement.de: could not connect to host
marshut.net: could not connect to host
mccrypto.de: could not connect to host
mediacru.sh: could not connect to host
@ -267,6 +275,7 @@ mnemotiv.com: could not connect to host
mobilethreat.net: could not connect to host
mobilethreatnetwork.net: could not connect to host
mocloud.eu: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
mpreserver.com: could not connect to host
mqas.net: could not connect to host
mt.search.yahoo.com: did not receive HSTS header
mu.search.yahoo.com: did not receive HSTS header
@ -300,6 +309,7 @@ npw.net: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FA
null-sec.ru: could not connect to host
nz.search.yahoo.com: did not receive HSTS header
nzb.cat: did not receive HSTS header
ooonja.de: could not connect to host
opendesk.cc: did not receive HSTS header
openshift.redhat.com: did not receive HSTS header
otakurepublic.com: did not receive HSTS header
@ -311,6 +321,7 @@ passwordbox.com: did not receive HSTS header
passwords.google.com: did not receive HSTS header (error ignored - included regardless)
pe.search.yahoo.com: did not receive HSTS header
ph.search.yahoo.com: did not receive HSTS header
phurl.de: could not connect to host
piratenlogin.de: could not connect to host
pisidia.de: did not receive HSTS header
pk.search.yahoo.com: did not receive HSTS header
@ -322,7 +333,6 @@ popcorntime.ws: max-age too low: 2592000
pr.search.yahoo.com: did not receive HSTS header
pressfreedomfoundation.org: did not receive HSTS header
prodpad.com: did not receive HSTS header
projektzentrisch.de: could not connect to host
promecon-gmbh.de: did not receive HSTS header
proximato.com: could not connect to host
pult.co: could not connect to host
@ -342,7 +352,6 @@ ro.search.yahoo.com: did not receive HSTS header
roan24.pl: did not receive HSTS header
roddis.net: did not receive HSTS header
ru.search.yahoo.com: did not receive HSTS header
rubyshop.nl: did not receive HSTS header
rw.search.yahoo.com: did not receive HSTS header
sah3.net: could not connect to host
saturngames.co.uk: could not connect to host
@ -351,6 +360,7 @@ script.google.com: did not receive HSTS header (error ignored - included regardl
sdsl-speedtest.de: could not connect to host
se.search.yahoo.com: did not receive HSTS header
search.yahoo.com: did not receive HSTS header
security-carpet.com: could not connect to host
security.google.com: did not receive HSTS header (error ignored - included regardless)
sellocdn.com: could not connect to host
semenkovich.com: did not receive HSTS header
@ -358,7 +368,6 @@ seomobo.com: did not receive HSTS header
seowarp.net: max-age too low: 1576800
serverdensity.io: did not receive HSTS header
sg.search.yahoo.com: did not receive HSTS header
sh-network.de: could not connect to host
shops.neonisi.com: could not connect to host
siammedia.co: did not receive HSTS header
silentcircle.org: could not connect to host
@ -383,7 +392,6 @@ sunshinepress.org: could not connect to host
surfeasy.com: did not receive HSTS header
suzukikenichi.com: did not receive HSTS header
sv.search.yahoo.com: did not receive HSTS header
sychov.pro: could not connect to host
sylaps.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
t.facebook.com: did not receive HSTS header
tablet.facebook.com: did not receive HSTS header
@ -423,10 +431,9 @@ ve.search.yahoo.com: did not receive HSTS header
vhost.co.id: could not connect to host
viennan.net: did not receive HSTS header
vn.search.yahoo.com: did not receive HSTS header
vyncke.org: did not receive HSTS header
vyncke.org: max-age too low: 2678400
webmail.mayfirst.org: did not receive HSTS header
wikidsystems.com: did not receive HSTS header
willnorris.com: could not connect to host
withustrading.com: did not receive HSTS header
wiz.biz: could not connect to host
wohnungsbau-ludwigsburg.de: did not receive HSTS header
@ -455,5 +462,6 @@ xtream-hosting.eu: could not connect to host
xtreamhosting.eu: could not connect to host
za.search.yahoo.com: did not receive HSTS header
zarooba.com: did not receive HSTS header
zenpayroll.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no]
zh.search.yahoo.com: did not receive HSTS header
zoo24.de: could not connect to host

View File

@ -8,7 +8,7 @@
/*****************************************************************************/
#include <stdint.h>
const PRTime gPreloadListExpirationTime = INT64_C(1443867487249000);
const PRTime gPreloadListExpirationTime = INT64_C(1444472299639000);
class nsSTSPreload
{
@ -39,11 +39,11 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "5apps.com", false },
{ "7183.org", true },
{ "8ack.de", true },
{ "9point6.com", true },
{ "abmahnhelfer.de", true },
{ "accounts.firefox.com", true },
{ "accounts.google.com", true },
{ "aclu.org", false },
{ "activiti.alfresco.com", false },
{ "acuica.co.uk", false },
{ "acus.gov", true },
{ "adamkostecki.de", true },
@ -512,7 +512,6 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "everhome.de", true },
{ "eveshamglass.co.uk", true },
{ "evstatus.com", true },
{ "exiahost.com", false },
{ "exon.io", true },
{ "expatads.com", true },
{ "explodie.org", true },
@ -636,7 +635,6 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "googleplex.com", true },
{ "gothamlimo.com", true },
{ "goto.google.com", true },
{ "gotowned.org", true },
{ "gotspot.com", true },
{ "gplintegratedit.com", true },
{ "gpsfix.cz", true },
@ -659,11 +657,14 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "guru-naradi.cz", true },
{ "gurusupe.com", true },
{ "guthabenkarten-billiger.de", true },
{ "gvt2.com", true },
{ "gvt3.com", true },
{ "gw2treasures.com", true },
{ "gwijaya.com", true },
{ "haber1903.com", true },
{ "hachre.de", false },
{ "hack.li", true },
{ "hackerone-user-content.com", true },
{ "hackerone.com", true },
{ "hangouts.google.com", true },
{ "hansvaneijsden.com", true },
@ -808,7 +809,6 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "jpbike.cz", true },
{ "jrc9.ca", true },
{ "julianmeyer.de", true },
{ "jwilsson.com", true },
{ "jwilsson.me", true },
{ "jwnotifier.org", true },
{ "k-dev.de", true },
@ -950,6 +950,7 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "madars.org", true },
{ "madeitwor.se", true },
{ "mafamane.com", true },
{ "maff.scot", false },
{ "magneticanvil.com", true },
{ "mahamed91.pw", true },
{ "mail.de", true },
@ -1071,7 +1072,7 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "mutantmonkey.sexy", true },
{ "mvno.io", true },
{ "mvsecurity.nl", true },
{ "mwe.st", true },
{ "mwe.st", false },
{ "my.onlime.ch", false },
{ "my.xero.com", false },
{ "myaccount.google.com", true },
@ -1337,6 +1338,7 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "ru-sprachstudio.ch", true },
{ "rubecodeberg.com", true },
{ "rubendv.be", true },
{ "rubyshop.nl", true },
{ "rudloff.pro", true },
{ "rusadmin.biz", true },
{ "ruudkoot.nl", true },
@ -1644,7 +1646,7 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "twolinepassbrewing.com", true },
{ "typingrevolution.com", true },
{ "uae-company-service.com", true },
{ "ub3rk1tten.com", true },
{ "ub3rk1tten.com", false },
{ "ubertt.org", true },
{ "ucfirst.nl", true },
{ "ukdefencejournal.org.uk", true },

View File

@ -131,7 +131,7 @@ HistoryStore.prototype = {
"SELECT visit_type type, visit_date date " +
"FROM moz_historyvisits " +
"WHERE place_id = (SELECT id FROM moz_places WHERE url = :url) " +
"ORDER BY date DESC LIMIT 10");
"ORDER BY date DESC LIMIT 20");
},
_visitCols: ["date", "type"],
@ -276,14 +276,14 @@ HistoryStore.prototype = {
if (!visit.date || typeof visit.date != "number") {
this._log.warn("Encountered record with invalid visit date: "
+ visit.date);
throw "Visit has no date!";
continue;
}
if (!visit.type || !(visit.type >= PlacesUtils.history.TRANSITION_LINK &&
visit.type <= PlacesUtils.history.TRANSITION_FRAMED_LINK)) {
this._log.warn("Encountered record with invalid visit type: "
+ visit.type);
throw "Invalid visit type!";
this._log.warn("Encountered record with invalid visit type: " +
visit.type + "; ignoring.");
continue;
}
// Dates need to be integers.
@ -294,6 +294,7 @@ HistoryStore.prototype = {
// overwritten.
continue;
}
visit.visitDate = visit.date;
visit.transitionType = visit.type;
k += 1;

View File

@ -226,7 +226,7 @@ add_test(function test_invalid_records() {
type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}
]);
_("Make sure we report records with invalid visits, gracefully handle non-integer dates.");
_("Make sure we handle records with invalid visit codes or visit dates, gracefully ignoring those visits.");
let no_date_visit_guid = Utils.makeGUID();
let no_type_visit_guid = Utils.makeGUID();
let invalid_type_visit_guid = Utils.makeGUID();
@ -235,11 +235,11 @@ add_test(function test_invalid_records() {
{id: no_date_visit_guid,
histUri: "http://no.date.visit/",
title: "Visit has no date",
visits: [{date: TIMESTAMP3}]},
visits: [{type: Ci.nsINavHistoryService.TRANSITION_EMBED}]},
{id: no_type_visit_guid,
histUri: "http://no.type.visit/",
title: "Visit has no type",
visits: [{type: Ci.nsINavHistoryService.TRANSITION_EMBED}]},
visits: [{date: TIMESTAMP3}]},
{id: invalid_type_visit_guid,
histUri: "http://invalid.type.visit/",
title: "Visit has invalid type",
@ -251,14 +251,7 @@ add_test(function test_invalid_records() {
visits: [{date: 1234.567,
type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}
]);
do_check_eq(failed.length, 3);
failed.sort();
let expected = [no_date_visit_guid,
no_type_visit_guid,
invalid_type_visit_guid].sort();
for (let i = 0; i < expected.length; i++) {
do_check_eq(failed[i], expected[i]);
}
do_check_eq(failed.length, 0);
_("Make sure we handle records with javascript: URLs gracefully.");
applyEnsureNoFailures([

View File

@ -71,7 +71,7 @@ var HistoryEntry = {
"SELECT id " +
"FROM moz_places " +
"WHERE url = :url) " +
"ORDER BY date DESC LIMIT 10");
"ORDER BY date DESC LIMIT 20");
this.__defineGetter__("_visitStm", () => stm);
return stm;
},

View File

@ -9,6 +9,7 @@ if (Cc === undefined) {
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
@ -74,10 +75,18 @@ function installAddon(url) {
resolve(addon);
},
onDownloadCancelled: function(install) {
reject("Download cancelled: " + install.error);
},
onDownloadFailed: function(install) {
reject("Download failed: " + install.error);
},
onInstallCancelled: function(install) {
reject("Install cancelled: " + install.error);
},
onInstallFailed: function(install) {
reject("Install failed: " + install.error);
}
@ -96,10 +105,11 @@ function uninstallAddon(oldAddon) {
if (addon.id != oldAddon.id)
return;
dump("TEST-INFO | jetpack-addon-harness.js | Uninstalled test add-on " + addon.id + "\n");
// Some add-ons do async work on uninstall, we must wait for that to
// complete
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.init(resolve, 500, timer.TYPE_ONE_SHOT);
setTimeout(resolve, 500);
}
});
@ -119,13 +129,13 @@ function waitForResults() {
}
// Runs tests for the add-on available at URL.
let testAddon = Task.async(function*({ url, expected }) {
let testAddon = Task.async(function*({ url }) {
dump("TEST-INFO | jetpack-addon-harness.js | Installing test add-on " + realPath(url) + "\n");
let addon = yield installAddon(url);
let results = yield waitForResults();
dump("TEST-INFO | jetpack-addon-harness.js | Uninstalling test add-on " + realPath(url) + "\n");
dump("TEST-INFO | jetpack-addon-harness.js | Uninstalling test add-on " + addon.id + "\n");
yield uninstallAddon(addon);
dump("TEST-INFO | jetpack-addon-harness.js | Testing add-on " + realPath(url) + " is complete\n");
@ -197,6 +207,8 @@ function testInit() {
}
if (config.closeWhenDone) {
dump("TEST-INFO | jetpack-addon-harness.js | Shutting down.\n");
const appStartup = Cc['@mozilla.org/toolkit/app-startup;1'].
getService(Ci.nsIAppStartup);
appStartup.quit(appStartup.eAttemptQuit);
@ -208,6 +220,7 @@ function testInit() {
return finish();
let filename = fileNames.shift();
dump("TEST-INFO | jetpack-addon-harness.js | Starting test add-on " + realPath(filename.url) + "\n");
testAddon(filename).then(results => {
passed += results.passed;
failed += results.failed;

View File

@ -51,15 +51,6 @@ const PING_FORMAT_VERSION = 4;
const TELEMETRY_DELAY = 60000;
// Delay before initializing telemetry if we're testing (ms)
const TELEMETRY_TEST_DELAY = 100;
// Timeout after which we consider a ping submission failed.
const PING_SUBMIT_TIMEOUT_MS = 2 * 60 * 1000;
// For midnight fuzzing we want to affect pings around midnight with this tolerance.
const MIDNIGHT_TOLERANCE_FUZZ_MS = 5 * 60 * 1000;
// We try to spread "midnight" pings out over this interval.
const MIDNIGHT_FUZZING_INTERVAL_MS = 60 * 60 * 1000;
// We delay sending "midnight" pings on this client by this interval.
const MIDNIGHT_FUZZING_DELAY_MS = Math.random() * MIDNIGHT_FUZZING_INTERVAL_MS;
// Ping types.
const PING_TYPE_MAIN = "main";
@ -89,6 +80,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "TelemetryArchive",
"resource://gre/modules/TelemetryArchive.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySession",
"resource://gre/modules/TelemetrySession.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySend",
"resource://gre/modules/TelemetrySend.jsm");
/**
* Setup Telemetry logging. This function also gets called when loggin related
@ -123,34 +116,11 @@ function configureLogging() {
}
}
function generateUUID() {
let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
// strip {}
return str.substring(1, str.length - 1);
}
/**
* Determine if the ping has new ping format or a legacy one.
*/
function isNewPingFormat(aPing) {
return ("id" in aPing) && ("application" in aPing) &&
("version" in aPing) && (aPing.version >= 2);
}
function tomorrow(date) {
let d = new Date(date);
d.setDate(d.getDate() + 1);
return d;
}
/**
* This is a policy object used to override behavior for testing.
*/
let Policy = {
now: () => new Date(),
midnightPingFuzzingDelay: () => MIDNIGHT_FUZZING_DELAY_MS,
setPingSendTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
clearPingSendTimeout: (id) => clearTimeout(id),
}
this.EXPORTED_SYMBOLS = ["TelemetryController"];
@ -174,6 +144,8 @@ this.TelemetryController = Object.freeze({
reset: function() {
Impl._clientID = null;
TelemetryStorage.reset();
TelemetrySend.reset();
return this.setup();
},
/**
@ -190,13 +162,6 @@ this.TelemetryController = Object.freeze({
return Impl.observe(aSubject, aTopic, aData);
},
/**
* Sets a server to send pings to.
*/
setServer: function(aServer) {
return Impl.setServer(aServer);
},
/**
* Submit ping payloads to Telemetry. This will assemble a complete ping, adding
* environment data, client id and some general info.
@ -318,15 +283,6 @@ this.TelemetryController = Object.freeze({
return Impl.savePing(aType, aPayload, aFilePath, options);
},
/**
* Send the persisted pings to the server.
*
* @return Promise A promise that is resolved when all pings finished sending or failed.
*/
sendPersistedPings: function() {
return Impl.sendPersistedPings();
},
/**
* The client id send with the telemetry ping.
*
@ -374,8 +330,6 @@ let Impl = {
_delayedInitTask: null,
// The deferred promise resolved when the initialization task completes.
_delayedInitTaskDeferred: null,
// Timer for scheduled ping sends.
_pingSendTimer: null,
// The session recorder, shared with FHR and the Data Reporting Service.
_sessionRecorder: null,
@ -388,9 +342,6 @@ let Impl = {
// This is true when running in the test infrastructure.
_testMode: false,
// This tracks all pending ping requests to the server.
_pendingPingRequests: new Map(),
get _log() {
if (!this._logger) {
this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
@ -446,8 +397,7 @@ let Impl = {
* @returns Promise<Object> A promise that resolves when the ping is completely assembled.
*/
assemblePing: function assemblePing(aType, aPayload, aOptions = {}) {
this._log.trace("assemblePing - Type " + aType + ", Server " + this._server +
", aOptions " + JSON.stringify(aOptions));
this._log.trace("assemblePing - Type " + aType + ", aOptions " + JSON.stringify(aOptions));
// Clone the payload data so we don't race against unexpected changes in subobjects that are
// still referenced by other code.
@ -457,7 +407,7 @@ let Impl = {
// Fill the common ping fields.
let pingData = {
type: aType,
id: generateUUID(),
id: Utils.generateUUID(),
creationDate: (Policy.now()).toISOString(),
version: PING_FORMAT_VERSION,
application: this._getApplicationSection(),
@ -475,26 +425,6 @@ let Impl = {
return pingData;
},
popPayloads: function popPayloads() {
this._log.trace("popPayloads");
function payloadIter() {
let iterator = TelemetryStorage.popPendingPings();
for (let data of iterator) {
yield data;
}
}
let payloadIterWithThis = payloadIter.bind(this);
return { __iterator__: payloadIterWithThis };
},
/**
* Only used in tests.
*/
setServer: function (aServer) {
this._server = aServer;
},
/**
* Track any pending ping send and save tasks through the promise passed here.
* This is needed to block shutdown on any outstanding ping activity.
@ -503,43 +433,6 @@ let Impl = {
this._connectionsBarrier.client.addBlocker("Waiting for ping task", aPromise);
},
/**
* This helper calculates the next time that we can send pings at.
* Currently this mostly redistributes ping sends around midnight to avoid submission
* spikes around local midnight for daily pings.
*
* @param now Date The current time.
* @return Number The next time (ms from UNIX epoch) when we can send pings.
*/
_getNextPingSendTime: function(now) {
// 1. First we check if the time is between 11pm and 1am. If it's not, we send
// immediately.
// 2. If we confirmed the time is indeed between 11pm and 1am in step 1, we
// then check if the time is also 11:55pm or later. If it's not, we send
// immediately.
// 3. Finally, if the time is between 11:55pm and 1am, we disallow sending
// before (midnight + fuzzing delay), which is a random time between 12am-1am
// (decided at startup).
const midnight = Utils.getNearestMidnight(now, MIDNIGHT_FUZZING_INTERVAL_MS);
// Don't delay ping if we are not close to midnight.
if (!midnight) {
return now.getTime();
}
// Delay ping send if we are within the midnight fuzzing range.
// This is from: |midnight - MIDNIGHT_TOLERANCE_FUZZ_MS|
// to: |midnight + MIDNIGHT_FUZZING_INTERVAL_MS|
const midnightRangeStart = midnight.getTime() - MIDNIGHT_TOLERANCE_FUZZ_MS;
if (now.getTime() >= midnightRangeStart) {
// We spread those ping sends out between |midnight| and |midnight + midnightPingFuzzingDelay|.
return midnight.getTime() + Policy.midnightPingFuzzingDelay();
}
return now.getTime();
},
/**
* Submit ping payloads to Telemetry. This will assemble a complete ping, adding
* environment data, client id and some general info.
@ -557,8 +450,7 @@ let Impl = {
* @returns {Promise} A promise that is resolved with the ping id once the ping is stored or sent.
*/
submitExternalPing: function send(aType, aPayload, aOptions) {
this._log.trace("submitExternalPing - type: " + aType + ", server: " + this._server +
", aOptions: " + JSON.stringify(aOptions));
this._log.trace("submitExternalPing - type: " + aType + ", aOptions: " + JSON.stringify(aOptions));
// Enforce the type string to only contain sane characters.
const typeUuid = /^[a-z0-9][a-z0-9-]+[a-z0-9]$/i;
@ -577,67 +469,13 @@ let Impl = {
.catch(e => this._log.error("submitExternalPing - Failed to archive ping " + pingData.id, e));
let p = [ archivePromise ];
// Check if we can send pings now.
const now = Policy.now();
const nextPingSendTime = this._getNextPingSendTime(now);
const throttled = (nextPingSendTime > now.getTime());
// We can't send pings now, schedule a later send.
if (throttled) {
this._log.trace("submitExternalPing - throttled, delaying ping send to " + new Date(nextPingSendTime));
this._reschedulePingSendTimer(nextPingSendTime);
}
if (!this._initialized || throttled) {
// We can't send because we are still initializing or throttled, add this to the pending pings.
this._log.trace("submitExternalPing - ping is pending, initialized: " + this._initialized +
", throttled: " + throttled);
p.push(TelemetryStorage.addPendingPing(pingData));
} else {
// Try to send the ping, persist it if sending it fails.
this._log.trace("submitExternalPing - already initialized, ping will be sent");
p.push(this.doPing(pingData, false)
.catch(() => TelemetryStorage.savePing(pingData, true)));
p.push(this.sendPersistedPings());
}
p.push(TelemetrySend.submitPing(pingData));
let promise = Promise.all(p);
this._trackPendingPingTask(promise);
return promise.then(() => pingData.id);
},
/**
* Send the persisted pings to the server.
*
* @return Promise A promise that is resolved when all pings finished sending or failed.
*/
sendPersistedPings: function sendPersistedPings() {
this._log.trace("sendPersistedPings - Can send: " + this._canSend());
if (!this._canSend()) {
this._log.trace("sendPersistedPings - Telemetry is not allowed to send pings.");
return Promise.resolve();
}
// Check if we can send pings now - otherwise schedule a later send.
const now = Policy.now();
const nextPingSendTime = this._getNextPingSendTime(now);
if (nextPingSendTime > now.getTime()) {
this._log.trace("sendPersistedPings - delaying ping send to " + new Date(nextPingSendTime));
this._reschedulePingSendTimer(nextPingSendTime);
return Promise.resolve();
}
// We can send now.
let pingsIterator = Iterator(this.popPayloads());
let p = [for (data of pingsIterator) this.doPing(data, true).catch((e) => {
this._log.error("sendPersistedPings - doPing rejected", e);
})];
let promise = Promise.all(p);
this._trackPendingPingTask(promise);
return promise;
},
/**
* Save a ping to disk.
*
@ -655,12 +493,11 @@ let Impl = {
* disk.
*/
addPendingPing: function addPendingPing(aType, aPayload, aOptions) {
this._log.trace("addPendingPing - Type " + aType + ", Server " + this._server +
", aOptions " + JSON.stringify(aOptions));
this._log.trace("addPendingPing - Type " + aType + ", aOptions " + JSON.stringify(aOptions));
let pingData = this.assemblePing(aType, aPayload, aOptions);
let savePromise = TelemetryStorage.savePing(pingData, aOptions.overwrite);
let savePromise = TelemetryStorage.savePendingPing(pingData);
let archivePromise = TelemetryArchive.promiseArchivePing(pingData).catch(e => {
this._log.error("addPendingPing - Failed to archive ping " + pingData.id, e);
});
@ -692,8 +529,8 @@ let Impl = {
* disk.
*/
savePing: function savePing(aType, aPayload, aFilePath, aOptions) {
this._log.trace("savePing - Type " + aType + ", Server " + this._server +
", File Path " + aFilePath + ", aOptions " + JSON.stringify(aOptions));
this._log.trace("savePing - Type " + aType + ", File Path " + aFilePath +
", aOptions " + JSON.stringify(aOptions));
let pingData = this.assemblePing(aType, aPayload, aOptions);
return TelemetryStorage.savePingToFile(pingData, aFilePath, aOptions.overwrite)
.then(() => pingData.id);
@ -738,182 +575,6 @@ let Impl = {
return TelemetryStorage.removeAbortedSessionPing();
},
onPingRequestFinished: function(success, startTime, ping, isPersisted) {
this._log.trace("onPingRequestFinished - success: " + success + ", persisted: " + isPersisted);
Telemetry.getHistogramById("TELEMETRY_SEND").add(new Date() - startTime);
let hping = Telemetry.getHistogramById("TELEMETRY_PING");
let hsuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS");
hsuccess.add(success);
hping.add(new Date() - startTime);
if (success && isPersisted) {
return TelemetryStorage.cleanupPingFile(ping);
} else {
return Promise.resolve();
}
},
submissionPath: function submissionPath(ping) {
// The new ping format contains an "application" section, the old one doesn't.
let pathComponents;
if (isNewPingFormat(ping)) {
// We insert the Ping id in the URL to simplify server handling of duplicated
// pings.
let app = ping.application;
pathComponents = [
ping.id, ping.type, app.name, app.version, app.channel, app.buildId
];
} else {
// This is a ping in the old format.
if (!("slug" in ping)) {
// That's odd, we don't have a slug. Generate one so that TelemetryStorage.jsm works.
ping.slug = generateUUID();
}
// Do we have enough info to build a submission URL?
let payload = ("payload" in ping) ? ping.payload : null;
if (payload && ("info" in payload)) {
let info = ping.payload.info;
pathComponents = [ ping.slug, info.reason, info.appName, info.appVersion,
info.appUpdateChannel, info.appBuildID ];
} else {
// Only use the UUID as the slug.
pathComponents = [ ping.slug ];
}
}
let slug = pathComponents.join("/");
return "/submit/telemetry/" + slug;
},
doPing: function doPing(ping, isPersisted) {
if (!this._canSend()) {
// We can't send the pings to the server, so don't try to.
this._log.trace("doPing - Sending is disabled.");
return Promise.resolve();
}
this._log.trace("doPing - Server " + this._server + ", Persisted " + isPersisted);
const isNewPing = isNewPingFormat(ping);
const version = isNewPing ? PING_FORMAT_VERSION : 1;
const url = this._server + this.submissionPath(ping) + "?v=" + version;
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
request.mozBackgroundRequest = true;
request.timeout = PING_SUBMIT_TIMEOUT_MS;
request.open("POST", url, true);
request.overrideMimeType("text/plain");
request.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
this._pendingPingRequests.set(url, request);
let startTime = new Date();
let deferred = PromiseUtils.defer();
let onRequestFinished = (success, event) => {
let onCompletion = () => {
if (success) {
deferred.resolve();
} else {
deferred.reject(event);
}
};
this._pendingPingRequests.delete(url);
this.onPingRequestFinished(success, startTime, ping, isPersisted)
.then(() => onCompletion(),
(error) => {
this._log.error("doPing - request success: " + success + ", error" + error);
onCompletion();
});
};
let errorhandler = (event) => {
this._log.error("doPing - error making request to " + url + ": " + event.type);
onRequestFinished(false, event);
};
request.onerror = errorhandler;
request.ontimeout = errorhandler;
request.onabort = errorhandler;
request.onload = (event) => {
let status = request.status;
let statusClass = status - (status % 100);
let success = false;
if (statusClass === 200) {
// We can treat all 2XX as success.
this._log.info("doPing - successfully loaded, status: " + status);
success = true;
} else if (statusClass === 400) {
// 4XX means that something with the request was broken.
this._log.error("doPing - error submitting to " + url + ", status: " + status
+ " - ping request broken?");
// TODO: we should handle this better, but for now we should avoid resubmitting
// broken requests by pretending success.
success = true;
} else if (statusClass === 500) {
// 5XX means there was a server-side error and we should try again later.
this._log.error("doPing - error submitting to " + url + ", status: " + status
+ " - server error, should retry later");
} else {
// We received an unexpected status codes.
this._log.error("doPing - error submitting to " + url + ", status: " + status
+ ", type: " + event.type);
}
onRequestFinished(success, event);
};
// If that's a legacy ping format, just send its payload.
let networkPayload = isNewPing ? ping : ping.payload;
request.setRequestHeader("Content-Encoding", "gzip");
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
startTime = new Date();
let utf8Payload = converter.ConvertFromUnicode(JSON.stringify(networkPayload));
utf8Payload += converter.Finish();
Telemetry.getHistogramById("TELEMETRY_STRINGIFY").add(new Date() - startTime);
let payloadStream = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
startTime = new Date();
payloadStream.data = this.gzipCompressString(utf8Payload);
Telemetry.getHistogramById("TELEMETRY_COMPRESS").add(new Date() - startTime);
startTime = new Date();
request.send(payloadStream);
return deferred.promise;
},
gzipCompressString: function gzipCompressString(string) {
let observer = {
buffer: "",
onStreamComplete: function(loader, context, status, length, result) {
this.buffer = String.fromCharCode.apply(this, result);
}
};
let scs = Cc["@mozilla.org/streamConverters;1"]
.getService(Ci.nsIStreamConverterService);
let listener = Cc["@mozilla.org/network/stream-loader;1"]
.createInstance(Ci.nsIStreamLoader);
listener.init(observer);
let converter = scs.asyncConvertData("uncompressed", "gzip",
listener, null);
let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
stringStream.data = string;
converter.onStartRequest(null, null);
converter.onDataAvailable(null, null, stringStream, 0, string.length);
converter.onStopRequest(null, null, null);
return observer.buffer;
},
/**
* Perform telemetry initialization for either chrome or content process.
* @return {Boolean} True if Telemetry is allowed to record at least base (FHR) data,
@ -935,7 +596,6 @@ let Impl = {
}
#endif
this._server = Preferences.get(PREF_SERVER, undefined);
if (!enabled || !Telemetry.canRecordBase) {
// Turn off extended telemetry recording if disabled by preferences or if base/telemetry
// telemetry recording is off.
@ -947,7 +607,8 @@ let Impl = {
},
/**
* Initializes telemetry within a timer. If there is no PREF_SERVER set, don't turn on telemetry.
* This triggers basic telemetry initialization and schedules a full initialized for later
* for performance reasons.
*
* This delayed initialization means TelemetryController init can be in the following states:
* 1) setupTelemetry was never called
@ -998,21 +659,10 @@ let Impl = {
this._delayedInitTaskDeferred = Promise.defer();
this._delayedInitTask = new DeferredTask(function* () {
try {
// TODO: This should probably happen after all the delayed init here.
this._initialized = true;
// If any pings were submitted before the delayed init finished
// we will send them now.
yield this.sendPersistedPings();
// Load pending pings from disk.
yield TelemetryStorage.loadSavedPings();
// If we have any overdue pings lying around, we'll be aggressive
// and try to send them all off ASAP.
if (TelemetryStorage.pingsOverdue > 0) {
this._log.trace("setupChromeProcess - Sending " + TelemetryStorage.pingsOverdue +
" overdue pings now.");
yield this.sendPersistedPings();
}
yield TelemetrySend.setup(this._testMode);
// Load the ClientID and update the cache.
this._clientID = yield ClientID.getClientID();
@ -1047,24 +697,13 @@ let Impl = {
Preferences.ignore(PREF_BRANCH_LOG, configureLogging);
// Abort any pending ping XHRs.
for (let [url, request] of this._pendingPingRequests) {
this._log.trace("_cleanupOnShutdown - aborting ping request for " + url);
try {
request.abort();
} catch (e) {
this._log.error("_cleanupOnShutdown - failed to abort request to " + url, e);
}
}
this._pendingPingRequests.clear();
// Now do an orderly shutdown.
try {
// First wait for clients processing shutdown.
yield this._shutdownBarrier.wait();
// Then clear scheduled ping sends...
this._clearPingSendTimer();
// Stop any ping sending.
yield TelemetrySend.shutdown();
// ... and wait for any outstanding async ping activity.
yield this._connectionsBarrier.wait();
@ -1136,29 +775,6 @@ let Impl = {
return this._clientID;
},
/**
* Check if pings can be sent to the server. If FHR is not allowed to upload,
* pings are not sent to the server (Telemetry is a sub-feature of FHR).
* If unified telemetry is off, don't send pings if Telemetry is disabled.
*
* @return {Boolean} True if pings can be send to the servers, false otherwise.
*/
_canSend: function() {
// We only send pings from official builds, but allow overriding this for tests.
if (!Telemetry.isOfficialTelemetry && !this._testMode) {
return false;
}
// With unified Telemetry, the FHR upload setting controls whether we can send pings.
// The Telemetry pref enables sending extended data sets instead.
if (IS_UNIFIED_TELEMETRY) {
return Preferences.get(PREF_FHR_UPLOAD_ENABLED, false);
}
// Without unified Telemetry, the Telemetry enabled pref controls ping sending.
return Preferences.get(PREF_ENABLED, false);
},
/**
* Get an object describing the current state of this module for AsyncShutdown diagnostics.
*/
@ -1172,19 +788,6 @@ let Impl = {
};
},
_reschedulePingSendTimer: function(timestamp) {
this._clearPingSendTimer();
const interval = timestamp - Policy.now();
this._pingSendTimer = Policy.setPingSendTimeout(() => this.sendPersistedPings(), interval);
},
_clearPingSendTimer: function() {
if (this._pingSendTimer) {
Policy.clearPingSendTimeout(this._pingSendTimer);
this._pingSendTimer = null;
}
},
/**
* Allows waiting for TelemetryControllers delayed initialization to complete.
* This will complete before TelemetryController is shutting down.

View File

@ -0,0 +1,710 @@
/* 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/. */
/*
* This module is responsible for uploading pings to the server and persisting
* pings that can't be send now.
* Those pending pings are persisted on disk and sent at the next opportunity,
* newest first.
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"TelemetrySend",
];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/Log.jsm", this);
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
"resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStorage",
"resource://gre/modules/TelemetryStorage.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
"@mozilla.org/base/telemetry;1",
"nsITelemetry");
const Utils = TelemetryUtils;
const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetrySend::";
const PREF_BRANCH = "toolkit.telemetry.";
const PREF_SERVER = PREF_BRANCH + "server";
const PREF_UNIFIED = PREF_BRANCH + "unified";
const PREF_TELEMETRY_ENABLED = PREF_BRANCH + "enabled";
const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
const TOPIC_IDLE_DAILY = "idle-daily";
const TOPIC_QUIT_APPLICATION = "quit-application";
// Whether the FHR/Telemetry unification features are enabled.
// Changing this pref requires a restart.
const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_UNIFIED, false);
const PING_FORMAT_VERSION = 4;
// For midnight fuzzing we want to affect pings around midnight with this tolerance.
const MIDNIGHT_TOLERANCE_FUZZ_MS = 5 * 60 * 1000;
// We try to spread "midnight" pings out over this interval.
const MIDNIGHT_FUZZING_INTERVAL_MS = 60 * 60 * 1000;
// We delay sending "midnight" pings on this client by this interval.
const MIDNIGHT_FUZZING_DELAY_MS = Math.random() * MIDNIGHT_FUZZING_INTERVAL_MS;
// Timeout after which we consider a ping submission failed.
const PING_SUBMIT_TIMEOUT_MS = 2 * 60 * 1000;
// Files that have been lying around for longer than MAX_PING_FILE_AGE are
// deleted without being loaded.
const MAX_PING_FILE_AGE = 14 * 24 * 60 * 60 * 1000; // 2 weeks
// Files that are older than OVERDUE_PING_FILE_AGE, but younger than
// MAX_PING_FILE_AGE indicate that we need to send all of our pings ASAP.
const OVERDUE_PING_FILE_AGE = 7 * 24 * 60 * 60 * 1000; // 1 week
// Maximum number of pings to save.
const MAX_LRU_PINGS = 50;
/**
* This is a policy object used to override behavior within this module.
* Tests override properties on this object to allow for control of behavior
* that would otherwise be very hard to cover.
*/
let Policy = {
now: () => new Date(),
midnightPingFuzzingDelay: () => MIDNIGHT_FUZZING_DELAY_MS,
setPingSendTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
clearPingSendTimeout: (id) => clearTimeout(id),
};
/**
* Determine if the ping has the new v4 ping format or the legacy v2 one or earlier.
*/
function isV4PingFormat(aPing) {
return ("id" in aPing) && ("application" in aPing) &&
("version" in aPing) && (aPing.version >= 2);
}
function tomorrow(date) {
let d = new Date(date);
d.setDate(d.getDate() + 1);
return d;
}
/**
* @return {String} This returns a string with the gzip compressed data.
*/
function gzipCompressString(string) {
let observer = {
buffer: "",
onStreamComplete: function(loader, context, status, length, result) {
this.buffer = String.fromCharCode.apply(this, result);
}
};
let scs = Cc["@mozilla.org/streamConverters;1"]
.getService(Ci.nsIStreamConverterService);
let listener = Cc["@mozilla.org/network/stream-loader;1"]
.createInstance(Ci.nsIStreamLoader);
listener.init(observer);
let converter = scs.asyncConvertData("uncompressed", "gzip",
listener, null);
let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
stringStream.data = string;
converter.onStartRequest(null, null);
converter.onDataAvailable(null, null, stringStream, 0, string.length);
converter.onStopRequest(null, null, null);
return observer.buffer;
}
this.TelemetrySend = {
/**
* Maximum age in ms of a pending ping file before it gets evicted.
*/
get MAX_PING_FILE_AGE() {
return MAX_PING_FILE_AGE;
},
/**
* Age in ms of a pending ping to be considered overdue.
*/
get OVERDUE_PING_FILE_AGE() {
return OVERDUE_PING_FILE_AGE;
},
/**
* The maximum number of pending pings we keep in the backlog.
*/
get MAX_LRU_PINGS() {
return MAX_LRU_PINGS;
},
/**
* Initializes this module.
*
* @param {Boolean} testing Whether this is run in a test. This changes some behavior
* to enable proper testing.
* @return {Promise} Resolved when setup is finished.
*/
setup: function(testing = false) {
return TelemetrySendImpl.setup(testing);
},
/**
* Shutdown this module - this will cancel any pending ping tasks and wait for
* outstanding async activity like network and disk I/O.
*
* @return {Promise} Promise that is resolved when shutdown is finished.
*/
shutdown: function() {
return TelemetrySendImpl.shutdown();
},
/**
* Submit a ping for sending. This will:
* - send the ping right away if possible or
* - save the ping to disk and send it at the next opportunity
*
* @param {Object} ping The ping data to send, must be serializable to JSON.
* @return {Promise} A promise that is resolved when the ping is sent or saved.
*/
submitPing: function(ping) {
return TelemetrySendImpl.submitPing(ping);
},
/**
* Count of pending pings that were discarded at startup due to being too old.
*/
get discardedPingsCount() {
return TelemetrySendImpl.discardedPingsCount;
},
/**
* Count of pending pings that were found to be overdue at startup.
*/
get overduePingsCount() {
return TelemetrySendImpl.overduePingsCount;
},
/**
* Only used in tests. Used to reset the module data to emulate a restart.
*/
reset: function() {
return TelemetrySendImpl.reset();
},
/**
* Only used in tests.
*/
setServer: function(server) {
return TelemetrySendImpl.setServer(server);
},
};
let TelemetrySendImpl = {
_sendingEnabled: false,
_logger: null,
// Timer for scheduled ping sends.
_pingSendTimer: null,
// This tracks all pending ping requests to the server.
_pendingPingRequests: new Map(),
// This is a private barrier blocked by pending async ping activity (sending & saving).
_connectionsBarrier: new AsyncShutdown.Barrier("TelemetrySend: Waiting for pending ping activity"),
// This is true when running in the test infrastructure.
_testMode: false,
// Count of pending pings we discarded for age on startup.
_discardedPingsCount: 0,
// Count of pending pings we evicted for being over the limit on startup.
_evictedPingsCount: 0,
// Count of pending pings that were overdue.
_overduePingCount: 0,
OBSERVER_TOPICS: [
TOPIC_IDLE_DAILY,
],
get _log() {
if (!this._logger) {
this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
}
return this._logger;
},
get discardedPingsCount() {
return this._discardedPingsCount;
},
get overduePingsCount() {
return this._overduePingCount;
},
setup: Task.async(function*(testing) {
this._log.trace("setup");
this._testMode = testing;
this._sendingEnabled = true;
this._discardedPingsCount = 0;
this._evictedPingsCount = 0;
Services.obs.addObserver(this, TOPIC_IDLE_DAILY, false);
this._server = Preferences.get(PREF_SERVER, undefined);
// If any pings were submitted before the delayed init finished
// we will send them now.
yield this._sendPersistedPings();
// Check the pending pings on disk now.
yield this._checkPendingPings();
}),
_checkPendingPings: Task.async(function*() {
// Scan the pending pings - that gives us a list sorted by last modified, descending.
let infos = yield TelemetryStorage.loadPendingPingList();
this._log.info("_checkPendingPings - pending ping count: " + infos.length);
if (infos.length == 0) {
this._log.trace("_checkPendingPings - no pending pings");
return;
}
// Remove old pings that we haven't been able to send yet.
const now = new Date();
const tooOld = (info) => (now.getTime() - info.lastModificationDate) > MAX_PING_FILE_AGE;
const oldPings = infos.filter((info) => tooOld(info));
infos = infos.filter((info) => !tooOld(info));
this._log.info("_checkPendingPings - clearing out " + oldPings.length + " old pings");
for (let info of oldPings) {
try {
yield TelemetryStorage.removePendingPing(info.id);
++this._discardedPingsCount;
} catch(ex) {
this._log.error("_checkPendingPings - failed to remove old ping", ex);
}
}
// Keep only the last MAX_LRU_PINGS entries to avoid that the backlog overgrows.
const shouldEvict = infos.splice(MAX_LRU_PINGS, infos.length);
let evictedCount = 0;
this._log.info("_checkPendingPings - evicting " + shouldEvict.length + " pings to " +
"avoid overgrowing the backlog");
for (let info of shouldEvict) {
try {
yield TelemetryStorage.removePendingPing(info.id);
++this._evictedPingsCount;
} catch(ex) {
this._log.error("_checkPendingPings - failed to evict ping", ex);
}
}
Services.telemetry.getHistogramById('TELEMETRY_FILES_EVICTED')
.add(evictedCount);
// Check for overdue pings.
const overduePings = infos.filter((info) =>
(now.getTime() - info.lastModificationDate) > OVERDUE_PING_FILE_AGE);
this._overduePingCount = overduePings.length;
if (overduePings.length > 0) {
this._log.trace("_checkForOverduePings - Have " + overduePings.length +
" overdue pending pings, sending " + infos.length +
" pings now.");
yield this._sendPersistedPings();
}
}),
shutdown: Task.async(function*() {
for (let topic of this.OBSERVER_TOPICS) {
Services.obs.removeObserver(this, topic);
}
// We can't send anymore now.
this._sendingEnabled = false;
// Clear scheduled ping sends.
this._clearPingSendTimer();
// Cancel any outgoing requests.
yield this._cancelOutgoingRequests();
// ... and wait for any outstanding async ping activity.
yield this._connectionsBarrier.wait();
}),
reset: function() {
this._log.trace("reset");
this._overduePingCount = 0;
this._discardedPingsCount = 0;
this._evictedPingsCount = 0;
const histograms = [
"TELEMETRY_SUCCESS",
"TELEMETRY_FILES_EVICTED",
"TELEMETRY_SEND",
"TELEMETRY_PING",
];
histograms.forEach(h => Telemetry.getHistogramById(h).clear());
},
observe: function(subject, topic, data) {
switch(topic) {
case TOPIC_IDLE_DAILY:
this._sendPersistedPings();
break;
}
},
submitPing: function(ping) {
if (!this._canSend()) {
this._log.trace("submitPing - Telemetry is not allowed to send pings.");
return Promise.resolve();
}
// Check if we can send pings now.
const now = Policy.now();
const nextPingSendTime = this._getNextPingSendTime(now);
const throttled = (nextPingSendTime > now.getTime());
// We can't send pings now, schedule a later send.
if (throttled) {
this._log.trace("submitPing - throttled, delaying ping send to " + new Date(nextPingSendTime));
this._reschedulePingSendTimer(nextPingSendTime);
}
if (!this._sendingEnabled || throttled) {
// Sending is disabled or throttled, add this to the pending pings.
this._log.trace("submitPing - ping is pending, sendingEnabled: " + this._sendingEnabled +
", throttled: " + throttled);
return TelemetryStorage.savePendingPing(ping);
}
// Try to send the ping, persist it if sending it fails.
this._log.trace("submitPing - already initialized, ping will be sent");
let ps = [];
ps.push(this._doPing(ping, ping.id, false)
.catch((ex) => {
this._log.info("submitPing - ping not sent, saving to disk", ex);
TelemetryStorage.savePendingPing(ping);
}));
ps.push(this._sendPersistedPings());
return Promise.all([for (p of ps) p.catch((ex) => {
this._log.error("submitPing - ping activity had an error", ex);
})]);
},
/**
* Only used in tests.
*/
setServer: function (server) {
this._log.trace("setServer", server);
this._server = server;
},
_cancelOutgoingRequests: function() {
// Abort any pending ping XHRs.
for (let [url, request] of this._pendingPingRequests) {
this._log.trace("_cancelOutgoingRequests - aborting ping request for " + url);
try {
request.abort();
} catch (e) {
this._log.error("_cancelOutgoingRequests - failed to abort request to " + url, e);
}
}
this._pendingPingRequests.clear();
},
/**
* This helper calculates the next time that we can send pings at.
* Currently this mostly redistributes ping sends around midnight to avoid submission
* spikes around local midnight for daily pings.
*
* @param now Date The current time.
* @return Number The next time (ms from UNIX epoch) when we can send pings.
*/
_getNextPingSendTime: function(now) {
// 1. First we check if the time is between 11pm and 1am. If it's not, we send
// immediately.
// 2. If we confirmed the time is indeed between 11pm and 1am in step 1, we
// then check if the time is also 11:55pm or later. If it's not, we send
// immediately.
// 3. Finally, if the time is between 11:55pm and 1am, we disallow sending
// before (midnight + fuzzing delay), which is a random time between 12am-1am
// (decided at startup).
const midnightDate = Utils.getNearestMidnight(now, MIDNIGHT_FUZZING_INTERVAL_MS);
// Don't delay ping if we are not close to midnight.
if (!midnightDate) {
return now.getTime();
}
// Delay ping send if we are within the midnight fuzzing range.
// This is from: |midnight - MIDNIGHT_TOLERANCE_FUZZ_MS|
// to: |midnight + MIDNIGHT_FUZZING_INTERVAL_MS|
const midnightRangeStart = midnightDate.getTime() - MIDNIGHT_TOLERANCE_FUZZ_MS;
if (now.getTime() >= midnightRangeStart) {
// We spread those ping sends out between |midnight| and |midnight + midnightPingFuzzingDelay|.
return midnightDate.getTime() + Policy.midnightPingFuzzingDelay();
}
return now.getTime();
},
/**
* Send the persisted pings to the server.
*
* @return Promise A promise that is resolved when all pings finished sending or failed.
*/
_sendPersistedPings: Task.async(function*() {
this._log.trace("_sendPersistedPings - Can send: " + this._canSend());
if (TelemetryStorage.pendingPingCount < 1) {
this._log.trace("_sendPersistedPings - no pings to send");
return Promise.resolve();
}
if (!this._canSend()) {
this._log.trace("_sendPersistedPings - Telemetry is not allowed to send pings.");
return Promise.resolve();
}
// Check if we can send pings now - otherwise schedule a later send.
const now = Policy.now();
const nextPingSendTime = this._getNextPingSendTime(now);
if (nextPingSendTime > now.getTime()) {
this._log.trace("_sendPersistedPings - delaying ping send to " + new Date(nextPingSendTime));
this._reschedulePingSendTimer(nextPingSendTime);
return Promise.resolve();
}
// We can send now.
const pendingPings = TelemetryStorage.getPendingPingList();
this._log.trace("_sendPersistedPings - sending " + pendingPings.length + " pings");
let pingSendPromises = [];
for (let ping of pendingPings) {
let p = ping;
pingSendPromises.push(
TelemetryStorage.loadPendingPing(p.id)
.then((data) => this._doPing(data, p.id, true)
.catch(e => this._log.error("_sendPersistedPings - _doPing rejected", e))));
}
let promise = Promise.all(pingSendPromises);
this._trackPendingPingTask(promise);
yield promise;
}),
_onPingRequestFinished: function(success, startTime, id, isPersisted) {
this._log.trace("_onPingRequestFinished - success: " + success + ", persisted: " + isPersisted);
Telemetry.getHistogramById("TELEMETRY_SEND").add(new Date() - startTime);
let hping = Telemetry.getHistogramById("TELEMETRY_PING");
let hsuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS");
hsuccess.add(success);
hping.add(new Date() - startTime);
if (success && isPersisted) {
return TelemetryStorage.removePendingPing(id);
} else {
return Promise.resolve();
}
},
_getSubmissionPath: function(ping) {
// The new ping format contains an "application" section, the old one doesn't.
let pathComponents;
if (isV4PingFormat(ping)) {
// We insert the Ping id in the URL to simplify server handling of duplicated
// pings.
let app = ping.application;
pathComponents = [
ping.id, ping.type, app.name, app.version, app.channel, app.buildId
];
} else {
// This is a ping in the old format.
if (!("slug" in ping)) {
// That's odd, we don't have a slug. Generate one so that TelemetryStorage.jsm works.
ping.slug = Utils.generateUUID();
}
// Do we have enough info to build a submission URL?
let payload = ("payload" in ping) ? ping.payload : null;
if (payload && ("info" in payload)) {
let info = ping.payload.info;
pathComponents = [ ping.slug, info.reason, info.appName, info.appVersion,
info.appUpdateChannel, info.appBuildID ];
} else {
// Only use the UUID as the slug.
pathComponents = [ ping.slug ];
}
}
let slug = pathComponents.join("/");
return "/submit/telemetry/" + slug;
},
_doPing: function(ping, id, isPersisted) {
if (!this._canSend()) {
// We can't send the pings to the server, so don't try to.
this._log.trace("_doPing - Sending is disabled.");
return Promise.resolve();
}
this._log.trace("_doPing - server: " + this._server + ", persisted: " + isPersisted +
", id: " + id);
const isNewPing = isV4PingFormat(ping);
const version = isNewPing ? PING_FORMAT_VERSION : 1;
const url = this._server + this._getSubmissionPath(ping) + "?v=" + version;
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
request.mozBackgroundRequest = true;
request.timeout = PING_SUBMIT_TIMEOUT_MS;
request.open("POST", url, true);
request.overrideMimeType("text/plain");
request.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
this._pendingPingRequests.set(url, request);
let startTime = new Date();
let deferred = PromiseUtils.defer();
let onRequestFinished = (success, event) => {
let onCompletion = () => {
if (success) {
deferred.resolve();
} else {
deferred.reject(event);
}
};
this._pendingPingRequests.delete(url);
this._onPingRequestFinished(success, startTime, id, isPersisted)
.then(() => onCompletion(),
(error) => {
this._log.error("_doPing - request success: " + success + ", error" + error);
onCompletion();
});
};
let errorhandler = (event) => {
this._log.error("_doPing - error making request to " + url + ": " + event.type);
onRequestFinished(false, event);
};
request.onerror = errorhandler;
request.ontimeout = errorhandler;
request.onabort = errorhandler;
request.onload = (event) => {
let status = request.status;
let statusClass = status - (status % 100);
let success = false;
if (statusClass === 200) {
// We can treat all 2XX as success.
this._log.info("_doPing - successfully loaded, status: " + status);
success = true;
} else if (statusClass === 400) {
// 4XX means that something with the request was broken.
this._log.error("_doPing - error submitting to " + url + ", status: " + status
+ " - ping request broken?");
// TODO: we should handle this better, but for now we should avoid resubmitting
// broken requests by pretending success.
success = true;
} else if (statusClass === 500) {
// 5XX means there was a server-side error and we should try again later.
this._log.error("_doPing - error submitting to " + url + ", status: " + status
+ " - server error, should retry later");
} else {
// We received an unexpected status codes.
this._log.error("_doPing - error submitting to " + url + ", status: " + status
+ ", type: " + event.type);
}
onRequestFinished(success, event);
};
// If that's a legacy ping format, just send its payload.
let networkPayload = isNewPing ? ping : ping.payload;
request.setRequestHeader("Content-Encoding", "gzip");
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
startTime = new Date();
let utf8Payload = converter.ConvertFromUnicode(JSON.stringify(networkPayload));
utf8Payload += converter.Finish();
Telemetry.getHistogramById("TELEMETRY_STRINGIFY").add(new Date() - startTime);
let payloadStream = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
startTime = new Date();
payloadStream.data = gzipCompressString(utf8Payload);
Telemetry.getHistogramById("TELEMETRY_COMPRESS").add(new Date() - startTime);
startTime = new Date();
request.send(payloadStream);
return deferred.promise;
},
/**
* Check if pings can be sent to the server. If FHR is not allowed to upload,
* pings are not sent to the server (Telemetry is a sub-feature of FHR).
* If unified telemetry is off, don't send pings if Telemetry is disabled.
*
* @return {Boolean} True if pings can be send to the servers, false otherwise.
*/
_canSend: function() {
// We only send pings from official builds, but allow overriding this for tests.
if (!Telemetry.isOfficialTelemetry && !this._testMode) {
return false;
}
// With unified Telemetry, the FHR upload setting controls whether we can send pings.
// The Telemetry pref enables sending extended data sets instead.
if (IS_UNIFIED_TELEMETRY) {
return Preferences.get(PREF_FHR_UPLOAD_ENABLED, false);
}
// Without unified Telemetry, the Telemetry enabled pref controls ping sending.
return Preferences.get(PREF_TELEMETRY_ENABLED, false);
},
_reschedulePingSendTimer: function(timestamp) {
this._clearPingSendTimer();
const interval = timestamp - Policy.now();
this._pingSendTimer = Policy.setPingSendTimeout(() => this._sendPersistedPings(), interval);
},
_clearPingSendTimer: function() {
if (this._pingSendTimer) {
Policy.clearPingSendTimeout(this._pingSendTimer);
this._pingSendTimer = null;
}
},
/**
* Track any pending ping send and save tasks through the promise passed here.
* This is needed to block shutdown on any outstanding ping activity.
*/
_trackPendingPingTask: function (promise) {
this._connectionsBarrier.client.addBlocker("Waiting for ping task", promise);
},
};

View File

@ -20,6 +20,7 @@ Cu.import("resource://gre/modules/DeferredTask.jsm", this);
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/TelemetrySend.jsm", this);
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
const Utils = TelemetryUtils;
@ -705,13 +706,11 @@ this.TelemetrySession = Object.freeze({
return Impl.requestChildPayloads();
},
/**
* Save histograms to a file.
* Save the session state to a pending file.
* Used only for testing purposes.
*
* @param {nsIFile} aFile The file to load from.
*/
testSaveHistograms: function(aFile) {
return Impl.testSaveHistograms(aFile);
testSavePendingPing: function() {
return Impl.testSavePendingPing();
},
/**
* Collect and store information about startup.
@ -923,9 +922,8 @@ let Impl = {
hasPingBeenSent = Telemetry.getHistogramById("TELEMETRY_SUCCESS").snapshot().sum > 0;
} catch(e) {
}
if (!forSavedSession || hasPingBeenSent) {
ret.savedPings = TelemetryStorage.pingsLoaded;
}
ret.savedPings = TelemetryStorage.pendingPingCount;
ret.activeTicks = -1;
let sr = TelemetryController.getSessionRecorder();
@ -942,8 +940,8 @@ let Impl = {
ret.activeTicks = activeTicks;
}
ret.pingsOverdue = TelemetryStorage.pingsOverdue;
ret.pingsDiscarded = TelemetryStorage.pingsDiscarded;
ret.pingsOverdue = TelemetrySend.overduePingsCount;
ret.pingsDiscarded = TelemetrySend.discardedPingsCount;
return ret;
},
@ -1669,15 +1667,15 @@ let Impl = {
}),
testSaveHistograms: function testSaveHistograms(file) {
this._log.trace("testSaveHistograms - Path: " + file.path);
testSavePendingPing: function testSaveHistograms() {
this._log.trace("testSaveHistograms");
let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);
let options = {
addClientId: true,
addEnvironment: true,
overwrite: true,
};
return TelemetryController.savePing(getPingType(payload), payload, file.path, options);
return TelemetryController.addPendingPing(getPingType(payload), payload, options);
},
/**
@ -1798,9 +1796,6 @@ let Impl = {
// bug 1127907 lands.
Services.obs.notifyObservers(null, "gather-telemetry", null);
}).bind(this), Ci.nsIThread.DISPATCH_NORMAL);
// TODO: This is just a fallback for now. Remove this when we have ping send
// scheduling properly factored out and driven independently of this module.
TelemetryController.sendPersistedPings();
break;
#ifdef MOZ_WIDGET_ANDROID

View File

@ -33,6 +33,7 @@ const Utils = TelemetryUtils;
const DATAREPORTING_DIR = "datareporting";
const PINGS_ARCHIVE_DIR = "archived";
const ABORTED_SESSION_FILE_NAME = "aborted-session-ping";
XPCOMUtils.defineLazyGetter(this, "gDataReportingDir", function() {
return OS.Path.join(OS.Constants.Path.profileDir, DATAREPORTING_DIR);
});
@ -43,17 +44,6 @@ XPCOMUtils.defineLazyGetter(this, "gAbortedSessionFilePath", function() {
return OS.Path.join(gDataReportingDir, ABORTED_SESSION_FILE_NAME);
});
// Files that have been lying around for longer than MAX_PING_FILE_AGE are
// deleted without being loaded.
const MAX_PING_FILE_AGE = 14 * 24 * 60 * 60 * 1000; // 2 weeks
// Files that are older than OVERDUE_PING_FILE_AGE, but younger than
// MAX_PING_FILE_AGE indicate that we need to send all of our pings ASAP.
const OVERDUE_PING_FILE_AGE = 7 * 24 * 60 * 60 * 1000; // 1 week
// Maximum number of pings to save.
const MAX_LRU_PINGS = 50;
// Maxmimum time, in milliseconds, archive pings should be retained.
const MAX_ARCHIVED_PINGS_RETENTION_MS = 180 * 24 * 60 * 60 * 1000; // 180 days
@ -63,20 +53,7 @@ const ARCHIVE_QUOTA_BYTES = 120 * 1024 * 1024; // 120 MB
// This special value is submitted when the archive is outside of the quota.
const ARCHIVE_SIZE_PROBE_SPECIAL_VALUE = 300;
// The number of outstanding saved pings that we have issued loading
// requests for.
let pingsLoaded = 0;
// The number of pings that we have destroyed due to being older
// than MAX_PING_FILE_AGE.
let pingsDiscarded = 0;
// The number of pings that are older than OVERDUE_PING_FILE_AGE
// but younger than MAX_PING_FILE_AGE.
let pingsOverdue = 0;
// Data that has neither been saved nor sent by ping
let pendingPings = [];
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
let isPingDirectoryCreated = false;
@ -112,18 +89,6 @@ function waitForAll(it) {
}
this.TelemetryStorage = {
get MAX_PING_FILE_AGE() {
return MAX_PING_FILE_AGE;
},
get OVERDUE_PING_FILE_AGE() {
return OVERDUE_PING_FILE_AGE;
},
get MAX_LRU_PINGS() {
return MAX_LRU_PINGS;
},
get pingDirectoryPath() {
return OS.Path.join(OS.Constants.Path.profileDir, "saved-telemetry-pings");
},
@ -157,6 +122,17 @@ this.TelemetryStorage = {
return TelemetryStorageImpl.loadArchivedPing(id);
},
/**
* Get a list of info on the archived pings.
* This will scan the archive directory and grab basic data about the existing
* pings out of their filename.
*
* @return {promise<sequence<object>>}
*/
loadArchivedPingList: function() {
return TelemetryStorageImpl.loadArchivedPingList();
},
/**
* Clean the pings archive by removing old pings.
* This will scan the archive directory.
@ -182,16 +158,63 @@ this.TelemetryStorage = {
},
/**
* Get a list of info on the archived pings.
* This will scan the archive directory and grab basic data about the existing
* pings out of their filename.
* Save a pending - outgoing - ping to disk and track it.
*
* @return {promise<sequence<object>>}
* @param {Object} ping The ping data.
* @return {Promise} Resolved when the ping was saved.
*/
loadArchivedPingList: function() {
return TelemetryStorageImpl.loadArchivedPingList();
savePendingPing: function(ping) {
return TelemetryStorageImpl.savePendingPing(ping);
},
/**
* Load a pending ping from disk by id.
*
* @param {String} id The pings id.
* @return {Promise} Resolved with the loaded ping data.
*/
loadPendingPing: function(id) {
return TelemetryStorageImpl.loadPendingPing(id);
},
/**
* Remove a pending ping from disk by id.
*
* @param {String} id The pings id.
* @return {Promise} Resolved when the ping was removed.
*/
removePendingPing: function(id) {
return TelemetryStorageImpl.removePendingPing(id);
},
/**
* Returns a list of the currently pending pings in the format:
* {
* id: <string>, // The pings UUID.
* lastModificationDate: <number>, // Timestamp of the pings last modification.
* }
* This populates the list by scanning the disk.
*
* @return {Promise<sequence>} Resolved with the ping list.
*/
loadPendingPingList: function() {
return TelemetryStorageImpl.loadPendingPingList();
},
/**
* Returns a list of the currently pending pings in the format:
* {
* id: <string>, // The pings UUID.
* lastModificationDate: <number>, // Timestamp of the pings last modification.
* }
* This does not scan pending pings on disk.
*
* @return {sequence} The current pending ping list.
*/
getPendingPingList: function() {
return TelemetryStorageImpl.getPendingPingList();
},
/**
* Save an aborted-session ping to disk. This goes to a special location so
* it is not picked up as a pending ping.
@ -282,24 +305,9 @@ this.TelemetryStorage = {
return TelemetryStorageImpl.cleanupPingFile(ping);
},
/**
* Load all saved pings.
*
* Once loaded, the saved pings can be accessed (destructively only)
* through |popPendingPings|.
*
* @returns {promise}
*/
loadSavedPings: function() {
return TelemetryStorageImpl.loadSavedPings();
},
/**
* Load the histograms from a file.
*
* Once loaded, the saved pings can be accessed (destructively only)
* through |popPendingPings|.
*
* @param {string} file The file to load.
* @returns {promise}
*/
@ -308,38 +316,10 @@ this.TelemetryStorage = {
},
/**
* The number of pings loaded since the beginning of time.
* The number of pending pings on disk.
*/
get pingsLoaded() {
return TelemetryStorageImpl.pingsLoaded;
},
/**
* The number of pings loaded that are older than OVERDUE_PING_FILE_AGE
* but younger than MAX_PING_FILE_AGE.
*/
get pingsOverdue() {
return TelemetryStorageImpl.pingsOverdue;
},
/**
* The number of pings that we just tossed out for being older than
* MAX_PING_FILE_AGE.
*/
get pingsDiscarded() {
return TelemetryStorageImpl.pingsDiscarded;
},
/**
* Iterate destructively through the pending pings.
*
* @return {iterator}
*/
popPendingPings: function*() {
while (pendingPings.length > 0) {
let data = pendingPings.pop();
yield data;
}
get pendingPingCount() {
return TelemetryStorageImpl.pendingPingCount;
},
testLoadHistograms: function(file) {
@ -489,6 +469,10 @@ let TelemetryStorageImpl = {
// Whether we already scanned the archived pings on disk.
_scannedArchiveDirectory: false,
// Tracks the pending pings in a Map of (id -> {timestampCreated, type}).
// We use this to cache info on pending pings to avoid scanning the disk more than once.
_pendingPings: new Map(),
// Track the shutdown process to bail out of the clean up task quickly.
_shutdown: false,
@ -508,7 +492,6 @@ let TelemetryStorageImpl = {
shutdown: Task.async(function*() {
this._shutdown = true;
yield this._abortedSessionSerializer.flushTasks();
yield this.savePendingPings();
// If the archive cleaning task is running, block on it. It should bail out as soon
// as possible.
yield this._cleanArchiveTask;
@ -819,6 +802,8 @@ let TelemetryStorageImpl = {
this._shutdown = false;
this._scannedArchiveDirectory = false;
this._archivedPings = new Map();
this._scannedPendingDirectory = false;
this._pendingPings = new Map();
},
/**
@ -946,25 +931,12 @@ let TelemetryStorageImpl = {
* if it exists.
* @returns {promise}
*/
savePing: function(ping, overwrite) {
return Task.spawn(function*() {
yield getPingDirectory();
let file = pingFilePath(ping);
yield this.savePingToFile(ping, file, overwrite);
}.bind(this));
},
/**
* Save all pending pings.
*
* @returns {promise}
*/
savePendingPings: function() {
let p = [for (ping of pendingPings) this.savePing(ping, false).catch(ex => {
this._log.error("savePendingPings - failed to save pending pings.");
})];
return Promise.all(p);
},
savePing: Task.async(function*(ping, overwrite) {
yield getPingDirectory();
let file = pingFilePath(ping);
yield this.savePingToFile(ping, file, overwrite);
return file;
}),
/**
* Add a ping from an existing file to the saved pings directory so that it gets saved
@ -995,10 +967,7 @@ let TelemetryStorageImpl = {
* @return {Promise} A promise resolved when the ping is saved to the pings directory.
*/
addPendingPing: function(ping) {
// Append the ping to the pending list.
pendingPings.push(ping);
// Save the ping to the saved pings directory.
return this.savePing(ping, false);
return this.savePendingPing(ping);
},
/**
@ -1011,59 +980,111 @@ let TelemetryStorageImpl = {
return OS.File.remove(pingFilePath(ping));
},
/**
* Load all saved pings.
*
* Once loaded, the saved pings can be accessed (destructively only)
* through |popPendingPings|.
*
* @returns {promise}
*/
loadSavedPings: function() {
return Task.spawn(function*() {
let directory = TelemetryStorage.pingDirectoryPath;
let iter = new OS.File.DirectoryIterator(directory);
let exists = yield iter.exists();
savePendingPing: function(ping) {
return this.savePing(ping, true).then((path) => {
this._pendingPings.set(ping.id, {
path: path,
lastModificationDate: Policy.now().getTime(),
});
});
},
if (exists) {
let entries = yield iter.nextBatch();
let sortedEntries = [];
loadPendingPing: function(id) {
this._log.trace("loadPendingPing - id: " + id);
let info = this._pendingPings.get(id);
if (!info) {
return;
}
for (let entry of entries) {
if (entry.isDir) {
continue;
}
return this.loadPingFile(info.path, false);
},
let info = yield OS.File.stat(entry.path);
sortedEntries.push({entry:entry, lastModificationDate: info.lastModificationDate});
}
removePendingPing: function(id) {
let info = this._pendingPings.get(id);
if (!info) {
this._log.trace("removePendingPing - unknown id " + id);
return Promise.resolve();
}
sortedEntries.sort(function compare(a, b) {
return b.lastModificationDate - a.lastModificationDate;
});
this._log.trace("removePendingPing - deleting ping with id: " + id +
", path: " + info.path);
this._pendingPings.delete(id);
return OS.File.remove(info.path).catch((ex) =>
this._log.error("removePendingPing - failed to remove ping", ex));
},
let count = 0;
let result = [];
loadPendingPingList: function() {
// If we already have a pending scanning task active, return that.
if (this._scanPendingPingsTask) {
return this._scanPendingPingsTask;
}
// Keep only the last MAX_LRU_PINGS entries to avoid that the backlog overgrows.
for (let i = 0; i < MAX_LRU_PINGS && i < sortedEntries.length; i++) {
let entry = sortedEntries[i].entry;
result.push(this.loadHistograms(entry.path))
}
if (this._scannedPendingDirectory) {
this._log.trace("loadPendingPingList - Pending already scanned, hitting cache.");
return Promise.resolve(this._buildPingList());
}
for (let i = MAX_LRU_PINGS; i < sortedEntries.length; i++) {
let entry = sortedEntries[i].entry;
OS.File.remove(entry.path);
}
// Make sure to clear the task once done.
let clear = pings => {
this._scanPendingPingsTask = null;
return pings;
};
yield Promise.all(result);
// Since there's no pending pings scan task running, start it.
this._scanPendingPingsTask = this._scanPendingPings().then(clear, clear);
return this._scanPendingPingsTask;
},
Services.telemetry.getHistogramById('TELEMETRY_FILES_EVICTED').
add(sortedEntries.length - MAX_LRU_PINGS);
getPendingPingList: function() {
return this._buildPingList();
},
_scanPendingPings: Task.async(function*() {
this._log.trace("_scanPendingPings");
let directory = TelemetryStorage.pingDirectoryPath;
let iter = new OS.File.DirectoryIterator(directory);
let exists = yield iter.exists();
if (!exists) {
yield iter.close();
return [];
}
let files = (yield iter.nextBatch()).filter(e => !e.isDir);
for (let file of files) {
if (this._shutdown) {
yield iter.close();
return [];
}
yield iter.close();
}.bind(this));
let info = yield OS.File.stat(file.path);
let id = OS.Path.basename(file.path);
if (!UUID_REGEX.test(id)) {
this._log.trace("_scanPendingPings - unknown filename is not a UUID: " + id);
id = Utils.generateUUID();
}
this._pendingPings.set(id, {
path: file.path,
lastModificationDate: info.lastModificationDate,
});
}
yield iter.close();
this._scannedPendingDirectory = true;
return this._buildPingList();
}),
_buildPingList: function() {
const list = [for (p of this._pendingPings) {
id: p[0],
lastModificationDate: p[1].lastModificationDate,
}];
list.sort((a, b) => b.lastModificationDate - a.lastModificationDate);
return list;
},
/**
@ -1075,50 +1096,25 @@ let TelemetryStorageImpl = {
* @param {string} file The file to load.
* @returns {promise}
*/
loadHistograms: function loadHistograms(file) {
return OS.File.stat(file).then(function(info){
let now = Date.now();
if (now - info.lastModificationDate > MAX_PING_FILE_AGE) {
// We haven't had much luck in sending this file; delete it.
pingsDiscarded++;
return OS.File.remove(file);
}
loadHistograms: Task.async(function*(file) {
let success = true;
try {
const ping = yield this.loadPingfile(file);
return ping;
} catch (ex) {
success = false;
yield OS.File.remove(file);
} finally {
const success_histogram = Telemetry.getHistogramById("READ_SAVED_PING_SUCCESS");
success_histogram.add(success);
}
}),
// This file is a bit stale, and overdue for sending.
if (now - info.lastModificationDate > OVERDUE_PING_FILE_AGE) {
pingsOverdue++;
}
pingsLoaded++;
return addToPendingPings(file);
});
},
/**
* The number of pings loaded since the beginning of time.
*/
get pingsLoaded() {
return pingsLoaded;
},
/**
* The number of pings loaded that are older than OVERDUE_PING_FILE_AGE
* but younger than MAX_PING_FILE_AGE.
*/
get pingsOverdue() {
return pingsOverdue;
},
/**
* The number of pings that we just tossed out for being older than
* MAX_PING_FILE_AGE.
*/
get pingsDiscarded() {
return pingsDiscarded;
get pendingPingCount() {
return this._pendingPings.size;
},
testLoadHistograms: function(file) {
pingsLoaded = 0;
return this.loadHistograms(file.path);
},
@ -1180,8 +1176,7 @@ let TelemetryStorageImpl = {
}
// Check for a valid UUID.
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!uuidRegex.test(uuid)) {
if (!UUID_REGEX.test(uuid)) {
this._log.trace("_getArchivedPingDataFromFileName - should have a valid id");
return null;
}
@ -1255,22 +1250,6 @@ function getPingDirectory() {
});
}
function addToPendingPings(file) {
function onLoad(success) {
let success_histogram = Telemetry.getHistogramById("READ_SAVED_PING_SUCCESS");
success_histogram.add(success);
}
return TelemetryStorage.loadPingFile(file).then(ping => {
pendingPings.push(ping);
onLoad(true);
},
() => {
onLoad(false);
return OS.File.remove(file);
});
}
/**
* Build the path to the archived ping.
* @param {String} aPingId The ping id.

View File

@ -76,6 +76,12 @@ this.TelemetryUtils = {
return null;
},
generateUUID: function() {
let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
// strip {}
return str.substring(1, str.length - 1);
},
/**
* Find how many months passed between two dates.
* @param {Object} aStartDate The starting date.

View File

@ -32,6 +32,7 @@ EXTRA_COMPONENTS += [
EXTRA_JS_MODULES += [
'TelemetryArchive.jsm',
'TelemetryLog.jsm',
'TelemetrySend.jsm',
'TelemetryStopwatch.jsm',
'TelemetryStorage.jsm',
'TelemetryUtils.jsm',

View File

@ -149,6 +149,7 @@ function fakeNow(...args) {
Cu.import("resource://gre/modules/TelemetryEnvironment.jsm"),
Cu.import("resource://gre/modules/TelemetryController.jsm"),
Cu.import("resource://gre/modules/TelemetryStorage.jsm"),
Cu.import("resource://gre/modules/TelemetrySend.jsm"),
];
for (let m of modules) {
@ -160,14 +161,14 @@ function fakeNow(...args) {
// Fake the timeout functions for TelemetryController sending.
function fakePingSendTimer(set, clear) {
let ping = Cu.import("resource://gre/modules/TelemetryController.jsm");
ping.Policy.setPingSendTimeout = set;
ping.Policy.clearPingSendTimeout = clear;
let module = Cu.import("resource://gre/modules/TelemetrySend.jsm");
module.Policy.setPingSendTimeout = set;
module.Policy.clearPingSendTimeout = clear;
}
function fakeMidnightPingFuzzingDelay(delayMs) {
let ping = Cu.import("resource://gre/modules/TelemetryController.jsm");
ping.Policy.midnightPingFuzzingDelay = () => delayMs;
let module = Cu.import("resource://gre/modules/TelemetrySend.jsm");
module.Policy.midnightPingFuzzingDelay = () => delayMs;
}
// Return a date that is |offset| ms in the future from |date|.

View File

@ -14,6 +14,7 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/TelemetryController.jsm", this);
Cu.import("resource://gre/modules/TelemetryStorage.jsm", this);
Cu.import("resource://gre/modules/TelemetrySend.jsm", this);
Cu.import("resource://gre/modules/TelemetryArchive.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
@ -42,9 +43,9 @@ let gClientID = null;
function sendPing(aSendClientId, aSendEnvironment) {
if (gServerStarted) {
TelemetryController.setServer("http://localhost:" + gHttpServer.identity.primaryPort);
TelemetrySend.setServer("http://localhost:" + gHttpServer.identity.primaryPort);
} else {
TelemetryController.setServer("http://doesnotexist");
TelemetrySend.setServer("http://doesnotexist");
}
let options = {

View File

@ -8,6 +8,7 @@
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/TelemetryController.jsm", this);
Cu.import("resource://gre/modules/TelemetrySend.jsm", this);
Cu.import("resource://gre/modules/Timer.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
@ -59,8 +60,8 @@ add_task(function* test_sendTimeout() {
httpServer.start(-1);
yield TelemetryController.setup();
TelemetryController.setServer("http://localhost:" + httpServer.identity.primaryPort);
TelemetryController.submitExternalPing("test-ping-type", {});
TelemetrySend.setServer("http://localhost:" + httpServer.identity.primaryPort);
yield TelemetryController.submitExternalPing("test-ping-type", {});
// Trigger the AsyncShutdown phase TelemetryController hangs off.
AsyncShutdown.profileBeforeChange._trigger();

View File

@ -10,6 +10,7 @@ Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/TelemetryStorage.jsm", this);
Cu.import("resource://gre/modules/TelemetryController.jsm", this);
Cu.import("resource://gre/modules/TelemetrySession.jsm", this);
Cu.import("resource://gre/modules/TelemetrySend.jsm", this);
const PREF_ENABLED = "toolkit.telemetry.enabled";
const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
@ -43,7 +44,7 @@ add_task(function* testSendPendingOnIdleDaily() {
// Telemetry will not send this ping at startup, because it's not overdue.
yield TelemetryController.setup();
TelemetryController.setServer("http://localhost:" + gHttpServer.identity.primaryPort);
TelemetrySend.setServer("http://localhost:" + gHttpServer.identity.primaryPort);
let pendingPromise = new Promise(resolve =>
gHttpServer.registerPrefixHandler("/submit/telemetry/", request => resolve(request)));
@ -59,6 +60,9 @@ add_task(function* testSendPendingOnIdleDaily() {
Services.obs.removeObserver(gatherPromise.resolve, "gather-telemetry");
// Check that the pending ping is correctly received.
let ns = {};
let module = Cu.import("resource://gre/modules/TelemetrySend.jsm", ns);
module.TelemetrySendImpl.observe(null, "idle-daily", null);
let request = yield pendingPromise;
let ping = decodeRequestPayload(request);

View File

@ -18,6 +18,7 @@ Cu.import("resource://testing-common/httpd.js", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/TelemetryStorage.jsm", this);
Cu.import("resource://gre/modules/TelemetryController.jsm", this);
Cu.import("resource://gre/modules/TelemetrySend.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let {OS: {File, Path, Constants}} = Cu.import("resource://gre/modules/osfile.jsm", {});
@ -31,8 +32,8 @@ XPCOMUtils.defineLazyGetter(this, "gDatareportingService",
// OVERDUE_PING_FILE_AGE by 1 minute so that our test pings exceed
// those points in time, even taking into account file system imprecision.
const ONE_MINUTE_MS = 60 * 1000;
const EXPIRED_PING_FILE_AGE = TelemetryStorage.MAX_PING_FILE_AGE + ONE_MINUTE_MS;
const OVERDUE_PING_FILE_AGE = TelemetryStorage.OVERDUE_PING_FILE_AGE + ONE_MINUTE_MS;
const EXPIRED_PING_FILE_AGE = TelemetrySend.MAX_PING_FILE_AGE + ONE_MINUTE_MS;
const OVERDUE_PING_FILE_AGE = TelemetrySend.OVERDUE_PING_FILE_AGE + ONE_MINUTE_MS;
const PING_SAVE_FOLDER = "saved-telemetry-pings";
const PING_TIMEOUT_LENGTH = 5000;
@ -40,7 +41,7 @@ const EXPIRED_PINGS = 5;
const OVERDUE_PINGS = 6;
const OLD_FORMAT_PINGS = 4;
const RECENT_PINGS = 4;
const LRU_PINGS = TelemetryStorage.MAX_LRU_PINGS;
const LRU_PINGS = TelemetrySend.MAX_LRU_PINGS;
const TOTAL_EXPECTED_PINGS = OVERDUE_PINGS + RECENT_PINGS + OLD_FORMAT_PINGS;
@ -91,8 +92,7 @@ let createSavedPings = Task.async(function* (aPingInfos) {
*/
let clearPings = Task.async(function* (aPingIds) {
for (let pingId of aPingIds) {
let filePath = getSavePathForPingId(pingId);
yield File.remove(filePath);
yield TelemetryStorage.removePendingPing(pingId);
}
});
@ -161,14 +161,14 @@ function stopHttpServer() {
}
/**
* Reset Telemetry state.
* Clear out all pending pings.
*/
function resetTelemetry() {
// Quick and dirty way to clear TelemetryStorage's pendingPings
// collection, and put it back in its initial state.
let gen = TelemetryStorage.popPendingPings();
for (let item of gen) {};
}
let clearPendingPings = Task.async(function*() {
const pending = yield TelemetryStorage.loadPendingPingList();
for (let p of pending) {
yield TelemetryStorage.removePendingPing(p.id);
}
});
/**
* Creates and returns a TelemetryController instance in "testing"
@ -205,7 +205,7 @@ add_task(function* setupEnvironment() {
let directory = TelemetryStorage.pingDirectoryPath;
yield File.makeDir(directory, { ignoreExisting: true, unixMode: OS.Constants.S_IRWXU });
yield resetTelemetry();
yield clearPendingPings();
});
/**
@ -215,10 +215,12 @@ add_task(function* setupEnvironment() {
add_task(function* test_expired_pings_are_deleted() {
let pingTypes = [{ num: EXPIRED_PINGS, age: EXPIRED_PING_FILE_AGE }];
let expiredPings = yield createSavedPings(pingTypes);
yield startTelemetry();
yield TelemetryController.reset();
assertReceivedPings(0);
yield assertNotSaved(expiredPings);
yield resetTelemetry();
yield clearPendingPings();
});
/**
@ -227,10 +229,11 @@ add_task(function* test_expired_pings_are_deleted() {
add_task(function* test_recent_pings_not_sent() {
let pingTypes = [{ num: RECENT_PINGS }];
let recentPings = yield createSavedPings(pingTypes);
yield startTelemetry();
yield TelemetryController.reset();
assertReceivedPings(0);
yield resetTelemetry();
yield clearPings(recentPings);
yield clearPendingPings();
});
/**
@ -245,18 +248,16 @@ add_task(function* test_most_recent_pings_kept() {
let head = pings.slice(0, LRU_PINGS);
let tail = pings.slice(-3);
yield startTelemetry();
let gen = TelemetryStorage.popPendingPings();
yield TelemetryController.reset();
const pending = yield TelemetryStorage.loadPendingPingList();
for (let item of gen) {
for (let id of tail) {
do_check_neq(id, item.id);
}
for (let id of tail) {
const found = pending.some(p => p.id == id);
Assert.ok(!found, "Should have discarded the oldest pings");
}
assertNotSaved(tail);
yield resetTelemetry();
yield clearPings(pings);
yield clearPendingPings();
});
/**
@ -301,10 +302,10 @@ add_task(function* test_overdue_old_format() {
};
const PING_FILES_PATHS = [
Path.join(Constants.Path.profileDir, PING_SAVE_FOLDER, PING_OLD_FORMAT.slug),
Path.join(Constants.Path.profileDir, PING_SAVE_FOLDER, PING_NO_INFO.slug),
Path.join(Constants.Path.profileDir, PING_SAVE_FOLDER, PING_NO_PAYLOAD.slug),
Path.join(Constants.Path.profileDir, PING_SAVE_FOLDER, "no-slug-file"),
getSavePathForPingId(PING_OLD_FORMAT.slug),
getSavePathForPingId(PING_NO_INFO.slug),
getSavePathForPingId(PING_NO_PAYLOAD.slug),
getSavePathForPingId("no-slug-file"),
];
// Write the ping to file and make it overdue.
@ -317,14 +318,14 @@ add_task(function* test_overdue_old_format() {
yield File.setDates(PING_FILES_PATHS[f], null, Date.now() - OVERDUE_PING_FILE_AGE);
}
yield startTelemetry();
yield TelemetryController.reset();
assertReceivedPings(OLD_FORMAT_PINGS);
// |TelemetryStorage.cleanup| doesn't know how to remove a ping with no slug or id,
// so remove it manually so that the next test doesn't fail.
yield OS.File.remove(PING_FILES_PATHS[3]);
yield resetTelemetry();
yield clearPendingPings();
});
/**
@ -343,13 +344,14 @@ add_task(function* test_overdue_pings_trigger_send() {
let expiredPings = pings.slice(RECENT_PINGS, RECENT_PINGS + EXPIRED_PINGS);
let overduePings = pings.slice(-OVERDUE_PINGS);
yield startTelemetry();
yield TelemetryController.reset();
assertReceivedPings(TOTAL_EXPECTED_PINGS);
yield assertNotSaved(recentPings);
yield assertNotSaved(expiredPings);
yield assertNotSaved(overduePings);
yield resetTelemetry();
yield clearPendingPings();
});
/**
@ -395,10 +397,10 @@ add_task(function* test_overdue_old_format() {
receivedPings++;
});
yield startTelemetry();
yield TelemetryController.reset();
Assert.equal(receivedPings, 1, "We must receive a ping in the old format.");
yield resetTelemetry();
yield clearPendingPings();
});
add_task(function* teardown() {

View File

@ -18,6 +18,7 @@ Cu.import("resource://gre/modules/TelemetryController.jsm", this);
Cu.import("resource://gre/modules/TelemetrySession.jsm", this);
Cu.import("resource://gre/modules/TelemetryStorage.jsm", this);
Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", this);
Cu.import("resource://gre/modules/TelemetrySend.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Preferences.jsm");
@ -97,14 +98,21 @@ function truncateDateToDays(date) {
function sendPing() {
TelemetrySession.gatherStartup();
if (gServerStarted) {
TelemetryController.setServer("http://localhost:" + gHttpServer.identity.primaryPort);
TelemetrySend.setServer("http://localhost:" + gHttpServer.identity.primaryPort);
return TelemetrySession.testPing();
} else {
TelemetryController.setServer("http://doesnotexist");
TelemetrySend.setServer("http://doesnotexist");
return TelemetrySession.testPing();
}
}
let clearPendingPings = Task.async(function*() {
const pending = yield TelemetryStorage.loadPendingPingList();
for (let p of pending) {
yield TelemetryStorage.removePendingPing(p.id);
}
});
function wrapWithExceptionHandler(f) {
function wrapper(...args) {
try {
@ -267,7 +275,7 @@ function checkPayloadInfo(data) {
Assert.ok(data.timezoneOffset <= 12*60, "The timezone must be in a valid range.");
}
function checkPayload(payload, reason, successfulPings) {
function checkPayload(payload, reason, successfulPings, savedPings) {
Assert.ok("info" in payload, "Payload must contain an info section.");
checkPayloadInfo(payload.info);
@ -275,7 +283,7 @@ function checkPayload(payload, reason, successfulPings) {
Assert.ok(payload.simpleMeasurements.uptime >= 0);
Assert.equal(payload.simpleMeasurements.startupInterrupted, 1);
Assert.equal(payload.simpleMeasurements.shutdownDuration, SHUTDOWN_TIME);
Assert.equal(payload.simpleMeasurements.savedPings, 1);
Assert.equal(payload.simpleMeasurements.savedPings, savedPings);
Assert.ok("maximalNumberOfConcurrentThreads" in payload.simpleMeasurements);
Assert.ok(payload.simpleMeasurements.maximalNumberOfConcurrentThreads >= gNumberOfThreadsLaunched);
@ -304,7 +312,9 @@ function checkPayload(payload, reason, successfulPings) {
const TELEMETRY_TEST_KEYED_COUNT = "TELEMETRY_TEST_KEYED_COUNT";
const READ_SAVED_PING_SUCCESS = "READ_SAVED_PING_SUCCESS";
Assert.ok(TELEMETRY_PING in payload.histograms);
if (successfulPings > 0) {
Assert.ok(TELEMETRY_PING in payload.histograms);
}
Assert.ok(READ_SAVED_PING_SUCCESS in payload.histograms);
Assert.ok(TELEMETRY_TEST_FLAG in payload.histograms);
Assert.ok(TELEMETRY_TEST_COUNT in payload.histograms);
@ -346,17 +356,19 @@ function checkPayload(payload, reason, successfulPings) {
Assert.equal(uneval(count), uneval(expected_count));
// There should be one successful report from the previous telemetry ping.
const expected_tc = {
range: [1, 2],
bucket_count: 3,
histogram_type: 2,
values: {0:2, 1:successfulPings, 2:0},
sum: successfulPings,
sum_squares_lo: successfulPings,
sum_squares_hi: 0
};
let tc = payload.histograms[TELEMETRY_SUCCESS];
Assert.equal(uneval(tc), uneval(expected_tc));
if (successfulPings > 0) {
const expected_tc = {
range: [1, 2],
bucket_count: 3,
histogram_type: 2,
values: {0:2, 1:successfulPings, 2:0},
sum: successfulPings,
sum_squares_lo: successfulPings,
sum_squares_hi: 0
};
let tc = payload.histograms[TELEMETRY_SUCCESS];
Assert.equal(uneval(tc), uneval(expected_tc));
}
let h = payload.histograms[READ_SAVED_PING_SUCCESS];
Assert.equal(h.values[0], 1);
@ -562,42 +574,42 @@ add_task(function* test_simplePing() {
// Saves the current session histograms, reloads them, performs a ping
// and checks that the dummy http server received both the previously
// saved histograms and the new ones.
// saved ping and the new one.
add_task(function* test_saveLoadPing() {
let histogramsFile = getSavedPingFile("saved-histograms.dat");
// Let's start out with a defined state.
yield clearPendingPings();
yield TelemetryController.reset();
gRequestIterator = Iterator(new Request());
// Setup test data and trigger pings.
setupTestData();
yield TelemetrySession.testSaveHistograms(histogramsFile);
yield TelemetryStorage.testLoadHistograms(histogramsFile);
yield TelemetrySession.testSavePendingPing();
yield sendPing();
// Get requests received by dummy server.
let request1 = yield gRequestIterator.next();
let request2 = yield gRequestIterator.next();
let requests = [
yield gRequestIterator.next(),
yield gRequestIterator.next(),
];
Assert.equal(request1.getHeader("content-type"), "application/json; charset=UTF-8",
"The request must have the correct content-type.");
Assert.equal(request2.getHeader("content-type"), "application/json; charset=UTF-8",
"The request must have the correct content-type.");
for (let req of requests) {
Assert.equal(req.getHeader("content-type"), "application/json; charset=UTF-8",
"The request must have the correct content-type.");
}
// We decode both requests to check for the |reason|.
let ping1 = decodeRequestPayload(request1);
let ping2 = decodeRequestPayload(request2);
let pings = [for (req of requests) decodeRequestPayload(req)];
// Check we have the correct two requests. Ordering is not guaranteed. The ping type
// is encoded in the URL.
let requestTypeComponent = request1.path.split("/")[4];
if (requestTypeComponent === PING_TYPE_MAIN) {
checkPingFormat(ping1, PING_TYPE_MAIN, true, true);
checkPayload(ping1.payload, REASON_TEST_PING, 1);
checkPingFormat(ping2, PING_TYPE_SAVED_SESSION, true, true);
checkPayload(ping2.payload, REASON_SAVED_SESSION, 1);
} else {
checkPingFormat(ping1, PING_TYPE_SAVED_SESSION, true, true);
checkPayload(ping1.payload, REASON_SAVED_SESSION, 1);
checkPingFormat(ping2, PING_TYPE_MAIN, true, true);
checkPayload(ping2.payload, REASON_TEST_PING, 1);
if (pings[0].type != PING_TYPE_MAIN) {
pings.reverse();
}
checkPingFormat(pings[0], PING_TYPE_MAIN, true, true);
checkPayload(pings[0].payload, REASON_TEST_PING, 0, 1);
checkPingFormat(pings[1], PING_TYPE_SAVED_SESSION, true, true);
checkPayload(pings[1].payload, REASON_SAVED_SESSION, 0, 0);
});
add_task(function* test_checkSubsessionHistograms() {
@ -855,7 +867,7 @@ add_task(function* test_dailyCollection() {
// Init and check timer.
yield TelemetrySession.setup();
TelemetryController.setServer("http://localhost:" + gHttpServer.identity.primaryPort);
TelemetrySend.setServer("http://localhost:" + gHttpServer.identity.primaryPort);
// Set histograms to expected state.
const COUNT_ID = "TELEMETRY_TEST_COUNT";
@ -1066,7 +1078,7 @@ add_task(function* test_environmentChange() {
// Setup.
yield TelemetrySession.setup();
TelemetryController.setServer("http://localhost:" + gHttpServer.identity.primaryPort);
TelemetrySend.setServer("http://localhost:" + gHttpServer.identity.primaryPort);
TelemetryEnvironment._watchPreferences(PREFS_TO_WATCH);
// Set histograms to expected state.
@ -1121,16 +1133,18 @@ add_task(function* test_environmentChange() {
});
// Checks that an expired histogram file is deleted when loaded.
add_task(function* test_runOldPingFile() {
let histogramsFile = getSavedPingFile("old-histograms.dat");
add_task(function* test_pruneOldPingFile() {
const id = generateUUID();
const path = OS.Path.join(TelemetryStorage.pingDirectoryPath, id);
yield OS.File.writeAtomic(path, "{}", {noOverwrite: false});
yield TelemetrySession.testSaveHistograms(histogramsFile);
do_check_true(histogramsFile.exists());
let mtime = histogramsFile.lastModifiedTime;
histogramsFile.lastModifiedTime = mtime - (14 * 24 * 60 * 60 * 1000 + 60000); // 14 days, 1m
// fake a time 14 days & 1m earlier
const now = new Date().getTime();
const fakeMtime = now - (14 * 24 * 60 * 60 * 1000 + 60000);
OS.File.setDates(path, null, fakeMtime);
yield TelemetryStorage.testLoadHistograms(histogramsFile);
do_check_false(histogramsFile.exists());
yield TelemetryController.reset();
Assert.ok(!(yield OS.File.exists(path)), "File should have been removed.");
});
add_task(function* test_savedPingsOnShutdown() {
@ -1144,11 +1158,13 @@ add_task(function* test_savedPingsOnShutdown() {
yield OS.File.makeDir(dir);
yield TelemetrySession.shutdown();
yield TelemetryStorage.loadSavedPings();
Assert.equal(TelemetryStorage.pingsLoaded, expectedPings);
yield TelemetryController.reset();
const pending = yield TelemetryStorage.loadPendingPingList();
Assert.equal(pending.length, expectedPings,
"Should have the correct number of pending pings.");
let pingsIterator = TelemetryStorage.popPendingPings();
for (let ping of pingsIterator) {
const pings = [for (p of pending) yield TelemetryStorage.loadPendingPing(p.id)];
for (let ping of pings) {
Assert.ok("type" in ping);
let expectedReason =
@ -1349,8 +1365,10 @@ add_task(function* test_abortedSession() {
// saved pings directory when it starts.
yield TelemetryStorage.savePingToFile(abortedSessionPing, ABORTED_FILE, false);
yield clearPendingPings();
gRequestIterator = Iterator(new Request());
yield TelemetrySession.reset();
yield TelemetryController.reset();
Assert.ok(!(yield OS.File.exists(ABORTED_FILE)),
"The aborted session ping must be removed from the aborted session ping directory.");
@ -1362,16 +1380,20 @@ add_task(function* test_abortedSession() {
Assert.ok((yield OS.File.exists(PENDING_PING_FILE)),
"The aborted session ping must exist in the saved pings directory.");
// Trick: make the aborted ping file overdue so that it gets sent immediately when
// resetting TelemetryController.
const OVERDUE_PING_FILE_AGE = TelemetryStorage.OVERDUE_PING_FILE_AGE + 60 * 1000;
yield OS.File.setDates(PENDING_PING_FILE, null, Date.now() - OVERDUE_PING_FILE_AGE);
yield TelemetryController.reset();
// Trigger sending the pending pings.
yield sendPing();
// Wait for the aborted-session ping.
let request = yield gRequestIterator.next();
let receivedPing = decodeRequestPayload(request);
Assert.equal(receivedPing.payload.info.reason, REASON_ABORTED_SESSION);
// We should receive two pings, one of them an aborted-session ping.
let requests = [
yield gRequestIterator.next(),
yield gRequestIterator.next(),
];
let pings = [for (req of requests) decodeRequestPayload(req)].filter((p) => {
return p.type == PING_TYPE_MAIN && p.payload.info.reason == REASON_ABORTED_SESSION;
});
Assert.equal(pings.length, 1, "Should have received one aborted-session ping.");
let receivedPing = pings[0];
Assert.equal(receivedPing.id, abortedSessionPing.id);
yield TelemetrySession.shutdown();
@ -1512,6 +1534,7 @@ add_task(function* test_schedulerEnvironmentReschedules() {
[PREF_TEST, TelemetryEnvironment.RECORD_PREF_VALUE],
]);
yield clearPendingPings();
gRequestIterator = Iterator(new Request());
// Set a fake current date and start Telemetry.
@ -1554,6 +1577,7 @@ add_task(function* test_schedulerNothingDue() {
// Remove any aborted-session ping from the previous tests.
yield OS.File.removeDir(DATAREPORTING_PATH, { ignoreAbsent: true });
yield clearPendingPings();
// We don't expect to receive any ping in this test, so assert if we do.
registerPingHandler((req, res) => {
@ -1590,6 +1614,7 @@ add_task(function* test_pingExtendedStats() {
// Disable sending extended statistics.
Telemetry.canRecordExtended = false;
yield clearPendingPings();
gRequestIterator = Iterator(new Request());
yield TelemetrySession.reset();
yield sendPing();
@ -1653,6 +1678,7 @@ add_task(function* test_schedulerUserIdle() {
schedulerTimeout = timeout;
}, () => {});
yield TelemetrySession.reset();
yield clearPendingPings();
gRequestIterator = Iterator(new Request());
// When not idle, the scheduler should have a 5 minutes tick interval.
@ -1693,7 +1719,9 @@ add_task(function* test_sendDailyOnIdle() {
fakeSchedulerTimer((callback, timeout) => {
schedulerTickCallback = callback;
}, () => {});
yield TelemetrySession.reset();
yield clearPendingPings();
// Make sure we are not sending a daily before midnight when active.
now = new Date(2040, 1, 1, 23, 55, 0);

View File

@ -44,7 +44,7 @@ skip-if = android_version == "18"
# Bug 1144395: crash on Android 4.3
skip-if = android_version == "18"
[test_TelemetrySendOldPings.js]
skip-if = debug == true || os == "android" # Disabled due to intermittent orange on Android
skip-if = os == "android" # Disabled due to intermittent orange on Android
[test_TelemetrySession.js]
# Bug 1144395: crash on Android 4.3
skip-if = android_version == "18"

View File

@ -675,11 +675,17 @@ PopupNotifications.prototype = {
_update: function PopupNotifications_update(notifications, anchors = new Set(), dismissShowing = false) {
if (anchors instanceof Ci.nsIDOMXULElement)
anchors = new Set([anchors]);
if (!notifications)
notifications = this._currentNotifications;
let haveNotifications = notifications.length > 0;
if (!anchors.size && haveNotifications)
anchors = this._getAnchorsForNotifications(notifications);
let notificationsToShow = [];
// Filter out notifications that have been dismissed.
notificationsToShow = notifications.filter(function (n) {
return !n.dismissed && !n.options.neverShow;
});
if (!anchors.size && notificationsToShow.length)
anchors = this._getAnchorsForNotifications(notificationsToShow);
let useIconBox = !!this.iconBox;
if (useIconBox && anchors.size) {
@ -696,13 +702,8 @@ PopupNotifications.prototype = {
this._hideIcons();
}
let notificationsToShow = [];
let haveNotifications = notifications.length > 0;
if (haveNotifications) {
// Filter out notifications that have been dismissed.
notificationsToShow = notifications.filter(function (n) {
return !n.dismissed && !n.options.neverShow;
});
if (useIconBox) {
this._showIcons(notifications);
this.iconBox.hidden = false;

View File

@ -36,4 +36,7 @@ toolkit.jar:
+ skin/classic/mozapps/places/defaultFavicon.png (places/defaultFavicon.png)
#endif
#if MOZ_BUILD_APP == browser
../browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
#endif
% override chrome://mozapps/skin/passwordmgr/key.png chrome://mozapps/skin/passwordmgr/key-16.png

View File

@ -215,4 +215,7 @@ toolkit.jar:
skin/classic/global/tree/folder.png (tree/folder.png)
skin/classic/global/tree/folder@2x.png (tree/folder@2x.png)
#if MOZ_BUILD_APP == browser
../browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
#endif
% override chrome://global/skin/dirListing/local.png chrome://global/skin/dirListing/folder.png

View File

@ -84,6 +84,9 @@ toolkit.jar:
#endif
skin/classic/mozapps/handling/handling.css (handling/handling.css)
#if MOZ_BUILD_APP == browser
../browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
#endif
% override chrome://mozapps/skin/extensions/category-extensions.png chrome://mozapps/skin/extensions/extensionGeneric.png
% override chrome://mozapps/skin/extensions/category-languages.png chrome://mozapps/skin/extensions/localeGeneric.png
% override chrome://mozapps/skin/extensions/category-themes.png chrome://mozapps/skin/extensions/themeGeneric.png

View File

@ -243,6 +243,9 @@ toolkit.jar:
skin/classic/global/tree/twisty-clsd-XP.png (tree/twisty-clsd-XP.png)
skin/classic/global/tree/twisty-open-XP.png (tree/twisty-open-XP.png)
#if MOZ_BUILD_APP == browser
../browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
#endif
% override chrome://global/skin/console/console-toolbar.png chrome://global/skin/console/console-toolbar-XP.png osversion<6
% override chrome://global/skin/dirListing/folder.png chrome://global/skin/dirListing/folder-XP.png osversion<6
% override chrome://global/skin/dirListing/local.png chrome://global/skin/dirListing/local-XP.png osversion<6
@ -283,5 +286,8 @@ toolkit.jar:
% override chrome://global/skin/tree/twisty-open.png chrome://global/skin/tree/twisty-open-XP.png osversion<6
#endif
#if MOZ_BUILD_APP == browser
../browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
#endif
% override chrome://global/skin/arrow/arrow-lft-hov.gif chrome://global/skin/arrow/arrow-lft.gif
% override chrome://global/skin/arrow/arrow-rit-hov.gif chrome://global/skin/arrow/arrow-rit.gif

View File

@ -94,6 +94,9 @@ toolkit.jar:
skin/classic/mozapps/profile/profileicon-XP.png (profile/profileicon-XP.png)
skin/classic/mozapps/update/downloadButtons-XP.png (update/downloadButtons-XP.png)
#if MOZ_BUILD_APP == browser
../browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
#endif
% override chrome://mozapps/skin/downloads/downloadButtons.png chrome://mozapps/skin/downloads/downloadButtons-XP.png osversion<6
% override chrome://mozapps/skin/downloads/downloadIcon.png chrome://mozapps/skin/downloads/downloadIcon-XP.png osversion<6
% override chrome://mozapps/skin/extensions/category-discover.png chrome://mozapps/skin/extensions/category-discover-XP.png osversion<6
@ -112,6 +115,9 @@ toolkit.jar:
% override chrome://mozapps/skin/update/downloadButtons.png chrome://mozapps/skin/update/downloadButtons-XP.png osversion<6
#endif
#if MOZ_BUILD_APP == browser
../browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
#endif
% override chrome://mozapps/skin/extensions/category-dictionaries.png chrome://mozapps/skin/extensions/dictionaryGeneric.png
% override chrome://mozapps/skin/extensions/category-experiments.png chrome://mozapps/skin/extensions/experimentGeneric.png
% override chrome://mozapps/skin/extensions/category-extensions.png chrome://mozapps/skin/extensions/extensionGeneric.png

View File

@ -135,7 +135,9 @@ static const ManifestDirective kParsingTable[] = {
nullptr, &nsChromeRegistry::ManifestStyle, nullptr
},
{
"override", 2, false, true, true, true, false,
// NB: note that while skin manifests can use this, they are only allowed
// to use it for chrome://../skin/ URLs
"override", 2, false, false, true, true, false,
nullptr, &nsChromeRegistry::ManifestOverride, nullptr
},
{