mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
916718880d
@ -15,11 +15,11 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="d7385b79e68d4ad662cacf810506e9ee53345d23"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="85ae6808d298a4010aaab341c66699f1b87eec9c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
|
||||
<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"/>
|
||||
|
@ -15,11 +15,11 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="d7385b79e68d4ad662cacf810506e9ee53345d23"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="85ae6808d298a4010aaab341c66699f1b87eec9c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
|
||||
<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"/>
|
||||
|
@ -19,12 +19,12 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d7385b79e68d4ad662cacf810506e9ee53345d23"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="85ae6808d298a4010aaab341c66699f1b87eec9c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d70e4bfdcb65e7514de0f9315b74aea1c811678d"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
|
||||
|
@ -17,9 +17,9 @@
|
||||
</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="d7385b79e68d4ad662cacf810506e9ee53345d23"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="85ae6808d298a4010aaab341c66699f1b87eec9c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e1a50a20b3383e3b0959e5b32ef429425fd6be5b"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
|
@ -15,11 +15,11 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="d7385b79e68d4ad662cacf810506e9ee53345d23"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="85ae6808d298a4010aaab341c66699f1b87eec9c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
|
||||
<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"/>
|
||||
|
@ -15,11 +15,11 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="05a36844c1046a1eb07d5b1325f85ed741f961ea">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="d7385b79e68d4ad662cacf810506e9ee53345d23"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="85ae6808d298a4010aaab341c66699f1b87eec9c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
|
||||
<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"/>
|
||||
|
@ -19,12 +19,12 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d7385b79e68d4ad662cacf810506e9ee53345d23"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="85ae6808d298a4010aaab341c66699f1b87eec9c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d70e4bfdcb65e7514de0f9315b74aea1c811678d"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
|
||||
|
@ -15,11 +15,11 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="d7385b79e68d4ad662cacf810506e9ee53345d23"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="85ae6808d298a4010aaab341c66699f1b87eec9c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
|
||||
<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"/>
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"git": {
|
||||
"git_revision": "d7385b79e68d4ad662cacf810506e9ee53345d23",
|
||||
"git_revision": "85ae6808d298a4010aaab341c66699f1b87eec9c",
|
||||
"remote": "https://git.mozilla.org/releases/gaia.git",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "c379332cd161e9675eedc580725f101a18be38f6",
|
||||
"revision": "40beee0c2eb5e129dc5a30f0fc83f73be0eb33da",
|
||||
"repo_path": "integration/gaia-central"
|
||||
}
|
||||
|
@ -17,9 +17,9 @@
|
||||
</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="d7385b79e68d4ad662cacf810506e9ee53345d23"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="85ae6808d298a4010aaab341c66699f1b87eec9c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e1a50a20b3383e3b0959e5b32ef429425fd6be5b"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
|
@ -15,11 +15,11 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="05a36844c1046a1eb07d5b1325f85ed741f961ea">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="d7385b79e68d4ad662cacf810506e9ee53345d23"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="85ae6808d298a4010aaab341c66699f1b87eec9c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
|
||||
<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"/>
|
||||
|
@ -248,7 +248,7 @@ let gSyncUI = {
|
||||
},
|
||||
|
||||
handleToolbarButton: function SUI_handleStatusbarButton() {
|
||||
if (this._needsSetup())
|
||||
if (this._needsSetup() || this._loginFailed())
|
||||
this.openSetup();
|
||||
else
|
||||
this.doSync();
|
||||
|
@ -6795,9 +6795,13 @@ var gIdentityHandler = {
|
||||
delete this._permissionList;
|
||||
return this._permissionList = document.getElementById("identity-popup-permission-list");
|
||||
},
|
||||
get _permissionSubviewList () {
|
||||
delete this._permissionSubviewList;
|
||||
return this._permissionSubviewList = document.getElementById("identity-popup-permission-subview-list");
|
||||
get _permissionSubviewListPageFunctionality () {
|
||||
delete this._permissionSubviewListPageFunctionality;
|
||||
return this._permissionSubviewListPageFunctionality = document.getElementById("permission-subview-list-page-functionality");
|
||||
},
|
||||
get _permissionSubviewListSystemAccess () {
|
||||
delete this._permissionSubviewListSystemAccess;
|
||||
return this._permissionSubviewListSystemAccess = document.getElementById("permission-subview-list-system-access");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -6819,7 +6823,8 @@ var gIdentityHandler = {
|
||||
this._identityIcon = document.getElementById("page-proxy-favicon");
|
||||
this._permissionsContainer = document.getElementById("identity-popup-permissions");
|
||||
this._permissionList = document.getElementById("identity-popup-permission-list");
|
||||
this._permissionSubviewList = document.getElementById("identity-popup-permission-subview-list");
|
||||
this._permissionSubviewListPageFunctionality = document.getElementById("permission-subview-list-page-functionality");
|
||||
this._permissionSubviewListSystemAccess = document.getElementById("permission-subview-list-system-access");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -7290,26 +7295,33 @@ var gIdentityHandler = {
|
||||
},
|
||||
|
||||
updateSitePermissions: function () {
|
||||
while (this._permissionList.hasChildNodes())
|
||||
this._permissionList.removeChild(this._permissionList.lastChild);
|
||||
|
||||
while (this._permissionSubviewList.hasChildNodes())
|
||||
this._permissionSubviewList.removeChild(this._permissionSubviewList.lastChild);
|
||||
// Clear all lists and then repopulate them.
|
||||
this._permissionList.textContent = "";
|
||||
this._permissionSubviewListPageFunctionality.textContent = "";
|
||||
this._permissionSubviewListSystemAccess.textContent = "";
|
||||
|
||||
let uri = gBrowser.currentURI;
|
||||
|
||||
for (let permission of SitePermissions.listPermissions()) {
|
||||
for (let permission of SitePermissions.listPageFunctionalityPermissions()) {
|
||||
let state = SitePermissions.get(uri, permission);
|
||||
let item = this._createPermissionItem(permission, state);
|
||||
this._permissionSubviewListPageFunctionality.appendChild(item);
|
||||
}
|
||||
|
||||
for (let permission of SitePermissions.listSystemAccessPermissions()) {
|
||||
let state = SitePermissions.get(uri, permission);
|
||||
let item = this._createPermissionItem(permission, state);
|
||||
this._permissionSubviewListSystemAccess.appendChild(item);
|
||||
}
|
||||
|
||||
for (let permission of SitePermissions.listPermissions()) {
|
||||
// Add to the main view only if there is a known / non-default
|
||||
// value for the permission for this site.
|
||||
let state = SitePermissions.get(uri, permission);
|
||||
if (state != SitePermissions.UNKNOWN) {
|
||||
this._permissionList.appendChild(item.cloneNode(true));
|
||||
let item = this._createPermissionItem(permission, state);
|
||||
this._permissionList.appendChild(item);
|
||||
}
|
||||
|
||||
// Add all permissions to the subview.
|
||||
this._permissionSubviewList.appendChild(item);
|
||||
}
|
||||
|
||||
this._permissionsContainer.hidden = !this._permissionList.hasChildNodes();
|
||||
|
@ -46,14 +46,14 @@ add_task(function* testSubviewListing() {
|
||||
info("Opening control center and expanding permissions subview");
|
||||
gIdentityHandler._identityBox.click();
|
||||
|
||||
let menulists = gIdentityHandler._permissionSubviewList.querySelectorAll("menulist");
|
||||
let perms = SitePermissions.listPermissions();
|
||||
info("Checking 'Page Functionality' permissions");
|
||||
let pageFunctionalityMenulists = gIdentityHandler._permissionSubviewListPageFunctionality.querySelectorAll("menulist");
|
||||
let pageFunctionalityPerms = SitePermissions.listPageFunctionalityPermissions();
|
||||
is(pageFunctionalityMenulists.length, pageFunctionalityPerms.length, "One menulist for each permission");
|
||||
|
||||
is(menulists.length, perms.length, "One menulist for each permission");
|
||||
|
||||
for (let i = 0; i < menulists.length; i++) {
|
||||
let menulist = menulists[i];
|
||||
let perm = perms[i];
|
||||
for (let i = 0; i < pageFunctionalityMenulists.length; i++) {
|
||||
let menulist = pageFunctionalityMenulists[i];
|
||||
let perm = pageFunctionalityPerms[i];
|
||||
let expectedValue = SitePermissions.get(gBrowser.currentURI, perm);
|
||||
if (expectedValue == SitePermissions.UNKNOWN) {
|
||||
expectedValue = SitePermissions.getDefault(perm);
|
||||
@ -62,5 +62,23 @@ add_task(function* testSubviewListing() {
|
||||
is(menulist.id, "identity-popup-permission:" + perm, "Correct id for menulist: " + perm);
|
||||
is(menulist.value, expectedValue, "Correct value on menulist: " + perm);
|
||||
}
|
||||
|
||||
info("Checking 'System Access' permissions");
|
||||
let systemAccessMenulists = gIdentityHandler._permissionSubviewListSystemAccess.querySelectorAll("menulist");
|
||||
let systemAccessPerms = SitePermissions.listSystemAccessPermissions();
|
||||
is(systemAccessMenulists.length, systemAccessPerms.length, "One menulist for each permission");
|
||||
|
||||
for (let i = 0; i < systemAccessMenulists.length; i++) {
|
||||
let menulist = systemAccessMenulists[i];
|
||||
let perm = systemAccessPerms[i];
|
||||
let expectedValue = SitePermissions.get(gBrowser.currentURI, perm);
|
||||
if (expectedValue == SitePermissions.UNKNOWN) {
|
||||
expectedValue = SitePermissions.getDefault(perm);
|
||||
}
|
||||
|
||||
is(menulist.id, "identity-popup-permission:" + perm, "Correct id for menulist: " + perm);
|
||||
is(menulist.value, expectedValue, "Correct value on menulist: " + perm);
|
||||
}
|
||||
|
||||
gIdentityHandler._identityPopup.hidden = true;
|
||||
});
|
||||
|
@ -172,7 +172,16 @@
|
||||
</vbox>
|
||||
|
||||
<vbox id="identity-popup-permissionsView-body">
|
||||
<vbox id="identity-popup-permission-subview-list"/>
|
||||
<vbox id="identity-popup-permission-subview-list">
|
||||
<label class="identity-popup-subheadline"
|
||||
value="&identity.permissionsPageFunctionality;"
|
||||
crop="end"/>
|
||||
<vbox id="permission-subview-list-page-functionality"></vbox>
|
||||
<label class="identity-popup-subheadline"
|
||||
value="&identity.permissionsSystemAccess;"
|
||||
crop="end"/>
|
||||
<vbox id="permission-subview-list-system-access"></vbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</panelview>
|
||||
</panelmultiview>
|
||||
|
@ -542,11 +542,11 @@ html[dir="rtl"] .room-entry-call-btn {
|
||||
|
||||
html[dir="rtl"] .room-entry-context-actions > .dropdown-menu {
|
||||
right: auto;
|
||||
left: 45px;
|
||||
left: 21px;
|
||||
}
|
||||
|
||||
.room-entry-context-actions > .dropdown-menu {
|
||||
right: 45px;
|
||||
right: 21px;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
}
|
||||
|
@ -175,6 +175,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
||||
"resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
|
||||
"@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
|
||||
|
||||
const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
|
||||
const PREF_PLUGINS_UPDATEURL = "plugins.update.url";
|
||||
|
||||
@ -507,6 +510,12 @@ BrowserGlue.prototype = {
|
||||
case "autocomplete-did-enter-text":
|
||||
this._handleURLBarTelemetry(subject.QueryInterface(Ci.nsIAutoCompleteInput));
|
||||
break;
|
||||
case "tablet-mode-change":
|
||||
if (data == "tablet-mode") {
|
||||
Services.telemetry.getHistogramById("FX_TABLET_MODE_USED_DURING_SESSION")
|
||||
.add(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@ -613,6 +622,7 @@ BrowserGlue.prototype = {
|
||||
os.addObserver(this, "flash-plugin-hang", false);
|
||||
os.addObserver(this, "xpi-signature-changed", false);
|
||||
os.addObserver(this, "autocomplete-did-enter-text", false);
|
||||
os.addObserver(this, "tablet-mode-change", false);
|
||||
|
||||
ExtensionManagement.registerScript("chrome://browser/content/ext-utils.js");
|
||||
ExtensionManagement.registerScript("chrome://browser/content/ext-browserAction.js");
|
||||
@ -668,6 +678,7 @@ BrowserGlue.prototype = {
|
||||
os.removeObserver(this, "flash-plugin-hang");
|
||||
os.removeObserver(this, "xpi-signature-changed");
|
||||
os.removeObserver(this, "autocomplete-did-enter-text");
|
||||
os.removeObserver(this, "tablet-mode-change");
|
||||
},
|
||||
|
||||
_onAppDefaults: function BG__onAppDefaults() {
|
||||
@ -1041,6 +1052,13 @@ BrowserGlue.prototype = {
|
||||
let scaling = aWindow.devicePixelRatio * 100;
|
||||
Services.telemetry.getHistogramById(SCALING_PROBE_NAME).add(scaling);
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
if (WindowsUIUtils.inTabletMode) {
|
||||
Services.telemetry.getHistogramById("FX_TABLET_MODE_USED_DURING_SESSION")
|
||||
.add(1);
|
||||
}
|
||||
#endif
|
||||
},
|
||||
|
||||
// the first browser window has finished initializing
|
||||
|
@ -461,21 +461,17 @@ this.PlacesUIUtils = {
|
||||
showBookmarkDialog:
|
||||
function PUIU_showBookmarkDialog(aInfo, aParentWindow) {
|
||||
// Preserve size attributes differently based on the fact the dialog has
|
||||
// a folder picker or not. If the picker is visible, the dialog should
|
||||
// be resizable since it may not show enough content for the folders
|
||||
// hierarchy.
|
||||
// a folder picker or not, since it needs more horizontal space than the
|
||||
// other controls.
|
||||
let hasFolderPicker = !("hiddenRows" in aInfo) ||
|
||||
aInfo.hiddenRows.indexOf("folderPicker") == -1;
|
||||
// Use a different chrome url, since this allows to persist different sizes,
|
||||
// based on resizability of the dialog.
|
||||
// Use a different chrome url to persist different sizes.
|
||||
let dialogURL = hasFolderPicker ?
|
||||
"chrome://browser/content/places/bookmarkProperties2.xul" :
|
||||
"chrome://browser/content/places/bookmarkProperties.xul";
|
||||
|
||||
let features =
|
||||
"centerscreen,chrome,modal,resizable=" + (hasFolderPicker ? "yes" : "no");
|
||||
|
||||
aParentWindow.openDialog(dialogURL, "", features, aInfo);
|
||||
let features = "centerscreen,chrome,modal,resizable=yes";
|
||||
aParentWindow.openDialog(dialogURL, "", features, aInfo);
|
||||
return ("performed" in aInfo && aInfo.performed);
|
||||
},
|
||||
|
||||
|
@ -72,6 +72,8 @@ const LIVEMARK_CONTAINER = 2;
|
||||
const ACTION_EDIT = 0;
|
||||
const ACTION_ADD = 1;
|
||||
|
||||
let elementsHeight = new Map();
|
||||
|
||||
var BookmarkPropertiesPanel = {
|
||||
|
||||
/** UI Text Strings */
|
||||
@ -271,6 +273,43 @@ var BookmarkPropertiesPanel = {
|
||||
var acceptButton = document.documentElement.getButton("accept");
|
||||
acceptButton.label = this._getAcceptLabel();
|
||||
|
||||
// Do not use sizeToContent, otherwise, due to bug 90276, the dialog will
|
||||
// grow at every opening.
|
||||
// Since elements can be uncollapsed asynchronously, we must observe their
|
||||
// mutations and resize the dialog using a cached element size.
|
||||
this._height = window.outerHeight;
|
||||
this._mutationObserver = new MutationObserver(mutations => {
|
||||
for (let mutation of mutations) {
|
||||
let target = mutation.target;
|
||||
let id = target.id;
|
||||
if (!/^editBMPanel_.*(Row|Checkbox)$/.test(id))
|
||||
continue;
|
||||
|
||||
let collapsed = target.getAttribute("collapsed") === "true";
|
||||
let wasCollapsed = mutation.oldValue === "true";
|
||||
if (collapsed == wasCollapsed)
|
||||
continue;
|
||||
|
||||
if (collapsed) {
|
||||
this._height -= elementsHeight.get(id);
|
||||
elementsHeight.delete(id);
|
||||
} else {
|
||||
elementsHeight.set(id, target.boxObject.height);
|
||||
this._height += elementsHeight.get(id);
|
||||
}
|
||||
window.resizeTo(window.outerWidth, this._height);
|
||||
}
|
||||
});
|
||||
|
||||
this._mutationObserver.observe(document,
|
||||
{ subtree: true,
|
||||
attributeOldValue: true,
|
||||
attributeFilter: ["collapsed"] });
|
||||
|
||||
// Some controls are flexible and we want to update their cached size when
|
||||
// the dialog is resized.
|
||||
window.addEventListener("resize", this);
|
||||
|
||||
this._beginBatch();
|
||||
|
||||
switch (this._action) {
|
||||
@ -300,25 +339,6 @@ var BookmarkPropertiesPanel = {
|
||||
break;
|
||||
}
|
||||
|
||||
// Adjust the dialog size to the changes done by initPanel. This is necessary because
|
||||
// initPanel, which shows and hides elements, may run after some async work was done
|
||||
// here - i.e. after the DOM load event was processed.
|
||||
window.sizeToContent();
|
||||
|
||||
// When collapsible elements change their collapsed attribute we must
|
||||
// resize the dialog.
|
||||
// sizeToContent is not usable due to bug 90276, so we'll use resizeTo
|
||||
// instead and cache the element size. See WSucks in the legacy
|
||||
// UI code (addBookmark2.js).
|
||||
if (!this._element("tagsRow").collapsed) {
|
||||
this._element("tagsSelectorRow")
|
||||
.addEventListener("DOMAttrModified", this, false);
|
||||
}
|
||||
if (!this._element("folderRow").collapsed) {
|
||||
this._element("folderTreeRow")
|
||||
.addEventListener("DOMAttrModified", this, false);
|
||||
}
|
||||
|
||||
if (!gEditItemOverlay.readOnly) {
|
||||
// Listen on uri fields to enable accept button if input is valid
|
||||
if (this._itemType == BOOKMARK_ITEM) {
|
||||
@ -330,12 +350,9 @@ var BookmarkPropertiesPanel = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.sizeToContent();
|
||||
}),
|
||||
|
||||
// nsIDOMEventListener
|
||||
_elementsHeight: [],
|
||||
handleEvent: function BPP_handleEvent(aEvent) {
|
||||
var target = aEvent.target;
|
||||
switch (aEvent.type) {
|
||||
@ -347,24 +364,11 @@ var BookmarkPropertiesPanel = {
|
||||
.getButton("accept").disabled = !this._inputIsValid();
|
||||
}
|
||||
break;
|
||||
|
||||
case "DOMAttrModified":
|
||||
// this is called when collapsing a node, but also its direct children,
|
||||
// we only need to resize when the original node changes.
|
||||
if ((target.id == "editBMPanel_tagsSelectorRow" ||
|
||||
target.id == "editBMPanel_folderTreeRow") &&
|
||||
aEvent.attrName == "collapsed" &&
|
||||
target == aEvent.originalTarget) {
|
||||
var id = target.id;
|
||||
var newHeight = window.outerHeight;
|
||||
if (aEvent.newValue) // is collapsed
|
||||
newHeight -= this._elementsHeight[id];
|
||||
else {
|
||||
this._elementsHeight[id] = target.boxObject.height;
|
||||
newHeight += this._elementsHeight[id];
|
||||
}
|
||||
|
||||
window.resizeTo(window.outerWidth, newHeight);
|
||||
case "resize":
|
||||
for (let [id, oldHeight] of elementsHeight) {
|
||||
let newHeight = document.getElementById(id).boxObject.height;
|
||||
this._height += - oldHeight + newHeight;
|
||||
elementsHeight.set(id, newHeight);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -418,12 +422,13 @@ var BookmarkPropertiesPanel = {
|
||||
|
||||
onDialogUnload() {
|
||||
// gEditItemOverlay does not exist anymore here, so don't rely on it.
|
||||
this._mutationObserver.disconnect();
|
||||
delete this._mutationObserver;
|
||||
|
||||
window.removeEventListener("resize", this);
|
||||
|
||||
// Calling removeEventListener with arguments which do not identify any
|
||||
// currently registered EventListener on the EventTarget has no effect.
|
||||
this._element("tagsSelectorRow")
|
||||
.removeEventListener("DOMAttrModified", this, false);
|
||||
this._element("folderTreeRow")
|
||||
.removeEventListener("DOMAttrModified", this, false);
|
||||
this._element("locationField")
|
||||
.removeEventListener("input", this, false);
|
||||
},
|
||||
@ -578,48 +583,44 @@ var BookmarkPropertiesPanel = {
|
||||
childItemsTransactions);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a transaction for creating a new live-bookmark item representing
|
||||
* the various fields and opening arguments of the dialog.
|
||||
*/
|
||||
_getCreateNewLivemarkTransaction:
|
||||
function BPP__getCreateNewLivemarkTransaction(aContainer, aIndex) {
|
||||
return new PlacesCreateLivemarkTransaction(this._feedURI, this._siteURI,
|
||||
this._title,
|
||||
aContainer, aIndex);
|
||||
},
|
||||
|
||||
_createNewItem: function BPP__getCreateItemTransaction() {
|
||||
var [container, index] = this._getInsertionPointDetails();
|
||||
var txn;
|
||||
|
||||
_createNewItem: Task.async(function* () {
|
||||
let [container, index] = this._getInsertionPointDetails();
|
||||
let txn;
|
||||
switch (this._itemType) {
|
||||
case BOOKMARK_FOLDER:
|
||||
txn = this._getCreateNewFolderTransaction(container, index);
|
||||
break;
|
||||
case LIVEMARK_CONTAINER:
|
||||
txn = this._getCreateNewLivemarkTransaction(container, index);
|
||||
txn = new PlacesCreateLivemarkTransaction(this._feedURI, this._siteURI,
|
||||
this._title, container, index);
|
||||
break;
|
||||
default: // BOOKMARK_ITEM
|
||||
txn = this._getCreateNewBookmarkTransaction(container, index);
|
||||
}
|
||||
|
||||
PlacesUtils.transactionManager.doTransaction(txn);
|
||||
this._itemId = PlacesUtils.bookmarks.getIdForItemAt(container, index);
|
||||
// This is a temporary hack until we use PlacesTransactions.jsm
|
||||
if (txn._promise) {
|
||||
yield txn._promise;
|
||||
}
|
||||
|
||||
let folderGuid = yield PlacesUtils.promiseItemGuid(container);
|
||||
let bm = yield PlacesUtils.bookmarks.fetch({
|
||||
parentGuid: folderGuid,
|
||||
index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
});
|
||||
this._itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
|
||||
return Object.freeze({
|
||||
itemId: this._itemId,
|
||||
get bookmarkGuid() {
|
||||
throw new Error("Node-like bookmarkGuid getter called even though " +
|
||||
"async transactions are disabled");
|
||||
},
|
||||
bookmarkGuid: bm.guid,
|
||||
title: this._title,
|
||||
uri: this._uri ? this._uri.spec : "",
|
||||
type: this._itemType == BOOKMARK_ITEM ?
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_URI :
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
||||
_promiseNewItem: Task.async(function* () {
|
||||
if (!PlacesUIUtils.useAsyncTransactions)
|
||||
|
@ -34,6 +34,12 @@ let gEditItemOverlay = {
|
||||
let isItem = itemId != -1;
|
||||
let isFolderShortcut = isItem &&
|
||||
node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
|
||||
let isTag = node && PlacesUtils.nodeIsTagQuery(node);
|
||||
if (isTag) {
|
||||
itemId = PlacesUtils.getConcreteItemId(node);
|
||||
// For now we don't have access to the item guid synchronously for tags,
|
||||
// so we'll need to fetch it later.
|
||||
}
|
||||
let isURI = node && PlacesUtils.nodeIsURI(node);
|
||||
let uri = isURI ? NetUtil.newURI(node.uri) : null;
|
||||
let title = node ? node.title : null;
|
||||
@ -55,7 +61,7 @@ let gEditItemOverlay = {
|
||||
isURI, uri, title,
|
||||
isBookmark, isFolderShortcut, isParentReadOnly,
|
||||
bulkTagging, uris,
|
||||
visibleRows, postData };
|
||||
visibleRows, postData, isTag };
|
||||
},
|
||||
|
||||
get initialized() {
|
||||
@ -89,12 +95,13 @@ let gEditItemOverlay = {
|
||||
// This pane is read-only if:
|
||||
// * the panel is not initialized
|
||||
// * the node is a folder shortcut
|
||||
// * the node is not bookmarked
|
||||
// * the node is child of a read-only container and is not a bookmarked URI
|
||||
// * the node is not bookmarked and not a tag container
|
||||
// * the node is child of a read-only container and is not a bookmarked
|
||||
// URI nor a tag container
|
||||
return !this.initialized ||
|
||||
this._paneInfo.isFolderShortcut ||
|
||||
!this._paneInfo.isItem ||
|
||||
(this._paneInfo.isParentReadOnly && !this._paneInfo.isBookmark);
|
||||
(!this._paneInfo.isItem && !this._paneInfo.isTag) ||
|
||||
(this._paneInfo.isParentReadOnly && !this._paneInfo.isBookmark && !this._paneInfo.isTag);
|
||||
},
|
||||
|
||||
// the first field which was edited after this panel was initialized for
|
||||
@ -518,7 +525,7 @@ let gEditItemOverlay = {
|
||||
},
|
||||
|
||||
onNamePickerChange() {
|
||||
if (this.readOnly || !this._paneInfo.isItem)
|
||||
if (this.readOnly || !(this._paneInfo.isItem || this._paneInfo.isTag))
|
||||
return;
|
||||
|
||||
// Here we update either the item title or its cached static title
|
||||
@ -536,9 +543,13 @@ let gEditItemOverlay = {
|
||||
PlacesUtils.transactionManager.doTransaction(txn);
|
||||
return;
|
||||
}
|
||||
let guid = this._paneInfo.itemGuid;
|
||||
PlacesTransactions.EditTitle({ guid, title: newTitle })
|
||||
.transact().catch(Components.utils.reportError);
|
||||
Task.spawn(function* () {
|
||||
let guid = this._paneInfo.isTag
|
||||
? (yield PlacesUtils.promiseItemGuid(this._paneInfo.itemId))
|
||||
: this._paneInfo.itemGuid;
|
||||
PlacesTransactions.EditTitle({ guid, title: newTitle })
|
||||
.transact().catch(Components.utils.reportError);
|
||||
}).catch(Cu.reportError);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -24,7 +24,9 @@
|
||||
<column flex="1" id="editBMPanel_editColumn" />
|
||||
</columns>
|
||||
<rows id="editBMPanel_rows">
|
||||
<row align="center" id="editBMPanel_nameRow">
|
||||
<row id="editBMPanel_nameRow"
|
||||
align="center"
|
||||
collapsed="true">
|
||||
<label value="&editBookmarkOverlay.name.label;"
|
||||
class="editBMPanel_rowLabel"
|
||||
accesskey="&editBookmarkOverlay.name.accesskey;"
|
||||
@ -33,7 +35,9 @@
|
||||
onchange="gEditItemOverlay.onNamePickerChange();"/>
|
||||
</row>
|
||||
|
||||
<row align="center" id="editBMPanel_locationRow">
|
||||
<row id="editBMPanel_locationRow"
|
||||
align="center"
|
||||
collapsed="true">
|
||||
<label value="&editBookmarkOverlay.location.label;"
|
||||
class="editBMPanel_rowLabel"
|
||||
accesskey="&editBookmarkOverlay.location.accesskey;"
|
||||
@ -43,7 +47,9 @@
|
||||
onchange="gEditItemOverlay.onLocationFieldChange();"/>
|
||||
</row>
|
||||
|
||||
<row align="center" id="editBMPanel_folderRow">
|
||||
<row id="editBMPanel_folderRow"
|
||||
align="center"
|
||||
collapsed="true">
|
||||
<label value="&editBookmarkOverlay.folder.label;"
|
||||
class="editBMPanel_rowLabel"
|
||||
control="editBMPanel_folderMenuList"/>
|
||||
@ -76,7 +82,9 @@
|
||||
</hbox>
|
||||
</row>
|
||||
|
||||
<row id="editBMPanel_folderTreeRow" collapsed="true" flex="1">
|
||||
<row id="editBMPanel_folderTreeRow"
|
||||
collapsed="true"
|
||||
flex="1">
|
||||
<spacer/>
|
||||
<vbox flex="1">
|
||||
<tree id="editBMPanel_folderTree"
|
||||
@ -103,7 +111,9 @@
|
||||
</vbox>
|
||||
</row>
|
||||
|
||||
<row align="center" id="editBMPanel_tagsRow">
|
||||
<row id="editBMPanel_tagsRow"
|
||||
align="center"
|
||||
collapsed="true">
|
||||
<label value="&editBookmarkOverlay.tags.label;"
|
||||
class="editBMPanel_rowLabel"
|
||||
accesskey="&editBookmarkOverlay.tags.accesskey;"
|
||||
@ -136,7 +146,9 @@
|
||||
height="150"/>
|
||||
</row>
|
||||
|
||||
<row align="center" id="editBMPanel_keywordRow">
|
||||
<row id="editBMPanel_keywordRow"
|
||||
align="center"
|
||||
collapsed="true">
|
||||
<observes element="additionalInfoBroadcaster" attribute="hidden"/>
|
||||
<label value="&editBookmarkOverlay.keyword.label;"
|
||||
class="editBMPanel_rowLabel"
|
||||
@ -146,7 +158,8 @@
|
||||
onchange="gEditItemOverlay.onKeywordFieldChange();"/>
|
||||
</row>
|
||||
|
||||
<row id="editBMPanel_descriptionRow">
|
||||
<row id="editBMPanel_descriptionRow"
|
||||
collapsed="true">
|
||||
<observes element="additionalInfoBroadcaster" attribute="hidden"/>
|
||||
<label value="&editBookmarkOverlay.description.label;"
|
||||
class="editBMPanel_rowLabel"
|
||||
@ -161,6 +174,7 @@
|
||||
</grid>
|
||||
|
||||
<checkbox id="editBMPanel_loadInSidebarCheckbox"
|
||||
collapsed="true"
|
||||
label="&editBookmarkOverlay.loadInSidebar.label;"
|
||||
accesskey="&editBookmarkOverlay.loadInSidebar.accesskey;"
|
||||
oncommand="gEditItemOverlay.onLoadInSidebarCheckboxCommand();">
|
||||
|
@ -25,6 +25,8 @@ support-files =
|
||||
pageopeningwindow.html
|
||||
[browser_bookmarkProperties_addFolderDefaultButton.js]
|
||||
[browser_bookmarkProperties_addKeywordForThisSearch.js]
|
||||
[browser_bookmarkProperties_addLivemark.js]
|
||||
[browser_bookmarkProperties_editTagContainer.js]
|
||||
[browser_bookmarkProperties_readOnlyRoot.js]
|
||||
[browser_bookmarksProperties.js]
|
||||
[browser_drag_bookmarks_on_toolbar.js]
|
||||
|
@ -0,0 +1,39 @@
|
||||
"use strict"
|
||||
|
||||
add_task(function* () {
|
||||
info("Add a live bookmark editing its data");
|
||||
|
||||
yield withSidebarTree("bookmarks", function* (tree) {
|
||||
let itemId = PlacesUIUtils.leftPaneQueries["UnfiledBookmarks"];
|
||||
tree.selectItems([itemId]);
|
||||
|
||||
yield withBookmarksDialog(
|
||||
true,
|
||||
function openDialog() {
|
||||
PlacesCommandHook.addLiveBookmark("http://livemark.com/",
|
||||
"livemark", "description");
|
||||
},
|
||||
function* test(dialogWin) {
|
||||
let promiseTitleChangeNotification = promiseBookmarksNotification(
|
||||
"onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "modified");
|
||||
|
||||
fillBookmarkTextField("editBMPanel_namePicker", "modified", dialogWin);
|
||||
|
||||
yield promiseTitleChangeNotification;
|
||||
|
||||
let bookmark = yield PlacesUtils.bookmarks.fetch({
|
||||
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
|
||||
index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
});
|
||||
|
||||
is(bookmark.title, "modified", "folder name has been edited");
|
||||
|
||||
let livemark = yield PlacesUtils.livemarks.getLivemark({
|
||||
guid: bookmark.guid
|
||||
});
|
||||
is(livemark.feedURI.spec, "http://livemark.com/", "livemark has the correct url");
|
||||
is(livemark.title, "modified", "livemark has the correct title");
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,71 @@
|
||||
"use strict"
|
||||
|
||||
add_task(function* () {
|
||||
info("Bug 479348 - Properties on a root should be read-only.");
|
||||
let uri = NetUtil.newURI("http://example.com/");
|
||||
let bm = yield PlacesUtils.bookmarks.insert({
|
||||
url: uri.spec,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid
|
||||
});
|
||||
registerCleanupFunction(function* () {
|
||||
yield PlacesUtils.bookmarks.remove(bm);
|
||||
});
|
||||
|
||||
PlacesUtils.tagging.tagURI(uri, ["tag1"]);
|
||||
|
||||
let library = yield promiseLibrary();
|
||||
let PlacesOrganizer = library.PlacesOrganizer;
|
||||
registerCleanupFunction(function* () {
|
||||
yield promiseLibraryClosed(library);
|
||||
});
|
||||
|
||||
PlacesOrganizer.selectLeftPaneQuery("Tags");
|
||||
let tree = PlacesOrganizer._places;
|
||||
let tagsContainer = tree.selectedNode;
|
||||
tagsContainer.containerOpen = true;
|
||||
let fooTag = tagsContainer.getChild(0);
|
||||
let tagNode = fooTag;
|
||||
tree.selectNode(fooTag);
|
||||
is(tagNode.title, 'tag1', "tagNode title is correct");
|
||||
|
||||
ok(tree.controller.isCommandEnabled("placesCmd_show:info"),
|
||||
"'placesCmd_show:info' on current selected node is enabled");
|
||||
|
||||
yield withBookmarksDialog(
|
||||
true,
|
||||
function openDialog() {
|
||||
tree.controller.doCommand("placesCmd_show:info");
|
||||
},
|
||||
function* test(dialogWin) {
|
||||
// Check that the dialog is not read-only.
|
||||
ok(!dialogWin.gEditItemOverlay.readOnly, "Dialog should not be read-only");
|
||||
|
||||
// Check that name picker is not read only
|
||||
let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
|
||||
ok(!namepicker.readOnly, "Name field should not be read-only");
|
||||
is(namepicker.value, "tag1", "Node title is correct");
|
||||
|
||||
let promiseTitleChangeNotification = promiseBookmarksNotification(
|
||||
"onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "tag2");
|
||||
|
||||
fillBookmarkTextField("editBMPanel_namePicker", "tag2", dialogWin);
|
||||
|
||||
yield promiseTitleChangeNotification;
|
||||
|
||||
is(namepicker.value, "tag2", "Node title has been properly edited");
|
||||
|
||||
// Check the shortcut's title.
|
||||
is(tree.selectedNode.title, "tag2", "The node has the correct title");
|
||||
|
||||
// Check the tags have been edited.
|
||||
let tags = PlacesUtils.tagging.getTagsForURI(uri);
|
||||
is(tags.length, 1, "Found the right number of tags");
|
||||
ok(tags.includes("tag2"), "Found the expected tag");
|
||||
}
|
||||
);
|
||||
|
||||
// Check the tag change has been reverted.
|
||||
let tags = PlacesUtils.tagging.getTagsForURI(uri);
|
||||
is(tags.length, 1, "Found the right number of tags");
|
||||
ok(tags.includes("tag1"), "Found the expected tag");
|
||||
});
|
@ -297,7 +297,7 @@ let withBookmarksDialog = Task.async(function* (autoCancel, openFn, taskFn) {
|
||||
win.addEventListener("load", function load() {
|
||||
win.removeEventListener("load", load);
|
||||
ok(win.location.href.startsWith("chrome://browser/content/places/bookmarkProperties"),
|
||||
"The bookmark properties dialog is ready");
|
||||
"The bookmark properties dialog is open");
|
||||
// This is needed for the overlay.
|
||||
waitForFocus(() => {
|
||||
resolve(win);
|
||||
@ -318,7 +318,9 @@ let withBookmarksDialog = Task.async(function* (autoCancel, openFn, taskFn) {
|
||||
let dialogWin = yield dialogPromise;
|
||||
|
||||
// Ensure overlay is loaded
|
||||
ok(dialogWin.gEditItemOverlay.initialized, "EditItemOverlay is initialized");
|
||||
info("waiting for the overlay to be loaded");
|
||||
yield waitForCondition(() => dialogWin.gEditItemOverlay.initialized,
|
||||
"EditItemOverlay should be initialized");
|
||||
|
||||
info("withBookmarksDialog: executing the task");
|
||||
try {
|
||||
|
@ -41,6 +41,10 @@ body[globalTpEnabled] .showGlobalTpDisabled {
|
||||
line-height: 2.5em;
|
||||
}
|
||||
|
||||
#bar:-moz-dir(rtl) {
|
||||
background-position: right 22px top 50%;
|
||||
}
|
||||
|
||||
#main {
|
||||
padding: 0 2em;
|
||||
flex: 1;
|
||||
@ -74,6 +78,10 @@ li {
|
||||
background-position: 0 50%;
|
||||
}
|
||||
|
||||
li:-moz-dir(rtl) {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
|
||||
#forgotten > li {
|
||||
background-image: url("chrome://browser/skin/privatebrowsing/check.png");
|
||||
}
|
||||
|
@ -976,6 +976,36 @@ InspectorPanel.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Use in Console.
|
||||
*
|
||||
* Takes the currently selected node in the inspector and assigns it to a
|
||||
* temp variable on the content window. Also opens the split console and
|
||||
* autofills it with the temp variable.
|
||||
*/
|
||||
useInConsole: function() {
|
||||
this._toolbox.openSplitConsole().then(() => {
|
||||
let panel = this._toolbox.getPanel("webconsole");
|
||||
let jsterm = panel.hud.jsterm;
|
||||
|
||||
let evalString = `let i = 0;
|
||||
while (window.hasOwnProperty("temp" + i) && i < 1000) {
|
||||
i++;
|
||||
}
|
||||
window["temp" + i] = $0;
|
||||
"temp" + i;
|
||||
`;
|
||||
|
||||
let options = {
|
||||
selectedNodeActor: this.selection.nodeFront.actorID,
|
||||
};
|
||||
jsterm.requestEvaluation(evalString, options).then((res) => {
|
||||
jsterm.setInputValue(res.result);
|
||||
this.emit("console-var-ready");
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear any pseudo-class locks applied to the current hierarchy.
|
||||
*/
|
||||
|
@ -55,6 +55,9 @@
|
||||
<menuitem id="node-menu-showdomproperties"
|
||||
label="&inspectorShowDOMProperties.label;"
|
||||
oncommand="inspector.showDOMProperties()"/>
|
||||
<menuitem id="node-menu-useinconsole"
|
||||
label="&inspectorUseInConsole.label;"
|
||||
oncommand="inspector.useInConsole()"/>
|
||||
<menuitem id="node-menu-expand"
|
||||
label="&inspectorExpandNode.label;"
|
||||
oncommand="inspector.expandNode()"/>
|
||||
|
@ -82,7 +82,8 @@ skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
|
||||
[browser_inspector_menu-01-sensitivity.js]
|
||||
[browser_inspector_menu-02-copy-items.js]
|
||||
[browser_inspector_menu-03-paste-items.js]
|
||||
[browser_inspector_menu-04-other.js]
|
||||
[browser_inspector_menu-04-use-in-console.js]
|
||||
[browser_inspector_menu-05-other.js]
|
||||
[browser_inspector_navigation.js]
|
||||
[browser_inspector_pane-toggle-01.js]
|
||||
[browser_inspector_pane-toggle-02.js]
|
||||
|
@ -16,36 +16,40 @@ const PASTE_MENU_ITEMS = [
|
||||
"node-menu-pastelastchild",
|
||||
];
|
||||
|
||||
const ACTIVE_ON_DOCTYPE_ITEMS = [
|
||||
"node-menu-showdomproperties",
|
||||
"node-menu-useinconsole"
|
||||
];
|
||||
|
||||
const ALL_MENU_ITEMS = [
|
||||
"node-menu-edithtml",
|
||||
"node-menu-copyinner",
|
||||
"node-menu-copyouter",
|
||||
"node-menu-copyuniqueselector",
|
||||
"node-menu-copyimagedatauri",
|
||||
"node-menu-showdomproperties",
|
||||
"node-menu-delete",
|
||||
"node-menu-pseudo-hover",
|
||||
"node-menu-pseudo-active",
|
||||
"node-menu-pseudo-focus",
|
||||
"node-menu-scrollnodeintoview",
|
||||
"node-menu-screenshotnode"
|
||||
].concat(PASTE_MENU_ITEMS);
|
||||
].concat(PASTE_MENU_ITEMS, ACTIVE_ON_DOCTYPE_ITEMS);
|
||||
|
||||
const ITEMS_WITHOUT_SHOWDOMPROPS =
|
||||
ALL_MENU_ITEMS.filter(item => item != "node-menu-showdomproperties");
|
||||
const INACTIVE_ON_DOCTYPE_ITEMS =
|
||||
ALL_MENU_ITEMS.filter(item => ACTIVE_ON_DOCTYPE_ITEMS.indexOf(item) === -1);
|
||||
|
||||
const TEST_CASES = [
|
||||
{
|
||||
desc: "doctype node with empty clipboard",
|
||||
selector: null,
|
||||
disabled: ITEMS_WITHOUT_SHOWDOMPROPS,
|
||||
disabled: INACTIVE_ON_DOCTYPE_ITEMS,
|
||||
},
|
||||
{
|
||||
desc: "doctype node with html on clipboard",
|
||||
clipboardData: "<p>some text</p>",
|
||||
clipboardDataType: "html",
|
||||
selector: null,
|
||||
disabled: ITEMS_WITHOUT_SHOWDOMPROPS,
|
||||
disabled: INACTIVE_ON_DOCTYPE_ITEMS,
|
||||
},
|
||||
{
|
||||
desc: "element node HTML on the clipboard",
|
||||
|
@ -0,0 +1,47 @@
|
||||
/* vim: set 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 "Use in Console" menu item
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html";
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
let { inspector, toolbox } = yield openInspectorForURL(TEST_URL);
|
||||
|
||||
yield testUseInConsole();
|
||||
|
||||
function* testUseInConsole() {
|
||||
info("Testing 'Use in Console' menu item.");
|
||||
let useInConsoleNode = inspector.panelDoc.getElementById("node-menu-useinconsole");
|
||||
|
||||
yield selectNode("#console-var", inspector);
|
||||
dispatchCommandEvent(useInConsoleNode);
|
||||
yield inspector.once("console-var-ready");
|
||||
|
||||
let hud = toolbox.getPanel("webconsole").hud;
|
||||
let jsterm = hud.jsterm;
|
||||
|
||||
let jstermInput = jsterm.hud.document.querySelector(".jsterm-input-node");
|
||||
ok(jstermInput.value === "temp0", "first console variable is named temp0");
|
||||
|
||||
let result = yield jsterm.execute();
|
||||
isnot(result.textContent.indexOf('<p id="console-var">'), -1, "variable temp0 references correct node");
|
||||
|
||||
yield selectNode("#console-var-multi", inspector);
|
||||
dispatchCommandEvent(useInConsoleNode);
|
||||
yield inspector.once("console-var-ready");
|
||||
|
||||
ok(jstermInput.value === "temp1", "second console variable is named temp1");
|
||||
|
||||
result = yield jsterm.execute();
|
||||
isnot(result.textContent.indexOf('<p id="console-var-multi">'), -1, "variable temp1 references correct node");
|
||||
|
||||
jsterm.clearHistory();
|
||||
}
|
||||
});
|
@ -71,12 +71,4 @@ add_task(function* () {
|
||||
// Follow up bug to add this test - https://bugzilla.mozilla.org/show_bug.cgi?id=1154107
|
||||
todo(false, "Verify that node is scrolled into the viewport.");
|
||||
}
|
||||
|
||||
function dispatchCommandEvent(node) {
|
||||
info("Dispatching command event on " + node);
|
||||
let commandEvent = document.createEvent("XULCommandEvent");
|
||||
commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
|
||||
false, false, null);
|
||||
node.dispatchEvent(commandEvent);
|
||||
}
|
||||
});
|
@ -20,6 +20,8 @@
|
||||
<div id="hiddenElement" style="display: none;">
|
||||
<p id="nestedHiddenElement">Visible element nested inside a non-visible element</p>
|
||||
</div>
|
||||
<p id="console-var">Paragraph for testing console variables</p>
|
||||
<p id="console-var-multi">Paragraph for testing multiple console variables</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -468,3 +468,15 @@ function redoChange(inspector) {
|
||||
inspector.markup.undo.redo();
|
||||
return mutated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a command event on a node (e.g. click on a contextual menu item).
|
||||
* @param {DOMNode} node
|
||||
*/
|
||||
function dispatchCommandEvent(node) {
|
||||
info("Dispatching command event on " + node);
|
||||
let commandEvent = document.createEvent("XULCommandEvent");
|
||||
commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
|
||||
false, false, null);
|
||||
node.dispatchEvent(commandEvent);
|
||||
}
|
||||
|
126
browser/devtools/memory/modules/census-view.js
Normal file
126
browser/devtools/memory/modules/census-view.js
Normal file
@ -0,0 +1,126 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains the tree view, displaying all the samples and frames
|
||||
* received from the proviler in a tree-like structure.
|
||||
*/
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const { L10N } = require("devtools/performance/global");
|
||||
const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
const { AbstractTreeItem } = require("resource:///modules/devtools/AbstractTreeItem.jsm");
|
||||
|
||||
const INDENTATION = exports.INDENTATION = 16; // px
|
||||
const DEFAULT_AUTO_EXPAND_DEPTH = 2;
|
||||
const COURSE_TYPES = ["objects", "scripts", "strings", "other"];
|
||||
|
||||
/**
|
||||
* Every instance of a `CensusView` represents a row in the census tree. The same
|
||||
* parent node is used for all rows.
|
||||
*
|
||||
* @param CensusView parent
|
||||
* The CensusView considered the parent row. Should be null
|
||||
* for root node.
|
||||
* @param {CensusTreeNode} censusTreeNode
|
||||
* Data from `takeCensus` transformed via `CensusTreeNode`.
|
||||
* @see browser/toolkit/heapsnapshot/census-tree-node.js
|
||||
* @param number level [optional]
|
||||
* The indentation level in the call tree. The root node is at level 0.
|
||||
* @param boolean hidden [optional]
|
||||
* Whether this node should be hidden and not contribute to depth/level
|
||||
* calculations. Defaults to false.
|
||||
* @param number autoExpandDepth [optional]
|
||||
* The depth to which the tree should automatically expand. Defaults to
|
||||
* the caller's `autoExpandDepth` if a caller exists, otherwise defaults
|
||||
* to DEFAULT_AUTO_EXPAND_DEPTH.
|
||||
*/
|
||||
function CensusView ({ caller, censusTreeNode, level, hidden, autoExpandDepth }) {
|
||||
AbstractTreeItem.call(this, { parent: caller, level: level|0 - (hidden ? 1 : 0) });
|
||||
|
||||
this.autoExpandDepth = autoExpandDepth != null
|
||||
? autoExpandDepth
|
||||
: caller ? caller.autoExpandDepth
|
||||
: DEFAULT_AUTO_EXPAND_DEPTH;
|
||||
|
||||
this.caller = caller;
|
||||
this.censusTreeNode = censusTreeNode;
|
||||
this.hidden = hidden;
|
||||
};
|
||||
|
||||
CensusView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
/**
|
||||
* Creates the view for this tree node.
|
||||
* @param nsIDOMNode document
|
||||
* @param nsIDOMNode arrowNode
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
_displaySelf: function (document, arrowNode) {
|
||||
let data = this.censusTreeNode;
|
||||
|
||||
let cells = [];
|
||||
|
||||
// Only use an arrow if there are children
|
||||
if (data.children && data.children.length) {
|
||||
cells.push(arrowNode);
|
||||
}
|
||||
|
||||
cells.push(this._createCell(document, data.name, "name"));
|
||||
|
||||
if (data.bytes != null) {
|
||||
cells.push(this._createCell(document, data.bytes, "bytes"));
|
||||
}
|
||||
if (data.count != null) {
|
||||
cells.push(this._createCell(document, data.count, "count"));
|
||||
}
|
||||
|
||||
let targetNode = document.createElement("li");
|
||||
targetNode.className = "heap-tree-item";
|
||||
targetNode.style.MozMarginStart = `${this.level * INDENTATION}px`;
|
||||
if (this.hidden) {
|
||||
targetNode.style.display = "none";
|
||||
}
|
||||
|
||||
for (let i = 0; i < cells.length; i++) {
|
||||
targetNode.appendChild(cells[i]);
|
||||
}
|
||||
|
||||
return targetNode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates this node in the call tree with the corresponding "callees".
|
||||
* These are defined in the `frame` data source for this call view.
|
||||
* @param array:AbstractTreeItem children
|
||||
*/
|
||||
_populateSelf: function (children) {
|
||||
let newLevel = this.level + 1;
|
||||
let data = this.censusTreeNode;
|
||||
|
||||
if (data.children) {
|
||||
for (let node of data.children) {
|
||||
children.push(new CensusView({
|
||||
caller: this,
|
||||
level: newLevel,
|
||||
censusTreeNode: node,
|
||||
}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Functions creating each cell in this call view.
|
||||
* Invoked by `_displaySelf`.
|
||||
*/
|
||||
_createCell: function (doc, value, type) {
|
||||
let cell = doc.createElement("span");
|
||||
cell.className = "plain heap-tree-cell";
|
||||
cell.setAttribute("type", type);
|
||||
cell.innerHTML = value;
|
||||
return cell;
|
||||
},
|
||||
});
|
||||
|
||||
exports.CensusView = CensusView;
|
87
browser/devtools/memory/modules/census.js
Normal file
87
browser/devtools/memory/modules/census.js
Normal file
@ -0,0 +1,87 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Utilities for interfacing with census reports from dbg.memory.takeCensus().
|
||||
*/
|
||||
|
||||
const COARSE_TYPES = new Set(["objects", "scripts", "strings", "other"]);
|
||||
|
||||
/**
|
||||
* Takes a report from a census (`dbg.memory.takeCensus()`) and the breakdown
|
||||
* used to generate the census and returns a structure used to render
|
||||
* a tree to display the data.
|
||||
*
|
||||
* Returns a recursive "CensusTreeNode" object, looking like:
|
||||
*
|
||||
* CensusTreeNode = {
|
||||
* // `children` if it exists, is sorted by `bytes`, if they are leaf nodes.
|
||||
* children: ?[<CensusTreeNode...>],
|
||||
* name: <?String>
|
||||
* count: <?Number>
|
||||
* bytes: <?Number>
|
||||
* }
|
||||
*
|
||||
* @param {Object} breakdown
|
||||
* @param {Object} report
|
||||
* @param {?String} name
|
||||
* @return {Object}
|
||||
*/
|
||||
function CensusTreeNode (breakdown, report, name) {
|
||||
this.name = name;
|
||||
this.bytes = void 0;
|
||||
this.count = void 0;
|
||||
this.children = void 0;
|
||||
|
||||
CensusTreeNodeBreakdowns[breakdown.by](this, breakdown, report);
|
||||
|
||||
if (this.children) {
|
||||
this.children.sort(sortByBytes);
|
||||
}
|
||||
}
|
||||
|
||||
CensusTreeNode.prototype = null;
|
||||
|
||||
/**
|
||||
* A series of functions to handle different breakdowns used by CensusTreeNode
|
||||
*/
|
||||
const CensusTreeNodeBreakdowns = Object.create(null);
|
||||
|
||||
CensusTreeNodeBreakdowns.count = function (node, breakdown, report) {
|
||||
if (breakdown.bytes === true) {
|
||||
node.bytes = report.bytes;
|
||||
}
|
||||
if (breakdown.count === true) {
|
||||
node.count = report.count;
|
||||
}
|
||||
};
|
||||
|
||||
CensusTreeNodeBreakdowns.internalType = function (node, breakdown, report) {
|
||||
node.children = [];
|
||||
for (let key of Object.keys(report)) {
|
||||
node.children.push(new CensusTreeNode(breakdown.then, report[key], key));
|
||||
}
|
||||
}
|
||||
|
||||
CensusTreeNodeBreakdowns.objectClass = function (node, breakdown, report) {
|
||||
node.children = [];
|
||||
for (let key of Object.keys(report)) {
|
||||
let bd = key === "other" ? breakdown.other : breakdown.then;
|
||||
node.children.push(new CensusTreeNode(bd, report[key], key));
|
||||
}
|
||||
}
|
||||
|
||||
CensusTreeNodeBreakdowns.coarseType = function (node, breakdown, report) {
|
||||
node.children = [];
|
||||
for (let type of Object.keys(breakdown).filter(type => COARSE_TYPES.has(type))) {
|
||||
node.children.push(new CensusTreeNode(breakdown[type], report[type], type));
|
||||
}
|
||||
}
|
||||
|
||||
function sortByBytes (a, b) {
|
||||
return (b.bytes || 0) - (a.bytes || 0);
|
||||
}
|
||||
|
||||
exports.CensusTreeNode = CensusTreeNode;
|
12
browser/devtools/memory/moz.build
Normal file
12
browser/devtools/memory/moz.build
Normal file
@ -0,0 +1,12 @@
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
EXTRA_JS_MODULES.devtools.memory += [
|
||||
'modules/census-view.js',
|
||||
'modules/census.js',
|
||||
]
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
||||
MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
|
7
browser/devtools/memory/test/mochitest/chrome.ini
Normal file
7
browser/devtools/memory/test/mochitest/chrome.ini
Normal file
@ -0,0 +1,7 @@
|
||||
[DEFAULT]
|
||||
tags = devtools
|
||||
skip-if = buildapp == 'b2g' || os == 'android'
|
||||
support-files =
|
||||
head.js
|
||||
|
||||
[test_census-view-01.html]
|
12
browser/devtools/memory/test/mochitest/head.js
Normal file
12
browser/devtools/memory/test/mochitest/head.js
Normal file
@ -0,0 +1,12 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
const CC = Components.Constructor;
|
||||
|
||||
const { require } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
100
browser/devtools/memory/test/mochitest/test_census-view-01.html
Normal file
100
browser/devtools/memory/test/mochitest/test_census-view-01.html
Normal file
@ -0,0 +1,100 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Bug 1067491 - Test taking a census over the RDP.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Census Tree 01</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link href="chrome://browser/content/devtools/widgets.css" type="text/css" />
|
||||
<link href="chrome://browser/skin/devtools/light-theme.css" type="text/css" />
|
||||
<link href="chrome://browser/skin/devtools/common.css" type="text/css" />
|
||||
<link href="chrome://browser/skin/devtools/widgets.css" type="text/css" />
|
||||
<link href="chrome://browser/skin/devtools/memory.css" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<ul id="container" style="width:100%;height:300px;"></ul>
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript;version=1.8"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
var { CensusTreeNode } = require("devtools/memory/census");
|
||||
var { INDENTATION, CensusView } = require("devtools/memory/census-view");
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
const countBreakdown = { by: "count", count: true, bytes: true };
|
||||
|
||||
const BREAKDOWN = {
|
||||
by: "coarseType",
|
||||
objects: { by: "objectClass", then: countBreakdown },
|
||||
strings: countBreakdown,
|
||||
other: { by: "internalType", then: countBreakdown },
|
||||
};
|
||||
|
||||
const REPORT = {
|
||||
"objects": {
|
||||
"Function": { bytes: 10, count: 1 },
|
||||
"Array": { bytes: 20, count: 2 },
|
||||
},
|
||||
"strings": { bytes: 10, count: 1 },
|
||||
"other": {
|
||||
"js::Shape": { bytes: 30, count: 3 },
|
||||
"js::Shape2": { bytes: 40, count: 4 }
|
||||
},
|
||||
};
|
||||
|
||||
const EXPECTED_ROWS = [
|
||||
{ level: 0, name: "strings", bytes: 10, count: 1, },
|
||||
{ level: 0, name: "objects" },
|
||||
{ level: 1, name: "Array", bytes: 20, count: 2, },
|
||||
{ level: 1, name: "Function", bytes: 10, count: 1, },
|
||||
{ level: 0, name: "other" },
|
||||
{ level: 1, name: "js::Shape2", bytes: 40, count: 4, },
|
||||
{ level: 1, name: "js::Shape", bytes: 30, count: 3, },
|
||||
];
|
||||
var censusTreeNode = new CensusTreeNode(BREAKDOWN, REPORT);
|
||||
|
||||
var view = new CensusView({
|
||||
censusTreeNode: censusTreeNode,
|
||||
hidden: true
|
||||
});
|
||||
|
||||
view.attachTo(document.querySelector("#container"));
|
||||
|
||||
var ul = document.querySelector("#container");
|
||||
var children = Array.from(ul.children).filter(n => n.style.display !== "none");
|
||||
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var el = children[i];
|
||||
var expected = EXPECTED_ROWS[i];
|
||||
var nameEl = el.querySelector(".heap-tree-cell[type='name']");
|
||||
var bytesEl = el.querySelector(".heap-tree-cell[type='bytes']");
|
||||
var countEl = el.querySelector(".heap-tree-cell[type='count']");
|
||||
|
||||
is(nameEl.innerHTML, expected.name,
|
||||
`correct name "${expected.name}" in heap tree`);
|
||||
|
||||
is(el.style.MozMarginStart, (INDENTATION * expected.level) + "px",
|
||||
`correct indentation for ${expected.name}`);
|
||||
|
||||
if ("bytes" in expected) {
|
||||
is(bytesEl.innerHTML, String(expected.bytes),
|
||||
`correct bytes "${expected.bytes}" in heap tree`);
|
||||
} else {
|
||||
ok(!bytesEl, "no bytes correctly displayed for ${expected.name}");
|
||||
}
|
||||
|
||||
if ("count" in expected) {
|
||||
is(countEl.innerHTML, String(expected.count),
|
||||
`correct count "${expected.count}" in heap tree`);
|
||||
} else {
|
||||
ok(!countEl, "no count correctly displayed for ${expected.name}");
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
};
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
143
browser/devtools/memory/test/unit/head.js
Normal file
143
browser/devtools/memory/test/unit/head.js
Normal file
@ -0,0 +1,143 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
const CC = Components.Constructor;
|
||||
|
||||
const { require } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
|
||||
const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
const { CensusTreeNode } = require("devtools/memory/census");
|
||||
const Services = require("Services");
|
||||
|
||||
// Always log packets when running tests. runxpcshelltests.py will throw
|
||||
// the output away anyway, unless you give it the --verbose flag.
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", true);
|
||||
|
||||
const SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"]
|
||||
.createInstance(Ci.nsIPrincipal);
|
||||
|
||||
function dumpn(msg) {
|
||||
dump("HEAPSNAPSHOT-TEST: " + msg + "\n");
|
||||
}
|
||||
|
||||
function addTestingFunctionsToGlobal(global) {
|
||||
global.eval(
|
||||
`
|
||||
const testingFunctions = Components.utils.getJSTestingFunctions();
|
||||
for (let k in testingFunctions) {
|
||||
this[k] = testingFunctions[k];
|
||||
}
|
||||
`
|
||||
);
|
||||
if (!global.print) {
|
||||
global.print = do_print;
|
||||
}
|
||||
if (!global.newGlobal) {
|
||||
global.newGlobal = newGlobal;
|
||||
}
|
||||
if (!global.Debugger) {
|
||||
addDebuggerToGlobal(global);
|
||||
}
|
||||
}
|
||||
|
||||
addTestingFunctionsToGlobal(this);
|
||||
|
||||
/**
|
||||
* Create a new global, with all the JS shell testing functions. Similar to the
|
||||
* newGlobal function exposed to JS shells, and useful for porting JS shell
|
||||
* tests to xpcshell tests.
|
||||
*/
|
||||
function newGlobal() {
|
||||
const global = new Cu.Sandbox(SYSTEM_PRINCIPAL, { freshZone: true });
|
||||
addTestingFunctionsToGlobal(global);
|
||||
return global;
|
||||
}
|
||||
|
||||
function assertThrowsValue(f, val, msg) {
|
||||
var fullmsg;
|
||||
try {
|
||||
f();
|
||||
} catch (exc) {
|
||||
if ((exc === val) === (val === val) && (val !== 0 || 1 / exc === 1 / val))
|
||||
return;
|
||||
fullmsg = "Assertion failed: expected exception " + val + ", got " + exc;
|
||||
}
|
||||
if (fullmsg === undefined)
|
||||
fullmsg = "Assertion failed: expected exception " + val + ", no exception thrown";
|
||||
if (msg !== undefined)
|
||||
fullmsg += " - " + msg;
|
||||
throw new Error(fullmsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path of the file with the specified name in a
|
||||
* platform-independent and URL-like form.
|
||||
*/
|
||||
function getFilePath(aName, aAllowMissing=false, aUsePlatformPathSeparator=false)
|
||||
{
|
||||
let file = do_get_file(aName, aAllowMissing);
|
||||
let path = Services.io.newFileURI(file).spec;
|
||||
let filePrePath = "file://";
|
||||
if ("nsILocalFileWin" in Ci &&
|
||||
file instanceof Ci.nsILocalFileWin) {
|
||||
filePrePath += "/";
|
||||
}
|
||||
|
||||
path = path.slice(filePrePath.length);
|
||||
|
||||
if (aUsePlatformPathSeparator && path.match(/^\w:/)) {
|
||||
path = path.replace(/\//g, "\\");
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a heap snapshot to the file with the given name in the current
|
||||
* directory, read it back as a HeapSnapshot instance, and then take a census of
|
||||
* the heap snapshot's serialized heap graph with the provided census options.
|
||||
*
|
||||
* @param {Object|undefined} censusOptions
|
||||
* Options that should be passed through to the takeCensus method. See
|
||||
* js/src/doc/Debugger/Debugger.Memory.md for details.
|
||||
*
|
||||
* @param {Debugger|null} dbg
|
||||
* If a Debugger object is given, only serialize the subgraph covered by
|
||||
* the Debugger's debuggees. If null, serialize the whole heap graph.
|
||||
*
|
||||
* @param {String} fileName
|
||||
* The file name to save the heap snapshot's core dump file to, within
|
||||
* the current directory.
|
||||
*
|
||||
* @returns Census
|
||||
*/
|
||||
function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined,
|
||||
// Add the Math.random() so that parallel
|
||||
// tests are less likely to mess with
|
||||
// each other.
|
||||
fileName="core-dump-" + (Math.random()) + ".tmp") {
|
||||
const filePath = getFilePath(fileName, true, true);
|
||||
ok(filePath, "Should get a file path to save the core dump to.");
|
||||
|
||||
const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true };
|
||||
ChromeUtils.saveHeapSnapshot(filePath, snapshotOptions);
|
||||
ok(true, "Should have saved a heap snapshot to " + filePath);
|
||||
|
||||
const snapshot = ChromeUtils.readHeapSnapshot(filePath);
|
||||
ok(snapshot, "Should have read a heap snapshot back from " + filePath);
|
||||
ok(snapshot instanceof HeapSnapshot, "snapshot should be an instance of HeapSnapshot");
|
||||
|
||||
equal(typeof snapshot.takeCensus, "function", "snapshot should have a takeCensus method");
|
||||
return snapshot.takeCensus(censusOptions);
|
||||
}
|
||||
|
||||
function compareCensusViewData (breakdown, report, expected, assertion) {
|
||||
let data = new CensusTreeNode(breakdown, report);
|
||||
equal(JSON.stringify(data), JSON.stringify(expected), assertion);
|
||||
}
|
37
browser/devtools/memory/test/unit/test_census-01.js
Normal file
37
browser/devtools/memory/test/unit/test_census-01.js
Normal file
@ -0,0 +1,37 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests CensusTreeNode with `internalType` breakdown.
|
||||
*/
|
||||
function run_test() {
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, `${JSON.stringify(BREAKDOWN)} has correct results.`);
|
||||
}
|
||||
|
||||
const BREAKDOWN = {
|
||||
by: "internalType",
|
||||
then: { by: "count", count: true, bytes: true }
|
||||
};
|
||||
|
||||
const REPORT = {
|
||||
"JSObject": {
|
||||
"bytes": 100,
|
||||
"count": 10,
|
||||
},
|
||||
"js::Shape": {
|
||||
"bytes": 500,
|
||||
"count": 50,
|
||||
},
|
||||
"JSString": {
|
||||
"bytes": 0,
|
||||
"count": 0,
|
||||
},
|
||||
};
|
||||
|
||||
const EXPECTED = {
|
||||
children: [
|
||||
{ name: "js::Shape", bytes: 500, count: 50, },
|
||||
{ name: "JSObject", bytes: 100, count: 10, },
|
||||
{ name: "JSString", bytes: 0, count: 0, },
|
||||
],
|
||||
};
|
45
browser/devtools/memory/test/unit/test_census-02.js
Normal file
45
browser/devtools/memory/test/unit/test_census-02.js
Normal file
@ -0,0 +1,45 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests CensusTreeNode with `coarseType` breakdown.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, `${JSON.stringify(BREAKDOWN)} has correct results.`);
|
||||
}
|
||||
|
||||
const countBreakdown = { by: "count", count: true, bytes: true };
|
||||
|
||||
const BREAKDOWN = {
|
||||
by: "coarseType",
|
||||
objects: { by: "objectClass", then: countBreakdown },
|
||||
strings: countBreakdown,
|
||||
other: { by: "internalType", then: countBreakdown },
|
||||
};
|
||||
|
||||
const REPORT = {
|
||||
"objects": {
|
||||
"Function": { bytes: 10, count: 1 },
|
||||
"Array": { bytes: 20, count: 2 },
|
||||
},
|
||||
"strings": { bytes: 10, count: 1 },
|
||||
"other": {
|
||||
"js::Shape": { bytes: 30, count: 3 },
|
||||
"js::Shape2": { bytes: 40, count: 4 }
|
||||
},
|
||||
};
|
||||
|
||||
const EXPECTED = {
|
||||
children: [
|
||||
{ name: "strings", bytes: 10, count: 1, },
|
||||
{ name: "objects", children: [
|
||||
{ name: "Array", bytes: 20, count: 2, },
|
||||
{ name: "Function", bytes: 10, count: 1, },
|
||||
]},
|
||||
{ name: "other", children: [
|
||||
{ name: "js::Shape2", bytes: 40, count: 4, },
|
||||
{ name: "js::Shape", bytes: 30, count: 3, },
|
||||
]},
|
||||
]
|
||||
};
|
38
browser/devtools/memory/test/unit/test_census-03.js
Normal file
38
browser/devtools/memory/test/unit/test_census-03.js
Normal file
@ -0,0 +1,38 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests CensusTreeNode with `objectClass` breakdown.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, `${JSON.stringify(BREAKDOWN)} has correct results.`);
|
||||
}
|
||||
|
||||
const countBreakdown = { by: "count", count: true, bytes: true };
|
||||
|
||||
const BREAKDOWN = {
|
||||
by: "objectClass",
|
||||
then: countBreakdown,
|
||||
other: { by: "internalType", then: countBreakdown }
|
||||
};
|
||||
|
||||
const REPORT = {
|
||||
"Function": { bytes: 10, count: 10 },
|
||||
"Array": { bytes: 100, count: 1 },
|
||||
"other": {
|
||||
"JIT::CODE::NOW!!!": { bytes: 20, count: 2 },
|
||||
"JIT::CODE::LATER!!!": { bytes: 40, count: 4 }
|
||||
}
|
||||
};
|
||||
|
||||
const EXPECTED = {
|
||||
children: [
|
||||
{ name: "Array", bytes: 100, count: 1 },
|
||||
{ name: "Function", bytes: 10, count: 10 },
|
||||
{ name: "other", children: [
|
||||
{ name: "JIT::CODE::LATER!!!", bytes: 40, count: 4 },
|
||||
{ name: "JIT::CODE::NOW!!!", bytes: 20, count: 2 },
|
||||
]}
|
||||
]
|
||||
};
|
10
browser/devtools/memory/test/unit/xpcshell.ini
Normal file
10
browser/devtools/memory/test/unit/xpcshell.ini
Normal file
@ -0,0 +1,10 @@
|
||||
[DEFAULT]
|
||||
tags = devtools
|
||||
head = head.js
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
|
||||
[test_census-01.js]
|
||||
[test_census-02.js]
|
||||
[test_census-03.js]
|
@ -16,6 +16,7 @@ DIRS += [
|
||||
'inspector',
|
||||
'layoutview',
|
||||
'markupview',
|
||||
'memory',
|
||||
'netmonitor',
|
||||
'performance',
|
||||
'projecteditor',
|
||||
|
@ -730,6 +730,8 @@ you can use these alternative items. Otherwise, their values should be empty. -
|
||||
<!ENTITY identity.moreInfoLinkText2 "More Information">
|
||||
|
||||
<!ENTITY identity.permissions "Permissions">
|
||||
<!ENTITY identity.permissionsPageFunctionality "Page Functionality">
|
||||
<!ENTITY identity.permissionsSystemAccess "System Access">
|
||||
|
||||
<!-- Name for the tabs toolbar as spoken by screen readers.
|
||||
The word "toolbar" is appended automatically and should not be contained below! -->
|
||||
|
@ -105,6 +105,12 @@
|
||||
opens the split Console and displays the properties in its side panel. -->
|
||||
<!ENTITY inspectorShowDOMProperties.label "Show DOM Properties">
|
||||
|
||||
<!-- LOCALIZATION NOTE (inspectorUseInConsole.label): This is the label
|
||||
shown in the inspector contextual-menu for the item that outputs a
|
||||
variable for the current node to the console. When triggered,
|
||||
this item opens the split Console. -->
|
||||
<!ENTITY inspectorUseInConsole.label "Use in Console">
|
||||
|
||||
<!-- LOCALIZATION NOTE (inspectorExpandNode.label): This is the label
|
||||
shown in the inspector contextual-menu for recursively expanding
|
||||
mark-up elements -->
|
||||
|
@ -6,6 +6,8 @@ this.EXPORTED_SYMBOLS = [ "SitePermissions" ];
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const GROUP_PAGE_FUNCTIONALITY = "pagefunctionality";
|
||||
const GROUP_SYSTEM_ACCESS = "systemaccess";
|
||||
let gStringBundle =
|
||||
Services.strings.createBundle("chrome://browser/locale/sitePermissions.properties");
|
||||
|
||||
@ -24,9 +26,34 @@ this.SitePermissions = {
|
||||
return aURI.schemeIs("http") || aURI.schemeIs("https");
|
||||
},
|
||||
|
||||
/* Returns an array of permission IDs that match a given
|
||||
* group identifier
|
||||
*/
|
||||
_listPermissionsByGroup(group) {
|
||||
let array = Object.keys(gPermissionObject).filter(p=> {
|
||||
return gPermissionObject[p].group == group;
|
||||
});
|
||||
array.sort((a, b) => {
|
||||
return this.getPermissionLabel(a).localeCompare(this.getPermissionLabel(b));
|
||||
});
|
||||
return array;
|
||||
},
|
||||
|
||||
/* Returns an array of 'page functionality' permission IDs
|
||||
*/
|
||||
listPageFunctionalityPermissions() {
|
||||
return this._listPermissionsByGroup(GROUP_PAGE_FUNCTIONALITY);
|
||||
},
|
||||
|
||||
/* Returns an array of 'system access' permission IDs
|
||||
*/
|
||||
listSystemAccessPermissions() {
|
||||
return this._listPermissionsByGroup(GROUP_SYSTEM_ACCESS);
|
||||
},
|
||||
|
||||
/* Returns an array of all permission IDs.
|
||||
*/
|
||||
listPermissions: function () {
|
||||
listPermissions () {
|
||||
let array = Object.keys(gPermissionObject);
|
||||
array.sort((a, b) => {
|
||||
return this.getPermissionLabel(a).localeCompare(this.getPermissionLabel(b));
|
||||
@ -136,12 +163,17 @@ let gPermissionObject = {
|
||||
* Defaults to UNKNOWN, indicating that the user will be asked each time
|
||||
* a page asks for that permissions.
|
||||
*
|
||||
* - group
|
||||
* A string, either 'systemacces' or 'pagefunctionality'.
|
||||
* Indicates what group this should be listed with in the UI
|
||||
*
|
||||
* - states
|
||||
* Array of permission states to be exposed to the user.
|
||||
* Defaults to ALLOW, BLOCK and the default state (see getDefault).
|
||||
*/
|
||||
|
||||
"image": {
|
||||
group: GROUP_PAGE_FUNCTIONALITY,
|
||||
getDefault: function () {
|
||||
return Services.prefs.getIntPref("permissions.default.image") == 2 ?
|
||||
SitePermissions.BLOCK : SitePermissions.ALLOW;
|
||||
@ -149,6 +181,7 @@ let gPermissionObject = {
|
||||
},
|
||||
|
||||
"cookie": {
|
||||
group: GROUP_PAGE_FUNCTIONALITY,
|
||||
states: [ SitePermissions.ALLOW, SitePermissions.SESSION, SitePermissions.BLOCK ],
|
||||
getDefault: function () {
|
||||
if (Services.prefs.getIntPref("network.cookie.cookieBehavior") == 2)
|
||||
@ -161,12 +194,19 @@ let gPermissionObject = {
|
||||
}
|
||||
},
|
||||
|
||||
"desktop-notification": {},
|
||||
"desktop-notification": {
|
||||
group: GROUP_PAGE_FUNCTIONALITY,
|
||||
},
|
||||
|
||||
"camera": {},
|
||||
"microphone": {},
|
||||
"camera": {
|
||||
group: GROUP_SYSTEM_ACCESS,
|
||||
},
|
||||
"microphone": {
|
||||
group: GROUP_SYSTEM_ACCESS,
|
||||
},
|
||||
|
||||
"popup": {
|
||||
group: GROUP_PAGE_FUNCTIONALITY,
|
||||
getDefault: function () {
|
||||
return Services.prefs.getBoolPref("dom.disable_open_during_load") ?
|
||||
SitePermissions.BLOCK : SitePermissions.ALLOW;
|
||||
@ -174,6 +214,7 @@ let gPermissionObject = {
|
||||
},
|
||||
|
||||
"install": {
|
||||
group: GROUP_PAGE_FUNCTIONALITY,
|
||||
getDefault: function () {
|
||||
return Services.prefs.getBoolPref("xpinstall.whitelist.required") ?
|
||||
SitePermissions.BLOCK : SitePermissions.ALLOW;
|
||||
@ -181,16 +222,21 @@ let gPermissionObject = {
|
||||
},
|
||||
|
||||
"geo": {
|
||||
group: GROUP_SYSTEM_ACCESS,
|
||||
exactHostMatch: true
|
||||
},
|
||||
|
||||
"indexedDB": {},
|
||||
"indexedDB": {
|
||||
group: GROUP_SYSTEM_ACCESS,
|
||||
},
|
||||
|
||||
"pointerLock": {
|
||||
group: GROUP_SYSTEM_ACCESS,
|
||||
exactHostMatch: true
|
||||
},
|
||||
|
||||
"push": {
|
||||
group: GROUP_SYSTEM_ACCESS,
|
||||
exactHostMatch: true
|
||||
}
|
||||
};
|
||||
|
22
browser/modules/test/xpcshell/test_SitePermissions.js
Normal file
22
browser/modules/test/xpcshell/test_SitePermissions.js
Normal file
@ -0,0 +1,22 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
Components.utils.import("resource:///modules/SitePermissions.jsm");
|
||||
|
||||
add_task(function* testPermissionsListing() {
|
||||
Assert.deepEqual(SitePermissions.listPermissions().sort(),
|
||||
["camera","cookie","desktop-notification","geo","image",
|
||||
"indexedDB","install","microphone","pointerLock","popup",
|
||||
"push"],
|
||||
"Correct list of all permissions");
|
||||
|
||||
Assert.deepEqual(SitePermissions.listPageFunctionalityPermissions().sort(),
|
||||
["cookie","desktop-notification","image","install","popup"],
|
||||
"Correct list of 'page functionality' permissions");
|
||||
|
||||
Assert.deepEqual(SitePermissions.listSystemAccessPermissions().sort(),
|
||||
["camera","geo","indexedDB","microphone","pointerLock","push"],
|
||||
"Correct list of 'page functionality' permissions");
|
||||
});
|
@ -6,3 +6,4 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
|
||||
[test_DirectoryLinksProvider.js]
|
||||
[test_NewTabURL.js]
|
||||
[test_SitePermissions.js]
|
||||
|
@ -34,6 +34,8 @@
|
||||
--identity-box-verified-background-color: #fff;
|
||||
|
||||
--panel-separator-color: ThreeDShadow;
|
||||
|
||||
--urlbar-separator-color: hsla(0,0%,16%,.2);
|
||||
}
|
||||
|
||||
#menubar-items {
|
||||
@ -1193,10 +1195,19 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
|
||||
/* Combined go/reload/stop button in location bar */
|
||||
|
||||
#urlbar > toolbarbutton {
|
||||
#urlbar-go-button,
|
||||
#urlbar-reload-button,
|
||||
#urlbar-stop-button {
|
||||
-moz-appearance: none;
|
||||
padding: 0 2px;
|
||||
list-style-image: url("chrome://browser/skin/reload-stop-go.png");
|
||||
padding: 0 9px;
|
||||
margin-inline-start: 2px;
|
||||
border-inline-start: 1px solid var(--urlbar-separator-color);
|
||||
border-image: linear-gradient(transparent 15%,
|
||||
var(--urlbar-separator-color) 15%,
|
||||
var(--urlbar-separator-color) 85%,
|
||||
transparent 85%);
|
||||
border-image-slice: 1;
|
||||
}
|
||||
|
||||
#urlbar-reload-button {
|
||||
@ -1244,10 +1255,18 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
#urlbar > toolbarbutton {
|
||||
#urlbar-go-button,
|
||||
#urlbar-reload-button,
|
||||
#urlbar-stop-button {
|
||||
list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
|
||||
}
|
||||
|
||||
#urlbar-go-button > .toolbarbutton-icon,
|
||||
#urlbar-reload-button > .toolbarbutton-icon,
|
||||
#urlbar-stop-button > .toolbarbutton-icon {
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
#urlbar-go-button {
|
||||
-moz-image-region: rect(0, 84px, 28px, 56px);
|
||||
}
|
||||
@ -1283,10 +1302,6 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
#urlbar-stop-button:hover:active {
|
||||
-moz-image-region: rect(56px, 56px, 84px, 28px);
|
||||
}
|
||||
|
||||
#urlbar > toolbarbutton > .toolbarbutton-icon {
|
||||
width: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Popup blocker button */
|
||||
|
@ -349,6 +349,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/eyedropper.css (../shared/devtools/eyedropper.css)
|
||||
* skin/classic/browser/devtools/netmonitor.css (../shared/devtools/netmonitor.css)
|
||||
skin/classic/browser/devtools/performance.css (../shared/devtools/performance.css)
|
||||
skin/classic/browser/devtools/memory.css (../shared/devtools/memory.css)
|
||||
skin/classic/browser/devtools/promisedebugger.css (../shared/devtools/promisedebugger.css)
|
||||
skin/classic/browser/devtools/timeline-filter.svg (../shared/devtools/images/timeline-filter.svg)
|
||||
* skin/classic/browser/devtools/scratchpad.css (../shared/devtools/scratchpad.css)
|
||||
|
@ -43,6 +43,8 @@
|
||||
--urlbar-dropmarker-active-2x-region: rect(0, 44px, 28px, 22px);
|
||||
|
||||
--panel-separator-color: hsla(210,4%,10%,.14);
|
||||
|
||||
--urlbar-separator-color: hsla(0,0%,16%,.2);
|
||||
}
|
||||
|
||||
#urlbar:-moz-lwtheme:not([focused="true"]),
|
||||
@ -1892,12 +1894,18 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
|
||||
|
||||
/* ----- COMBINED GO/RELOAD/STOP BUTTON IN LOCATION BAR ----- */
|
||||
|
||||
#urlbar > toolbarbutton {
|
||||
#urlbar-go-button,
|
||||
#urlbar-reload-button,
|
||||
#urlbar-stop-button {
|
||||
margin: 0;
|
||||
-moz-padding-start: 2px;
|
||||
-moz-padding-end: 1px;
|
||||
background-origin: border-box;
|
||||
list-style-image: url("chrome://browser/skin/reload-stop-go.png");
|
||||
padding: 0 9px;
|
||||
margin-inline-start: 2px;
|
||||
border-inline-start: 1px solid var(--urlbar-separator-color);
|
||||
border-image: linear-gradient(transparent 15%,
|
||||
var(--urlbar-separator-color) 15%,
|
||||
var(--urlbar-separator-color) 85%,
|
||||
transparent 85%);
|
||||
}
|
||||
|
||||
#urlbar-go-button {
|
||||
@ -1932,33 +1940,19 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
|
||||
-moz-image-region: rect(14px, 28px, 28px, 14px);
|
||||
}
|
||||
|
||||
#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
#bookmarks-menu-button[cui-areatype="toolbar"].bookmark-item > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#BMB_bookmarksPopup[side="top"],
|
||||
#BMB_bookmarksPopup[side="bottom"] {
|
||||
margin-left: -26px;
|
||||
margin-right: -26px;
|
||||
}
|
||||
|
||||
#BMB_bookmarksPopup[side="left"],
|
||||
#BMB_bookmarksPopup[side="right"] {
|
||||
margin-top: -26px;
|
||||
margin-bottom: -26px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
#urlbar > toolbarbutton {
|
||||
#urlbar-go-button,
|
||||
#urlbar-reload-button,
|
||||
#urlbar-stop-button {
|
||||
list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
|
||||
}
|
||||
|
||||
#urlbar-go-button > .toolbarbutton-icon,
|
||||
#urlbar-reload-button > .toolbarbutton-icon,
|
||||
#urlbar-stop-button > .toolbarbutton-icon {
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
#urlbar-go-button {
|
||||
-moz-image-region: rect(0, 84px, 28px, 56px);
|
||||
}
|
||||
@ -1982,10 +1976,28 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
|
||||
#urlbar-stop-button:hover:active {
|
||||
-moz-image-region: rect(28px, 56px, 56px, 28px);
|
||||
}
|
||||
}
|
||||
|
||||
#urlbar > toolbarbutton > .toolbarbutton-icon {
|
||||
width: 14px;
|
||||
}
|
||||
#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
#bookmarks-menu-button[cui-areatype="toolbar"].bookmark-item > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#BMB_bookmarksPopup[side="top"],
|
||||
#BMB_bookmarksPopup[side="bottom"] {
|
||||
margin-left: -26px;
|
||||
margin-right: -26px;
|
||||
}
|
||||
|
||||
#BMB_bookmarksPopup[side="left"],
|
||||
#BMB_bookmarksPopup[side="right"] {
|
||||
margin-top: -26px;
|
||||
margin-bottom: -26px;
|
||||
}
|
||||
|
||||
/* POPUP BLOCKER BUTTON */
|
||||
|
@ -449,6 +449,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/eyedropper.css (../shared/devtools/eyedropper.css)
|
||||
* skin/classic/browser/devtools/netmonitor.css (../shared/devtools/netmonitor.css)
|
||||
skin/classic/browser/devtools/performance.css (../shared/devtools/performance.css)
|
||||
skin/classic/browser/devtools/memory.css (../shared/devtools/memory.css)
|
||||
skin/classic/browser/devtools/promisedebugger.css (../shared/devtools/promisedebugger.css)
|
||||
skin/classic/browser/devtools/timeline-filter.svg (../shared/devtools/images/timeline-filter.svg)
|
||||
* skin/classic/browser/devtools/scratchpad.css (../shared/devtools/scratchpad.css)
|
||||
|
@ -170,6 +170,11 @@
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.identity-popup-subheadline {
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.identity-popup-warning-gray {
|
||||
-moz-padding-start: 24px;
|
||||
background: url(chrome://browser/skin/controlcenter/warning-gray.svg) no-repeat 0 50%;
|
||||
@ -297,6 +302,12 @@ description#identity-popup-content-verifier,
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#permission-subview-list-page-functionality,
|
||||
#permission-subview-list-system-access {
|
||||
margin: 3px 0;
|
||||
-moz-margin-start: 3px;
|
||||
}
|
||||
|
||||
#identity-popup-permission-list menulist.identity-popup-permission,
|
||||
#identity-popup-permission-subview-list menulist.identity-popup-permission {
|
||||
max-width: 10em;
|
||||
|
@ -50,6 +50,7 @@
|
||||
/* Url and search bars */
|
||||
--url-and-searchbar-background-color: #171B1F;
|
||||
--url-and-searchbar-color: #fff;
|
||||
--urlbar-separator-color: #5F6670;
|
||||
--urlbar-dropmarker-url: url("chrome://browser/skin/devedition/urlbar-history-dropmarker.svg");
|
||||
--urlbar-dropmarker-region: rect(0px, 11px, 14px, 0px);
|
||||
--urlbar-dropmarker-hover-region: rect(0, 22px, 14px, 11px);
|
||||
@ -62,7 +63,6 @@
|
||||
}
|
||||
|
||||
:root[devtoolstheme="dark"] #identity-box {
|
||||
--identity-box-border-color: #5F6670;
|
||||
--identity-box-chrome-color: #46afe3;
|
||||
--identity-box-verified-background-color: transparent;
|
||||
--identity-box-selected-background-color: rgba(231,230,230,.2);
|
||||
|
81
browser/themes/shared/devtools/memory.css
Normal file
81
browser/themes/shared/devtools/memory.css
Normal file
@ -0,0 +1,81 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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/. */
|
||||
|
||||
/* CSS Variables specific to this panel that aren't defined by the themes */
|
||||
.theme-dark {
|
||||
--cell-border-color: rgba(255,255,255,0.15);
|
||||
--cell-border-color-light: rgba(255,255,255,0.1);
|
||||
--focus-cell-border-color: rgba(255,255,255,0.5);
|
||||
--row-alt-background-color: rgba(29,79,115,0.15);
|
||||
--row-hover-background-color: rgba(29,79,115,0.25);
|
||||
}
|
||||
|
||||
.theme-light {
|
||||
--cell-border-color: rgba(0,0,0,0.15);
|
||||
--cell-border-color-light: rgba(0,0,0,0.1);
|
||||
--focus-cell-border-color: rgba(0,0,0,0.3);
|
||||
--row-alt-background-color: rgba(76,158,217,0.1);
|
||||
--row-hover-background-color: rgba(76,158,217,0.2);
|
||||
}
|
||||
|
||||
.heap-view {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.heap-view .theme-twisty {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.heap-tree-item {
|
||||
list-style-type: none;
|
||||
/* display: none; */
|
||||
}
|
||||
|
||||
.heap-tree-item[expanded] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.heap-tree-item:nth-child(2n) {
|
||||
background-color: var(--row-alt-background-color);
|
||||
}
|
||||
|
||||
.heap-tree-item:hover {
|
||||
background-color: var(--row-hover-background-color);
|
||||
}
|
||||
|
||||
.heap-tree-item:focus {
|
||||
background-color: var(--theme-selection-background);
|
||||
}
|
||||
|
||||
.heap-tree-item:focus description {
|
||||
color: var(--theme-selection-color) !important;
|
||||
}
|
||||
|
||||
.heap-tree-item:focus .call-tree-cell {
|
||||
-moz-border-end-color: var(--focus-cell-border-color);
|
||||
}
|
||||
|
||||
|
||||
.heap-tree-cell[type="bytes"], .heap-tree-cell[type="count"] {
|
||||
position: absolute;
|
||||
text-align: right;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.heap-tree-cell[type="name"] {
|
||||
width: 150px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.heap-tree-cell[type="count"] {
|
||||
left: 300px;
|
||||
}
|
||||
|
||||
.heap-tree-cell[type="bytes"] {
|
||||
left: 250px;
|
||||
}
|
@ -5,7 +5,6 @@
|
||||
%endif
|
||||
|
||||
#identity-box {
|
||||
--identity-box-border-color: hsla(0,0%,16%,.2);
|
||||
--identity-box-selected-background-color: rgb(231,230,230);
|
||||
--identity-box-verified-color: hsl(92,100%,30%);
|
||||
%ifdef MOZ_OFFICIAL_BRANDING
|
||||
@ -18,10 +17,10 @@
|
||||
%endif
|
||||
%endif
|
||||
|
||||
border-inline-end: 1px solid var(--identity-box-border-color);
|
||||
border-inline-end: 1px solid var(--urlbar-separator-color);
|
||||
border-image: linear-gradient(transparent 15%,
|
||||
var(--identity-box-border-color) 15%,
|
||||
var(--identity-box-border-color) 85%,
|
||||
var(--urlbar-separator-color) 15%,
|
||||
var(--urlbar-separator-color) 85%,
|
||||
transparent 85%);
|
||||
border-image-slice: 1;
|
||||
font-size: .9em;
|
||||
|
@ -47,6 +47,8 @@
|
||||
--urlbar-dropmarker-active-2x-region: rect(0, 66px, 28px, 44px);
|
||||
|
||||
--panel-separator-color: ThreeDLightShadow;
|
||||
|
||||
--urlbar-separator-color: hsla(0,0%,16%,.2);
|
||||
}
|
||||
|
||||
#menubar-items {
|
||||
@ -1649,12 +1651,20 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
|
||||
/* combined go/reload/stop button in location bar */
|
||||
|
||||
#urlbar > toolbarbutton {
|
||||
#urlbar-go-button,
|
||||
#urlbar-reload-button,
|
||||
#urlbar-stop-button {
|
||||
-moz-appearance: none;
|
||||
padding: 0 2px;
|
||||
background-origin: border-box;
|
||||
border: none;
|
||||
border-style: none;
|
||||
list-style-image: url("chrome://browser/skin/reload-stop-go.png");
|
||||
padding: 0 9px;
|
||||
margin-inline-start: 2px;
|
||||
border-inline-start: 1px solid var(--urlbar-separator-color);
|
||||
border-image: linear-gradient(transparent 15%,
|
||||
var(--urlbar-separator-color) 15%,
|
||||
var(--urlbar-separator-color) 85%,
|
||||
transparent 85%);
|
||||
border-image-slice: 1;
|
||||
}
|
||||
|
||||
#urlbar-reload-button {
|
||||
@ -1702,10 +1712,18 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
#urlbar > toolbarbutton {
|
||||
#urlbar-go-button,
|
||||
#urlbar-reload-button,
|
||||
#urlbar-stop-button {
|
||||
list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
|
||||
}
|
||||
|
||||
#urlbar-go-button > .toolbarbutton-icon,
|
||||
#urlbar-reload-button > .toolbarbutton-icon,
|
||||
#urlbar-stop-button > .toolbarbutton-icon {
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
#urlbar-go-button {
|
||||
-moz-image-region: rect(0, 84px, 28px, 56px);
|
||||
}
|
||||
@ -1741,10 +1759,6 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
#urlbar-stop-button:hover:active {
|
||||
-moz-image-region: rect(56px, 56px, 84px, 28px);
|
||||
}
|
||||
|
||||
#urlbar > toolbarbutton > .toolbarbutton-icon {
|
||||
width: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* popup blocker button */
|
||||
|
@ -472,6 +472,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/debugger.css (../shared/devtools/debugger.css)
|
||||
* skin/classic/browser/devtools/netmonitor.css (../shared/devtools/netmonitor.css)
|
||||
skin/classic/browser/devtools/performance.css (../shared/devtools/performance.css)
|
||||
skin/classic/browser/devtools/memory.css (../shared/devtools/memory.css)
|
||||
skin/classic/browser/devtools/promisedebugger.css (../shared/devtools/promisedebugger.css)
|
||||
skin/classic/browser/devtools/timeline-filter.svg (../shared/devtools/images/timeline-filter.svg)
|
||||
* skin/classic/browser/devtools/scratchpad.css (../shared/devtools/scratchpad.css)
|
||||
|
@ -78,7 +78,7 @@ BluetoothPbapManager::HandleShutdown()
|
||||
|
||||
BluetoothPbapManager::BluetoothPbapManager() : mConnected(false)
|
||||
, mRemoteMaxPacketLength(0)
|
||||
, mRequirePhonebookSize(false)
|
||||
, mPhonebookSizeRequired(false)
|
||||
{
|
||||
mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
|
||||
mCurrentPath.AssignLiteral("");
|
||||
@ -273,7 +273,7 @@ BluetoothPbapManager::ReceiveSocketData(BluetoothSocket* aSocket,
|
||||
// All OBEX request messages shall be sent as one OBEX packet containing
|
||||
// all of the headers. I.e. OBEX GET with opcode 0x83 shall always be
|
||||
// used. OBEX GET with opcode 0x03 shall never be used.
|
||||
BT_WARNING("PBAP shall always uses OBEX GetFinal instead of Get.");
|
||||
BT_LOGR("PBAP shall always uses OBEX GetFinal instead of Get.");
|
||||
|
||||
// no break. Treat 'Get' as 'GetFinal' for error tolerance.
|
||||
case ObexRequestCode::GetFinal: {
|
||||
@ -282,8 +282,8 @@ BluetoothPbapManager::ReceiveSocketData(BluetoothSocket* aSocket,
|
||||
// final body information (in an End-of-Body header) arrives, along with
|
||||
// the response code 0xA0 Success.
|
||||
if (mVCardDataStream) {
|
||||
if (!ReplyToGet(mVCardDataStream)) {
|
||||
BT_WARNING("Failed to reply to PBAP GET request.");
|
||||
if (!ReplyToGet()) {
|
||||
BT_LOGR("Failed to reply to PBAP GET request.");
|
||||
ReplyError(ObexResponseCode::InternalServerError);
|
||||
}
|
||||
return;
|
||||
@ -541,7 +541,7 @@ BluetoothPbapManager::AppendBtNamedValueByTagId(
|
||||
if (order < MOZ_ARRAY_LENGTH(sOrderStr)) {
|
||||
BT_APPEND_NAMED_VALUE(aValues, "order", sOrderStr[order]);
|
||||
} else {
|
||||
BT_WARNING("%s: Unexpected value '%d' of 'Order'", __FUNCTION__, order);
|
||||
BT_LOGR("Unexpected value %d of 'Order'", order);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -572,8 +572,7 @@ BluetoothPbapManager::AppendBtNamedValueByTagId(
|
||||
if (searchKey < MOZ_ARRAY_LENGTH(sSearchKeyStr)) {
|
||||
BT_APPEND_NAMED_VALUE(aValues, "searchKey", sSearchKeyStr[searchKey]);
|
||||
} else {
|
||||
BT_WARNING("%s: Unexpected value '%d' of 'SearchProperty'",
|
||||
__FUNCTION__, searchKey);
|
||||
BT_LOGR("Unexpected value %d of 'SearchProperty'", searchKey);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -590,7 +589,7 @@ BluetoothPbapManager::AppendBtNamedValueByTagId(
|
||||
// Section 5 "Phone Book Access Profile Functions", PBAP 1.2
|
||||
// Replying 'PhonebookSize' is mandatory if 'MaxListCount' parameter is
|
||||
// present in the request with a value of 0, else it is excluded.
|
||||
mRequirePhonebookSize = !maxListCount;
|
||||
mPhonebookSizeRequired = !maxListCount;
|
||||
|
||||
BT_APPEND_NAMED_VALUE(aValues, "maxListCount", (uint32_t) maxListCount);
|
||||
break;
|
||||
@ -697,7 +696,7 @@ BluetoothPbapManager::AfterPbapDisconnected()
|
||||
mConnected = false;
|
||||
|
||||
mRemoteMaxPacketLength = 0;
|
||||
mRequirePhonebookSize = false;
|
||||
mPhonebookSizeRequired = false;
|
||||
|
||||
if (mVCardDataStream) {
|
||||
mVCardDataStream->Close();
|
||||
@ -819,12 +818,12 @@ BluetoothPbapManager::ReplyToPullPhonebook(Blob* aBlob, uint16_t aPhonebookSize)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GetInputStreamFromBlob(mVCardDataStream, aBlob)) {
|
||||
if (!GetInputStreamFromBlob(aBlob)) {
|
||||
ReplyError(ObexResponseCode::InternalServerError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReplyToGet(mVCardDataStream, aPhonebookSize);
|
||||
return ReplyToGet(aPhonebookSize);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -845,12 +844,12 @@ BluetoothPbapManager::ReplyToPullvCardListing(Blob* aBlob,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GetInputStreamFromBlob(mVCardDataStream, aBlob)) {
|
||||
if (!GetInputStreamFromBlob(aBlob)) {
|
||||
ReplyError(ObexResponseCode::InternalServerError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReplyToGet(mVCardDataStream, aPhonebookSize);
|
||||
return ReplyToGet(aPhonebookSize);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -869,19 +868,18 @@ BluetoothPbapManager::ReplyToPullvCardEntry(Blob* aBlob)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GetInputStreamFromBlob(mVCardDataStream, aBlob)) {
|
||||
if (!GetInputStreamFromBlob(aBlob)) {
|
||||
ReplyError(ObexResponseCode::InternalServerError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReplyToGet(mVCardDataStream);
|
||||
return ReplyToGet();
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothPbapManager::ReplyToGet(nsIInputStream* aStream,
|
||||
uint16_t aPhonebookSize)
|
||||
BluetoothPbapManager::ReplyToGet(uint16_t aPhonebookSize)
|
||||
{
|
||||
MOZ_ASSERT(aStream);
|
||||
MOZ_ASSERT(mVCardDataStream);
|
||||
MOZ_ASSERT(mRemoteMaxPacketLength >= kObexLeastMaxSize);
|
||||
|
||||
// This response will be composed by these four parts.
|
||||
@ -897,7 +895,7 @@ BluetoothPbapManager::ReplyToGet(nsIInputStream* aStream,
|
||||
unsigned int index = kObexRespHeaderSize;
|
||||
|
||||
// ---- Part 2, add [response code:1][length:2] to response ---- //
|
||||
if (mRequirePhonebookSize) {
|
||||
if (mPhonebookSizeRequired) {
|
||||
// convert little endian to big endian
|
||||
uint8_t phonebookSize[2];
|
||||
phonebookSize[0] = (aPhonebookSize & 0xFF00) >> 8;
|
||||
@ -917,7 +915,7 @@ BluetoothPbapManager::ReplyToGet(nsIInputStream* aStream,
|
||||
mRemoteMaxPacketLength,
|
||||
appParameters,
|
||||
sizeof(appParameters));
|
||||
mRequirePhonebookSize = false;
|
||||
mPhonebookSizeRequired = false;
|
||||
}
|
||||
|
||||
// ---- Part 3, add [headerId:1][length:2][Body:var] to response ---- //
|
||||
@ -928,9 +926,10 @@ BluetoothPbapManager::ReplyToGet(nsIInputStream* aStream,
|
||||
// Read vCard data from input stream
|
||||
uint32_t numRead = 0;
|
||||
nsAutoArrayPtr<char> buffer(new char[remainingPacketSize]);
|
||||
nsresult rv = aStream->Read(buffer, remainingPacketSize, &numRead);
|
||||
nsresult rv = mVCardDataStream->Read(buffer, remainingPacketSize, &numRead);
|
||||
if (NS_FAILED(rv)) {
|
||||
BT_WARNING("Failed to read from input stream.");
|
||||
BT_LOGR("Failed to read from input stream. rv=0x%x",
|
||||
static_cast<uint32_t>(rv));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -951,8 +950,8 @@ BluetoothPbapManager::ReplyToGet(nsIInputStream* aStream,
|
||||
opcode = ObexResponseCode::Success;
|
||||
index += AppendHeaderEndOfBody(&res[index]);
|
||||
|
||||
aStream->Close();
|
||||
aStream = nullptr;
|
||||
mVCardDataStream->Close();
|
||||
mVCardDataStream = nullptr;
|
||||
}
|
||||
|
||||
SendObexData(res, opcode, index);
|
||||
@ -962,19 +961,20 @@ BluetoothPbapManager::ReplyToGet(nsIInputStream* aStream,
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothPbapManager::GetInputStreamFromBlob(nsIInputStream* aStream,
|
||||
Blob* aBlob)
|
||||
BluetoothPbapManager::GetInputStreamFromBlob(Blob* aBlob)
|
||||
{
|
||||
// PBAP can only handle one OBEX BODY transfer at the same time.
|
||||
if (mVCardDataStream) {
|
||||
BT_WARNING("Shouldn't handle multiple PBAP responses at the same time");
|
||||
BT_LOGR("Shouldn't handle multiple PBAP responses simultaneously");
|
||||
mVCardDataStream->Close();
|
||||
mVCardDataStream = nullptr;
|
||||
}
|
||||
|
||||
ErrorResult rv;
|
||||
aBlob->GetInternalStream(getter_AddRefs(mVCardDataStream), rv);
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
if (rv.Failed()) {
|
||||
BT_LOGR("Failed to get internal stream from blob. rv=0x%x",
|
||||
rv.ErrorCodeAsInt());
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1043,11 +1043,12 @@ BluetoothPbapManager::OnSocketDisconnect(BluetoothSocket* aSocket)
|
||||
void
|
||||
BluetoothPbapManager::Disconnect(BluetoothProfileController* aController)
|
||||
{
|
||||
if (mSocket) {
|
||||
mSocket->Close();
|
||||
} else {
|
||||
BT_WARNING("%s: No ongoing connection to disconnect", __FUNCTION__);
|
||||
if (!mSocket) {
|
||||
BT_LOGR("No ongoing connection to disconnect");
|
||||
return;
|
||||
}
|
||||
|
||||
mSocket->Close();
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(BluetoothPbapManager, nsIObserver)
|
||||
|
@ -143,8 +143,7 @@ private:
|
||||
void ReplyToSetPath();
|
||||
void ReplyError(uint8_t aError);
|
||||
void SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize);
|
||||
bool ReplyToGet(nsIInputStream* aStream, uint16_t aPhonebookSize = 0);
|
||||
bool GetInputStreamFromBlob(nsIInputStream* aStream, Blob* aBlob);
|
||||
bool ReplyToGet(uint16_t aPhonebookSize = 0);
|
||||
|
||||
uint8_t SetPhoneBookPath(uint8_t flags, const ObexHeaderSet& aHeader);
|
||||
uint8_t PullPhonebook(const ObexHeaderSet& aHeader);
|
||||
@ -158,6 +157,7 @@ private:
|
||||
InfallibleTArray<uint32_t> PackPropertiesMask(uint8_t* aData, int aSize);
|
||||
bool CompareHeaderTarget(const ObexHeaderSet& aHeader);
|
||||
bool IsLegalPath(const nsAString& aPath);
|
||||
bool GetInputStreamFromBlob(Blob* aBlob);
|
||||
void AfterPbapConnected();
|
||||
void AfterPbapDisconnected();
|
||||
|
||||
@ -188,15 +188,14 @@ private:
|
||||
nsRefPtr<BluetoothSocket> mServerSocket;
|
||||
|
||||
/**
|
||||
* The data stream of vCards which is used in current processing response.
|
||||
* The vCard data stream for current processing response
|
||||
*/
|
||||
nsCOMPtr<nsIInputStream> mVCardDataStream;
|
||||
|
||||
/**
|
||||
* A flag to indicate whether 'PhonebookSize' is mandatory for next OBEX
|
||||
* response
|
||||
* Whether 'PhonebookSize' is mandatory for next OBEX response
|
||||
*/
|
||||
bool mRequirePhonebookSize;
|
||||
bool mPhonebookSizeRequired;
|
||||
};
|
||||
|
||||
END_BLUETOOTH_NAMESPACE
|
||||
|
@ -185,8 +185,8 @@ HTTP(..) != 1170688.html 1170688-ref.html
|
||||
== emoji-03.html emoji-03-ref.html
|
||||
# the next two will fail on OS X 10.6 and on Windows prior to 8.1 because no color emoji font is present,
|
||||
# and also on Linux/Android/B2G platforms until we have color emoji fonts there
|
||||
fails-if(OSX==1006||/^Windows\x20NT\x20(5|6\.[0-2])/.test(http.oscpu)||gtkWidget||Android||B2G) != emoji-03.html emoji-03-notref.html
|
||||
fails-if(OSX==1006||/^Windows\x20NT\x20(5|6\.[0-2])/.test(http.oscpu)||gtkWidget||Android||B2G) == emoji-04.html emoji-04-ref.html
|
||||
fails-if(OSX==1006||/^Windows\x20NT\x20(5|6\.[0-2])/.test(http.oscpu)||gtkWidget||Android) != emoji-03.html emoji-03-notref.html
|
||||
fails-if(OSX==1006||/^Windows\x20NT\x20(5|6\.[0-2])/.test(http.oscpu)||gtkWidget||Android) == emoji-04.html emoji-04-ref.html
|
||||
!= emoji-05.html emoji-05-notref.html
|
||||
|
||||
# check that Graphite shaping (bug 631479) is working
|
||||
|
@ -97,6 +97,8 @@ public interface BrowserDB {
|
||||
*/
|
||||
public abstract Cursor getRecentHistory(ContentResolver cr, int limit);
|
||||
|
||||
public abstract Cursor getRecentHistoryBetweenTime(ContentResolver cr, int historyLimit, long start, long end);
|
||||
|
||||
public abstract void expireHistory(ContentResolver cr, ExpirePriority priority);
|
||||
|
||||
public abstract void removeHistoryEntry(ContentResolver cr, String url);
|
||||
|
@ -719,6 +719,21 @@ public class LocalBrowserDB implements BrowserDB {
|
||||
History.DATE_LAST_VISITED + " DESC");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getRecentHistoryBetweenTime(ContentResolver cr, int limit, long start, long end) {
|
||||
return cr.query(combinedUriWithLimit(limit),
|
||||
new String[] { Combined._ID,
|
||||
Combined.BOOKMARK_ID,
|
||||
Combined.HISTORY_ID,
|
||||
Combined.URL,
|
||||
Combined.TITLE,
|
||||
Combined.DATE_LAST_VISITED,
|
||||
Combined.VISITS },
|
||||
History.DATE_LAST_VISITED + " >= " + start + " AND " + History.DATE_LAST_VISITED + " < " + end,
|
||||
null,
|
||||
History.DATE_LAST_VISITED + " DESC");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireHistory(ContentResolver cr, ExpirePriority priority) {
|
||||
Uri url = mHistoryExpireUriWithProfile;
|
||||
|
@ -224,6 +224,11 @@ public class StubBrowserDB implements BrowserDB {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getRecentHistoryBetweenTime(ContentResolver cr, int limit, long time, long end) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void expireHistory(ContentResolver cr, BrowserContract.ExpirePriority priority) {
|
||||
}
|
||||
|
||||
|
143
mobile/android/base/home/HistoryHeaderListCursorAdapter.java
Normal file
143
mobile/android/base/home/HistoryHeaderListCursorAdapter.java
Normal file
@ -0,0 +1,143 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
|
||||
/**
|
||||
* CursorAdapter for <code>HistoryPanel</code> to partition history items by <code>MostRecentSection</code> range headers.
|
||||
*/
|
||||
public class HistoryHeaderListCursorAdapter extends MultiTypeCursorAdapter implements HistoryPanel.HistoryUrlProvider {
|
||||
private static final int ROW_HEADER = 0;
|
||||
private static final int ROW_STANDARD = 1;
|
||||
|
||||
private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
|
||||
private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
|
||||
|
||||
// Maps headers in the list with their respective sections
|
||||
private final SparseArray<HistoryPanel.MostRecentSection> mMostRecentSections;
|
||||
|
||||
public HistoryHeaderListCursorAdapter(Context context) {
|
||||
super(context, null, VIEW_TYPES, LAYOUT_TYPES);
|
||||
|
||||
// Initialize map of history sections
|
||||
mMostRecentSections = new SparseArray<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
final int type = getItemViewType(position);
|
||||
|
||||
// Header items are not in the cursor
|
||||
if (type == ROW_HEADER) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return super.getItem(position - getMostRecentSectionsCountBefore(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (mMostRecentSections.get(position) != null) {
|
||||
return ROW_HEADER;
|
||||
}
|
||||
|
||||
return ROW_STANDARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return (getItemViewType(position) == ROW_STANDARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
// Add the history section headers to the number of reported results.
|
||||
return super.getCount() + mMostRecentSections.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor cursor) {
|
||||
loadMostRecentSections(cursor);
|
||||
Cursor oldCursor = super.swapCursor(cursor);
|
||||
return oldCursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, int position) {
|
||||
final int type = getItemViewType(position);
|
||||
|
||||
if (type == ROW_HEADER) {
|
||||
final HistoryPanel.MostRecentSection section = mMostRecentSections.get(position);
|
||||
final TextView row = (TextView) view;
|
||||
row.setText(HistoryPanel.getMostRecentSectionTitle(section));
|
||||
} else {
|
||||
// Account for the most recent section headers
|
||||
position -= getMostRecentSectionsCountBefore(position);
|
||||
final Cursor c = getCursor(position);
|
||||
final TwoLinePageRow row = (TwoLinePageRow) view;
|
||||
row.updateFromCursor(c);
|
||||
}
|
||||
}
|
||||
|
||||
private int getMostRecentSectionsCountBefore(int position) {
|
||||
// Account for the number headers before the given position
|
||||
int sectionsBefore = 0;
|
||||
|
||||
final int historySectionsCount = mMostRecentSections.size();
|
||||
for (int i = 0; i < historySectionsCount; i++) {
|
||||
final int sectionPosition = mMostRecentSections.keyAt(i);
|
||||
if (sectionPosition > position) {
|
||||
break;
|
||||
}
|
||||
|
||||
sectionsBefore++;
|
||||
}
|
||||
|
||||
return sectionsBefore;
|
||||
}
|
||||
|
||||
private void loadMostRecentSections(Cursor c) {
|
||||
// Clear any history sections that may have been loaded before.
|
||||
mMostRecentSections.clear();
|
||||
|
||||
if (c == null || !c.moveToFirst()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HistoryPanel.MostRecentSection section = null;
|
||||
|
||||
do {
|
||||
final int position = c.getPosition();
|
||||
final long time = c.getLong(c.getColumnIndexOrThrow(BrowserContract.History.DATE_LAST_VISITED));
|
||||
final HistoryPanel.MostRecentSection itemSection = HistoryPanel.getMostRecentSectionForTime(time);
|
||||
|
||||
if (section != itemSection) {
|
||||
section = itemSection;
|
||||
mMostRecentSections.append(position + mMostRecentSections.size(), section);
|
||||
}
|
||||
|
||||
// Reached the last section, no need to continue
|
||||
if (section == HistoryPanel.MostRecentSection.OLDER_THAN_SIX_MONTHS) {
|
||||
break;
|
||||
}
|
||||
} while (c.moveToNext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURL(int position) {
|
||||
position -= getMostRecentSectionsCountBefore(position);
|
||||
final Cursor c = getCursor(position);
|
||||
return c.getString(c.getColumnIndexOrThrow(BrowserContract.History.URL));
|
||||
}
|
||||
}
|
53
mobile/android/base/home/HistoryItemAdapter.java
Normal file
53
mobile/android/base/home/HistoryItemAdapter.java
Normal file
@ -0,0 +1,53 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
|
||||
/**
|
||||
* A Cursor adapter that is used to populate the history list items in split plane mode.
|
||||
*/
|
||||
public class HistoryItemAdapter extends CursorAdapter implements HistoryPanel.HistoryUrlProvider {
|
||||
private final int resource;
|
||||
|
||||
public HistoryItemAdapter(Context context, Cursor c, int resource) {
|
||||
super(context, c, false);
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return LayoutInflater.from(context).inflate(resource, parent, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
return super.getView(position, convertView, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
final TwoLinePageRow row = (TwoLinePageRow) view;
|
||||
row.updateFromCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURL(int position) {
|
||||
final Cursor cursor = getCursor();
|
||||
if (cursor == null || !cursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("Couldn't move cursor to position " + position);
|
||||
}
|
||||
|
||||
return cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.URL));
|
||||
}
|
||||
}
|
@ -5,8 +5,11 @@
|
||||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -15,25 +18,29 @@ import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.GeckoScreenOrientation;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.RestrictedProfiles;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.BrowserContract.History;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.restrictions.Restriction;
|
||||
import org.mozilla.gecko.util.ColorUtils;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextPaint;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
@ -41,13 +48,13 @@ import android.text.style.ClickableSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewStub;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
@ -58,6 +65,11 @@ public class HistoryPanel extends HomeFragment {
|
||||
// Logging tag name
|
||||
private static final String LOGTAG = "GeckoHistoryPanel";
|
||||
|
||||
// For the time sections in history
|
||||
private static final long MS_PER_DAY = 86400000;
|
||||
private static final long MS_PER_WEEK = MS_PER_DAY * 7;
|
||||
private static final List<MostRecentSectionRange> recentSectionTimeOffsetList = new ArrayList<>(MostRecentSection.values().length);
|
||||
|
||||
// Cursor loader ID for history query
|
||||
private static final int LOADER_ID_HISTORY = 0;
|
||||
|
||||
@ -65,11 +77,19 @@ public class HistoryPanel extends HomeFragment {
|
||||
private final static String FORMAT_S1 = "%1$s";
|
||||
private final static String FORMAT_S2 = "%2$s";
|
||||
|
||||
// Maintain selected range state.
|
||||
// Only accessed from the UI thread.
|
||||
private static MostRecentSection selected;
|
||||
|
||||
// Adapter for the list of recent history entries.
|
||||
private HistoryAdapter mAdapter;
|
||||
private CursorAdapter mAdapter;
|
||||
|
||||
// Adapter for the timeline of history entries.
|
||||
private ArrayAdapter<MostRecentSection> mRangeAdapter;
|
||||
|
||||
// The view shown by the fragment.
|
||||
private HomeListView mList;
|
||||
private HomeListView mRangeList;
|
||||
|
||||
// The button view for clearing browsing history.
|
||||
private View mClearHistoryButton;
|
||||
@ -80,22 +100,62 @@ public class HistoryPanel extends HomeFragment {
|
||||
// Callbacks used for the search and favicon cursor loaders
|
||||
private CursorLoaderCallbacks mCursorLoaderCallbacks;
|
||||
|
||||
// The time ranges for each section
|
||||
public enum MostRecentSection {
|
||||
TODAY,
|
||||
YESTERDAY,
|
||||
WEEK,
|
||||
THIS_MONTH,
|
||||
MONTH_AGO,
|
||||
TWO_MONTHS_AGO,
|
||||
THREE_MONTHS_AGO,
|
||||
FOUR_MONTHS_AGO,
|
||||
FIVE_MONTHS_AGO,
|
||||
MostRecentSection, OLDER_THAN_SIX_MONTHS
|
||||
};
|
||||
|
||||
protected interface HistoryUrlProvider {
|
||||
public String getURL(int position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.home_history_panel, container, false);
|
||||
if (HardwareUtils.isTablet() && GeckoScreenOrientation.getInstance().getAndroidOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
return inflater.inflate(R.layout.home_history_split_pane_panel, container, false);
|
||||
} else {
|
||||
return inflater.inflate(R.layout.home_history_panel, container, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
mRangeList = (HomeListView) view.findViewById(R.id.range_list);
|
||||
mList = (HomeListView) view.findViewById(R.id.list);
|
||||
mList.setTag(HomePager.LIST_TAG_HISTORY);
|
||||
|
||||
if (mRangeList != null) {
|
||||
mRangeList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
|
||||
final MostRecentSection rangeItem = (MostRecentSection) adapter.getItemAtPosition(position);
|
||||
if (rangeItem != null) {
|
||||
// Notify data has changed for both range and item adapter.
|
||||
// This will update selected rangeItem item background and the tabs list.
|
||||
// This will also update the selected range along with cursor start and end.
|
||||
selected = rangeItem;
|
||||
mRangeAdapter.notifyDataSetChanged();
|
||||
getLoaderManager().getLoader(LOADER_ID_HISTORY).forceLoad();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
position -= mAdapter.getMostRecentSectionsCountBefore(position);
|
||||
final Cursor c = mAdapter.getCursor(position);
|
||||
final String url = c.getString(c.getColumnIndexOrThrow(History.URL));
|
||||
final String url = ((HistoryUrlProvider) mAdapter).getURL(position);
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM);
|
||||
|
||||
@ -177,6 +237,7 @@ public class HistoryPanel extends HomeFragment {
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mRangeList = null;
|
||||
mList = null;
|
||||
mEmptyView = null;
|
||||
mClearHistoryButton = null;
|
||||
@ -186,37 +247,45 @@ public class HistoryPanel extends HomeFragment {
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
// Intialize adapter
|
||||
mAdapter = new HistoryAdapter(getActivity());
|
||||
mList.setAdapter(mAdapter);
|
||||
// Reset selection.
|
||||
selected = mRangeList == null ? MostRecentSection.THIS_MONTH : MostRecentSection.TODAY;
|
||||
|
||||
// Initialize adapter
|
||||
if (mRangeList != null) {
|
||||
mAdapter = new HistoryItemAdapter(getActivity(), null, R.layout.home_item_row);
|
||||
mRangeAdapter = new HistoryRangeAdapter(getActivity(), R.layout.home_history_range_item);
|
||||
|
||||
mRangeList.setAdapter(mRangeAdapter);
|
||||
mList.setAdapter(mAdapter);
|
||||
} else {
|
||||
mAdapter = new HistoryHeaderListCursorAdapter(getActivity());
|
||||
mList.setAdapter(mAdapter);
|
||||
}
|
||||
|
||||
// Create callbacks before the initial loader is started
|
||||
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
|
||||
|
||||
// Update the section string with current time as reference.
|
||||
updateRecentSectionOffset(getActivity());
|
||||
loadIfVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadIfVisible() {
|
||||
// Force reload fragment only in tablets.
|
||||
if (canLoad() && HardwareUtils.isTablet()) {
|
||||
load();
|
||||
return;
|
||||
}
|
||||
|
||||
super.loadIfVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void load() {
|
||||
getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
|
||||
}
|
||||
|
||||
private static class HistoryCursorLoader extends SimpleCursorLoader {
|
||||
// Max number of history results
|
||||
private static final int HISTORY_LIMIT = 100;
|
||||
private final BrowserDB mDB;
|
||||
|
||||
public HistoryCursorLoader(Context context) {
|
||||
super(context);
|
||||
mDB = GeckoProfile.get(context).getDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
final ContentResolver cr = getContext().getContentResolver();
|
||||
return mDB.getRecentHistory(cr, HISTORY_LIMIT);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUiFromCursor(Cursor c) {
|
||||
if (c != null && c.getCount() > 0) {
|
||||
if (RestrictedProfiles.isAllowed(getActivity(), Restriction.DISALLOW_CLEAR_HISTORY)) {
|
||||
@ -314,176 +383,109 @@ public class HistoryPanel extends HomeFragment {
|
||||
return ssb;
|
||||
}
|
||||
|
||||
private static class HistoryAdapter extends MultiTypeCursorAdapter {
|
||||
private static final int ROW_HEADER = 0;
|
||||
private static final int ROW_STANDARD = 1;
|
||||
private static void updateRecentSectionOffset(final Context context) {
|
||||
final long now = System.currentTimeMillis();
|
||||
final Calendar cal = Calendar.getInstance();
|
||||
cal.set(Calendar.HOUR_OF_DAY, 0);
|
||||
cal.set(Calendar.MINUTE, 0);
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
cal.set(Calendar.MILLISECOND, 1);
|
||||
|
||||
private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
|
||||
private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
|
||||
// Calculate the start, end time and display text for the MostRecentSection range.
|
||||
recentSectionTimeOffsetList.add(MostRecentSection.TODAY.ordinal(),
|
||||
new MostRecentSectionRange(cal.getTimeInMillis(), now, context.getString(R.string.history_today_section)));
|
||||
recentSectionTimeOffsetList.add(MostRecentSection.YESTERDAY.ordinal(),
|
||||
new MostRecentSectionRange(cal.getTimeInMillis() - MS_PER_DAY, cal.getTimeInMillis(), context.getString(R.string.history_yesterday_section)));
|
||||
recentSectionTimeOffsetList.add(MostRecentSection.WEEK.ordinal(),
|
||||
new MostRecentSectionRange(cal.getTimeInMillis() - MS_PER_WEEK, now, context.getString(R.string.history_week_section)));
|
||||
|
||||
// For the time sections in history
|
||||
private static final long MS_PER_DAY = 86400000;
|
||||
private static final long MS_PER_WEEK = MS_PER_DAY * 7;
|
||||
// Update the calendar to start of next month.
|
||||
cal.add(Calendar.MONTH, 1);
|
||||
cal.set(Calendar.DAY_OF_MONTH, cal.getMinimum(Calendar.DAY_OF_MONTH));
|
||||
|
||||
// The time ranges for each section
|
||||
private static enum MostRecentSection {
|
||||
TODAY,
|
||||
YESTERDAY,
|
||||
WEEK,
|
||||
OLDER
|
||||
};
|
||||
// Iterate over the remaining MostRecentSections, to find the start, end and display text.
|
||||
for (int i = MostRecentSection.THIS_MONTH.ordinal(); i <= MostRecentSection.OLDER_THAN_SIX_MONTHS.ordinal(); i++) {
|
||||
final long end = cal.getTimeInMillis();
|
||||
cal.add(Calendar.MONTH, -1);
|
||||
final long start = cal.getTimeInMillis();
|
||||
final String displayName = (i != MostRecentSection.OLDER_THAN_SIX_MONTHS.ordinal())
|
||||
? cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())
|
||||
: context.getString(R.string.history_older_section);
|
||||
recentSectionTimeOffsetList.add(i, new MostRecentSectionRange(start, end, displayName));
|
||||
}
|
||||
}
|
||||
|
||||
private final Context mContext;
|
||||
private static class HistoryCursorLoader extends SimpleCursorLoader {
|
||||
// Max number of history results
|
||||
private static final int HISTORY_LIMIT = 100;
|
||||
private final BrowserDB mDB;
|
||||
|
||||
// Maps headers in the list with their respective sections
|
||||
private final SparseArray<MostRecentSection> mMostRecentSections;
|
||||
|
||||
public HistoryAdapter(Context context) {
|
||||
super(context, null, VIEW_TYPES, LAYOUT_TYPES);
|
||||
|
||||
mContext = context;
|
||||
|
||||
// Initialize map of history sections
|
||||
mMostRecentSections = new SparseArray<MostRecentSection>();
|
||||
public HistoryCursorLoader(Context context) {
|
||||
super(context);
|
||||
mDB = GeckoProfile.get(context).getDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
final int type = getItemViewType(position);
|
||||
public Cursor loadCursor() {
|
||||
final ContentResolver cr = getContext().getContentResolver();
|
||||
updateRecentSectionOffset(getContext());
|
||||
MostRecentSectionRange mostRecentSectionRange = recentSectionTimeOffsetList.get(selected.ordinal());
|
||||
return mDB.getRecentHistoryBetweenTime(cr, HISTORY_LIMIT, mostRecentSectionRange.start, mostRecentSectionRange.end);
|
||||
}
|
||||
}
|
||||
|
||||
// Header items are not in the cursor
|
||||
if (type == ROW_HEADER) {
|
||||
return null;
|
||||
protected static String getMostRecentSectionTitle(MostRecentSection section) {
|
||||
return recentSectionTimeOffsetList.get(section.ordinal()).displayName;
|
||||
}
|
||||
|
||||
protected static MostRecentSection getMostRecentSectionForTime(long time) {
|
||||
for (int i = 0; i < MostRecentSection.OLDER_THAN_SIX_MONTHS.ordinal(); i++) {
|
||||
if (time > recentSectionTimeOffsetList.get(i).start) {
|
||||
return MostRecentSection.values()[i];
|
||||
}
|
||||
}
|
||||
|
||||
return super.getItem(position - getMostRecentSectionsCountBefore(position));
|
||||
return MostRecentSection.OLDER_THAN_SIX_MONTHS;
|
||||
}
|
||||
|
||||
private static class MostRecentSectionRange {
|
||||
private final long start;
|
||||
private final long end;
|
||||
private final String displayName;
|
||||
|
||||
private MostRecentSectionRange(long start, long end, String displayName) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HistoryRangeAdapter extends ArrayAdapter<MostRecentSection> {
|
||||
private final Context context;
|
||||
private final int resource;
|
||||
|
||||
public HistoryRangeAdapter(Context context, int resource) {
|
||||
super(context, resource, MostRecentSection.values());
|
||||
this.context = context;
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (mMostRecentSections.get(position) != null) {
|
||||
return ROW_HEADER;
|
||||
}
|
||||
|
||||
return ROW_STANDARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return (getItemViewType(position) == ROW_STANDARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
// Add the history section headers to the number of reported results.
|
||||
return super.getCount() + mMostRecentSections.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor cursor) {
|
||||
loadMostRecentSections(cursor);
|
||||
Cursor oldCursor = super.swapCursor(cursor);
|
||||
return oldCursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, int position) {
|
||||
final int type = getItemViewType(position);
|
||||
|
||||
if (type == ROW_HEADER) {
|
||||
final MostRecentSection section = mMostRecentSections.get(position);
|
||||
final TextView row = (TextView) view;
|
||||
row.setText(getMostRecentSectionTitle(section));
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
final View view;
|
||||
if (convertView != null) {
|
||||
view = convertView;
|
||||
} else {
|
||||
// Account for the most recent section headers
|
||||
position -= getMostRecentSectionsCountBefore(position);
|
||||
final Cursor c = getCursor(position);
|
||||
final TwoLinePageRow row = (TwoLinePageRow) view;
|
||||
row.updateFromCursor(c);
|
||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||
view = inflater.inflate(resource, parent, false);
|
||||
view.setTag(view.findViewById(R.id.range_title));
|
||||
}
|
||||
}
|
||||
|
||||
private String getMostRecentSectionTitle(MostRecentSection section) {
|
||||
switch (section) {
|
||||
case TODAY:
|
||||
return mContext.getString(R.string.history_today_section);
|
||||
case YESTERDAY:
|
||||
return mContext.getString(R.string.history_yesterday_section);
|
||||
case WEEK:
|
||||
return mContext.getString(R.string.history_week_section);
|
||||
case OLDER:
|
||||
return mContext.getString(R.string.history_older_section);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Unrecognized history section");
|
||||
}
|
||||
|
||||
private int getMostRecentSectionsCountBefore(int position) {
|
||||
// Account for the number headers before the given position
|
||||
int sectionsBefore = 0;
|
||||
|
||||
final int historySectionsCount = mMostRecentSections.size();
|
||||
for (int i = 0; i < historySectionsCount; i++) {
|
||||
final int sectionPosition = mMostRecentSections.keyAt(i);
|
||||
if (sectionPosition > position) {
|
||||
break;
|
||||
}
|
||||
|
||||
sectionsBefore++;
|
||||
}
|
||||
|
||||
return sectionsBefore;
|
||||
}
|
||||
|
||||
private static MostRecentSection getMostRecentSectionForTime(long from, long time) {
|
||||
long delta = from - time;
|
||||
|
||||
if (delta < 0) {
|
||||
return MostRecentSection.TODAY;
|
||||
}
|
||||
|
||||
if (delta < MS_PER_DAY) {
|
||||
return MostRecentSection.YESTERDAY;
|
||||
}
|
||||
|
||||
if (delta < MS_PER_WEEK) {
|
||||
return MostRecentSection.WEEK;
|
||||
}
|
||||
|
||||
return MostRecentSection.OLDER;
|
||||
}
|
||||
|
||||
private void loadMostRecentSections(Cursor c) {
|
||||
// Clear any history sections that may have been loaded before.
|
||||
mMostRecentSections.clear();
|
||||
|
||||
if (c == null || !c.moveToFirst()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Date now = new Date();
|
||||
now.setHours(0);
|
||||
now.setMinutes(0);
|
||||
now.setSeconds(0);
|
||||
|
||||
final long today = now.getTime();
|
||||
MostRecentSection section = null;
|
||||
|
||||
do {
|
||||
final int position = c.getPosition();
|
||||
final long time = c.getLong(c.getColumnIndexOrThrow(History.DATE_LAST_VISITED));
|
||||
final MostRecentSection itemSection = HistoryAdapter.getMostRecentSectionForTime(today, time);
|
||||
|
||||
if (section != itemSection) {
|
||||
section = itemSection;
|
||||
mMostRecentSections.append(position + mMostRecentSections.size(), section);
|
||||
}
|
||||
|
||||
// Reached the last section, no need to continue
|
||||
if (section == MostRecentSection.OLDER) {
|
||||
break;
|
||||
}
|
||||
} while (c.moveToNext());
|
||||
final MostRecentSection current = getItem(position);
|
||||
final TextView textView = (TextView) view.getTag();
|
||||
textView.setText(getMostRecentSectionTitle(current));
|
||||
textView.setTextColor(ColorUtils.getColor(context, current == selected ? R.color.text_and_tabs_tray_grey : R.color.disabled_grey));
|
||||
textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, current == selected ? R.drawable.home_group_collapsed : 0, 0);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,27 +218,22 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
||||
final ContentResolver cr = getContext().getContentResolver();
|
||||
|
||||
String[] columns = new String[] { SearchHistory.QUERY };
|
||||
String sortOrderAndLimit = SearchHistory.DATE + " DESC";
|
||||
String actualQuery = SearchHistory.QUERY + " LIKE ?";
|
||||
String[] queryArgs = new String[] { '%' + searchTerm + '%' };
|
||||
String sortOrderAndLimit = SearchHistory.DATE + " DESC LIMIT 4";
|
||||
|
||||
final Cursor c = cr.query(SearchHistory.CONTENT_URI, columns, actualQuery, queryArgs, sortOrderAndLimit);
|
||||
|
||||
final Cursor c = cr.query(SearchHistory.CONTENT_URI, columns, null, null, sortOrderAndLimit);
|
||||
if (c == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (c.moveToFirst()) {
|
||||
int counter = 0;
|
||||
final int searchColumn = c.getColumnIndexOrThrow(SearchHistory.QUERY);
|
||||
do {
|
||||
final String savedSearch = c.getString(searchColumn);
|
||||
if (counter == 4) {
|
||||
break;
|
||||
}
|
||||
// Bug 1200371 will move the filtering/matching and limit into the sql query
|
||||
if (savedSearch.startsWith(searchTerm)) {
|
||||
bindSuggestionView(savedSearch, animate, recycledSuggestionCount, suggestionCounter, true);
|
||||
++suggestionCounter;
|
||||
++counter;
|
||||
}
|
||||
bindSuggestionView(savedSearch, animate, recycledSuggestionCount, suggestionCounter, true);
|
||||
++suggestionCounter;
|
||||
} while (c.moveToNext());
|
||||
}
|
||||
} finally {
|
||||
|
@ -55,8 +55,9 @@
|
||||
|
||||
<!ENTITY history_today_section "Today">
|
||||
<!ENTITY history_yesterday_section "Yesterday">
|
||||
<!ENTITY history_week_section2 "Last Week">
|
||||
<!ENTITY history_older_section2 "Last Month">
|
||||
<!ENTITY history_week_section3 "Last 7 days">
|
||||
<!ENTITY history_this_month_section "This month">
|
||||
<!ENTITY history_older_section3 "Older than 6 months">
|
||||
|
||||
<!ENTITY go "Go">
|
||||
<!ENTITY search "Search">
|
||||
@ -69,8 +70,6 @@
|
||||
<!ENTITY edit_mode_cancel "Cancel">
|
||||
|
||||
<!ENTITY close_tab "Close Tab">
|
||||
<!ENTITY tab_audio_playing "This tab is playing audio">
|
||||
<!ENTITY tab_audio_muted "This tab has been muted">
|
||||
<!ENTITY one_tab "1 tab">
|
||||
<!-- Localization note (num_tabs2) : Number of tabs is always more than one.
|
||||
We can't use android plural forms, sadly. See bug #753859. -->
|
||||
@ -81,6 +80,14 @@
|
||||
as possible because it's shown as a label in a toast. Ideally, this string
|
||||
is upper-case, to match Google and Android's convention. -->
|
||||
<!ENTITY switch_button_message "SWITCH">
|
||||
<!-- Localization note (tab_title_prefix_is_playing_audio): This string is not
|
||||
visible in the UI, but rather used as a text-to-speech content description
|
||||
for sight-impaired a11y users. The content description is set on a tab
|
||||
title in a list of open tabs when content in that tab is playing audio.
|
||||
&formatS; will be replaced with the title of the tab, as received from the
|
||||
web page. When audio is not playing in a tab, &formatS; will be used as
|
||||
the content description. -->
|
||||
<!ENTITY tab_title_prefix_is_playing_audio "Playing audio – &formatS;">
|
||||
|
||||
<!ENTITY settings "Settings">
|
||||
<!ENTITY settings_title "Settings">
|
||||
|
@ -313,6 +313,8 @@ gbjar.sources += [
|
||||
'home/BrowserSearch.java',
|
||||
'home/DynamicPanel.java',
|
||||
'home/FramePanelLayout.java',
|
||||
'home/HistoryHeaderListCursorAdapter.java',
|
||||
'home/HistoryItemAdapter.java',
|
||||
'home/HistoryPanel.java',
|
||||
'home/HomeAdapter.java',
|
||||
'home/HomeBanner.java',
|
||||
|
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:background="@color/about_page_header_grey"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/page_row_height">
|
||||
|
||||
<TextView android:id="@+id/range_title"
|
||||
style="@style/Widget.TwoLinePageRow.Title"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
</FrameLayout>
|
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<org.mozilla.gecko.home.HomeListView
|
||||
android:id="@+id/range_list"
|
||||
style="@style/Widget.HistoryListView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="@dimen/split_plane_left_pane_weight"/>
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/divider_light"/>
|
||||
|
||||
<ViewStub android:id="@id/home_empty_view_stub"
|
||||
android:layout="@layout/home_empty_panel"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="@dimen/split_plane_right_pane_weight"/>
|
||||
|
||||
<org.mozilla.gecko.home.HomeListView
|
||||
android:id="@+id/list"
|
||||
style="@style/Widget.HistoryListView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="@dimen/split_plane_right_pane_weight"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button android:id="@+id/clear_history_button"
|
||||
style="@style/Widget.Home.ActionButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/home_button_bar_bg"
|
||||
android:text="@string/home_clear_history_button"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
@ -218,4 +218,6 @@
|
||||
|
||||
<item name="tab_strip_content_start" type="dimen">12dp</item>
|
||||
|
||||
<item name="split_plane_left_pane_weight" format="float" type="dimen">0.32</item>
|
||||
<item name="split_plane_right_pane_weight" format="float" type="dimen">0.68</item>
|
||||
</resources>
|
||||
|
@ -578,6 +578,11 @@
|
||||
<item name="android:drawSelectorOnTop">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.HistoryListView" parent="Widget.HomeListView">
|
||||
<item name="android:childDivider">@color/divider_light</item>
|
||||
<item name="android:drawSelectorOnTop">true</item>
|
||||
</style>
|
||||
|
||||
<!-- TabsLayout Row -->
|
||||
<style name="TabLayoutItemTextAppearance">
|
||||
<item name="android:textColor">#FFFFFFFF</item>
|
||||
|
@ -87,8 +87,9 @@
|
||||
|
||||
<string name="history_today_section">&history_today_section;</string>
|
||||
<string name="history_yesterday_section">&history_yesterday_section;</string>
|
||||
<string name="history_week_section">&history_week_section2;</string>
|
||||
<string name="history_older_section">&history_older_section2;</string>
|
||||
<string name="history_week_section">&history_week_section3;</string>
|
||||
<string name="history_this_month_section">&history_this_month_section;</string>
|
||||
<string name="history_older_section">&history_older_section3;</string>
|
||||
|
||||
<string name="share">&share;</string>
|
||||
<string name="share_title">&share_title;</string>
|
||||
@ -311,11 +312,10 @@
|
||||
<string name="stop">&stop;</string>
|
||||
<string name="site_security">&site_security;</string>
|
||||
<string name="close_tab">&close_tab;</string>
|
||||
<string name="tab_audio_playing">&tab_audio_playing;</string>
|
||||
<string name="tab_audio_muted">&tab_audio_muted;</string>
|
||||
<string name="new_tab_opened">&new_tab_opened;</string>
|
||||
<string name="new_private_tab_opened">&new_private_tab_opened;</string>
|
||||
<string name="switch_button_message">&switch_button_message;</string>
|
||||
<string name="tab_title_prefix_is_playing_audio">&tab_title_prefix_is_playing_audio;</string>
|
||||
<string name="one_tab">&one_tab;</string>
|
||||
<string name="num_tabs">&num_tabs2;</string>
|
||||
<string name="addons">&addons;</string>
|
||||
|
@ -141,14 +141,19 @@ public class TabsLayoutItemView extends LinearLayout
|
||||
if (mThumbnailWrapper != null) {
|
||||
mThumbnailWrapper.setRecording(tab.isRecording());
|
||||
}
|
||||
mTitle.setText(tab.getDisplayTitle());
|
||||
|
||||
final String tabTitle = tab.getDisplayTitle();
|
||||
mTitle.setText(tabTitle);
|
||||
mCloseButton.setTag(this);
|
||||
|
||||
// TODO: Set content description to indicate audio is playing.
|
||||
if (tab.isAudioPlaying()) {
|
||||
mTitle.setCompoundDrawablesWithIntrinsicBounds(R.drawable.tab_audio_playing, 0, 0, 0);
|
||||
final String tabTitleWithAudio =
|
||||
getResources().getString(R.string.tab_title_prefix_is_playing_audio, tabTitle);
|
||||
mTitle.setContentDescription(tabTitleWithAudio);
|
||||
} else {
|
||||
mTitle.setCompoundDrawables(null, null, null, null);
|
||||
mTitle.setContentDescription(tabTitle);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,6 +98,9 @@ body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#edit-login-header {
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.update-button {
|
||||
flex: 1;
|
||||
|
@ -12,7 +12,17 @@ Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://testing-common/services/sync/utils.js");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
const FAKE_SERVER_URL = "http://dummy:9000/";
|
||||
let fakeServer = new SyncServer();
|
||||
fakeServer.start();
|
||||
|
||||
do_register_cleanup(function() {
|
||||
return new Promise(resolve => {
|
||||
fakeServer.stop(resolve);
|
||||
});
|
||||
});
|
||||
|
||||
let fakeServerUrl = "http://localhost:" + fakeServer.port;
|
||||
|
||||
const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"], true);
|
||||
|
||||
const PROLONGED_ERROR_DURATION =
|
||||
@ -236,8 +246,8 @@ add_identity_test(this, function test_shouldReportError() {
|
||||
|
||||
// Give ourselves a clusterURL so that the temporary 401 no-error situation
|
||||
// doesn't come into play.
|
||||
Service.serverURL = FAKE_SERVER_URL;
|
||||
Service.clusterURL = FAKE_SERVER_URL;
|
||||
Service.serverURL = fakeServerUrl;
|
||||
Service.clusterURL = fakeServerUrl;
|
||||
|
||||
// Test dontIgnoreErrors, non-network, non-prolonged, login error reported
|
||||
Status.resetSync();
|
||||
@ -589,8 +599,8 @@ add_identity_test(this, function test_sync_syncAndReportErrors_prolonged_non_net
|
||||
add_identity_test(this, function test_login_syncAndReportErrors_network_error() {
|
||||
// Test network errors are reported when calling syncAndReportErrors.
|
||||
yield configureIdentity({username: "broken.wipe"});
|
||||
Service.serverURL = FAKE_SERVER_URL;
|
||||
Service.clusterURL = FAKE_SERVER_URL;
|
||||
Service.serverURL = fakeServerUrl;
|
||||
Service.clusterURL = fakeServerUrl;
|
||||
|
||||
let deferred = Promise.defer();
|
||||
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
|
||||
@ -629,8 +639,8 @@ add_identity_test(this, function test_login_syncAndReportErrors_prolonged_networ
|
||||
// when calling syncAndReportErrors.
|
||||
yield configureIdentity({username: "johndoe"});
|
||||
|
||||
Service.serverURL = FAKE_SERVER_URL;
|
||||
Service.clusterURL = FAKE_SERVER_URL;
|
||||
Service.serverURL = fakeServerUrl;
|
||||
Service.clusterURL = fakeServerUrl;
|
||||
|
||||
let deferred = Promise.defer();
|
||||
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
|
||||
@ -715,8 +725,8 @@ add_task(function test_sync_prolonged_non_network_error() {
|
||||
add_identity_test(this, function test_login_prolonged_network_error() {
|
||||
// Test prolonged, network errors are reported
|
||||
yield configureIdentity({username: "johndoe"});
|
||||
Service.serverURL = FAKE_SERVER_URL;
|
||||
Service.clusterURL = FAKE_SERVER_URL;
|
||||
Service.serverURL = fakeServerUrl;
|
||||
Service.clusterURL = fakeServerUrl;
|
||||
|
||||
let deferred = Promise.defer();
|
||||
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
|
||||
@ -801,8 +811,8 @@ add_task(function test_sync_non_network_error() {
|
||||
|
||||
add_identity_test(this, function test_login_network_error() {
|
||||
yield configureIdentity({username: "johndoe"});
|
||||
Service.serverURL = FAKE_SERVER_URL;
|
||||
Service.clusterURL = FAKE_SERVER_URL;
|
||||
Service.serverURL = fakeServerUrl;
|
||||
Service.clusterURL = fakeServerUrl;
|
||||
|
||||
let deferred = Promise.defer();
|
||||
// Test network errors are not reported.
|
||||
|
@ -175,6 +175,20 @@ def configure_dependent_task(task_path, parameters, taskid, templates, build_tre
|
||||
|
||||
return task
|
||||
|
||||
def set_interactive_task(task, interactive):
|
||||
r"""Make the task interactive.
|
||||
|
||||
:param task: task definition.
|
||||
:param interactive: True if the task should be interactive.
|
||||
"""
|
||||
if not interactive:
|
||||
return
|
||||
|
||||
payload = task["task"]["payload"]
|
||||
if "features" not in payload:
|
||||
payload["features"] = {}
|
||||
payload["features"]["interactive"] = True
|
||||
|
||||
@CommandProvider
|
||||
class DecisionTask(object):
|
||||
@Command('taskcluster-decision', category="ci",
|
||||
@ -255,6 +269,12 @@ class Graph(object):
|
||||
help='email address of who owns this graph')
|
||||
@CommandArgument('--extend-graph',
|
||||
action="store_true", dest="ci", help='Omit create graph arguments')
|
||||
@CommandArgument('--interactive',
|
||||
required=False,
|
||||
default=False,
|
||||
action="store_true",
|
||||
dest="interactive",
|
||||
help="Run the tasks with the interactive feature enabled")
|
||||
def create_graph(self, **params):
|
||||
from taskcluster_graph.commit_parser import parse_commit
|
||||
from taskcluster_graph.slugid import slugid
|
||||
@ -287,6 +307,8 @@ class Graph(object):
|
||||
# once everything uses in-tree mozharness (bug 1187706), this can go away.
|
||||
mozharness = load_mozharness_info()
|
||||
|
||||
cmdline_interactive = params.get('interactive', False)
|
||||
|
||||
# Template parameters used when expanding the graph
|
||||
parameters = dict(gaia_info().items() + {
|
||||
'index': 'index',
|
||||
@ -337,9 +359,11 @@ class Graph(object):
|
||||
}
|
||||
|
||||
for build in job_graph:
|
||||
interactive = cmdline_interactive or build["interactive"]
|
||||
build_parameters = dict(parameters)
|
||||
build_parameters['build_slugid'] = slugid()
|
||||
build_task = templates.load(build['task'], build_parameters)
|
||||
set_interactive_task(build_task, interactive)
|
||||
|
||||
if params['revision_hash']:
|
||||
decorate_task_treeherder_routes(build_task['task'],
|
||||
@ -412,6 +436,7 @@ class Graph(object):
|
||||
slugid(),
|
||||
templates,
|
||||
build_treeherder_config)
|
||||
set_interactive_task(post_task, interactive)
|
||||
graph['tasks'].append(post_task)
|
||||
|
||||
for test in build['dependents']:
|
||||
@ -443,6 +468,7 @@ class Graph(object):
|
||||
slugid(),
|
||||
templates,
|
||||
build_treeherder_config)
|
||||
set_interactive_task(test_task, interactive)
|
||||
|
||||
if params['revision_hash']:
|
||||
decorate_task_treeherder_routes(
|
||||
@ -485,6 +511,12 @@ class CIBuild(object):
|
||||
help='email address of who owns this graph')
|
||||
@CommandArgument('build_task',
|
||||
help='path to build task definition')
|
||||
@CommandArgument('--interactive',
|
||||
required=False,
|
||||
default=False,
|
||||
action="store_true",
|
||||
dest="interactive",
|
||||
help="Run the task with the interactive feature enabled")
|
||||
def create_ci_build(self, **params):
|
||||
from taskcluster_graph.templates import Templates
|
||||
import taskcluster_graph.build_task
|
||||
@ -518,6 +550,7 @@ class CIBuild(object):
|
||||
|
||||
try:
|
||||
build_task = templates.load(params['build_task'], build_parameters)
|
||||
set_interactive_task(build_task, params.get('interactive', False))
|
||||
except IOError:
|
||||
sys.stderr.write(
|
||||
"Could not load build task file. Ensure path is a relative " \
|
||||
|
@ -222,6 +222,7 @@ def parse_commit(message, jobs):
|
||||
parser.add_argument('-b', '--build', dest='build_types')
|
||||
parser.add_argument('-p', '--platform', nargs='?', dest='platforms', const='all', default='all')
|
||||
parser.add_argument('-u', '--unittests', nargs='?', dest='tests', const='all', default='all')
|
||||
parser.add_argument('-i', '--interactive', dest='interactive', action='store_true', default=False)
|
||||
args, unknown = parser.parse_known_args(parts[try_idx:])
|
||||
|
||||
# Then builds...
|
||||
@ -278,6 +279,7 @@ def parse_commit(message, jobs):
|
||||
'additional-parameters': additional_parameters,
|
||||
'build_name': platform,
|
||||
'build_type': build_type,
|
||||
'interactive': args.interactive,
|
||||
})
|
||||
|
||||
return result
|
||||
|
@ -2911,7 +2911,7 @@ PlacesCreateLivemarkTransaction.prototype = {
|
||||
|
||||
doTransaction: function CLTXN_doTransaction()
|
||||
{
|
||||
PlacesUtils.livemarks.addLivemark(
|
||||
this._promise = PlacesUtils.livemarks.addLivemark(
|
||||
{ title: this.item.title
|
||||
, feedURI: this.item.feedURI
|
||||
, parentId: this.item.parentId
|
||||
@ -2930,7 +2930,7 @@ PlacesCreateLivemarkTransaction.prototype = {
|
||||
{
|
||||
// The getLivemark callback may fail, but it is used just to serialize,
|
||||
// so it doesn't matter.
|
||||
PlacesUtils.livemarks.getLivemark({ id: this.item.id })
|
||||
this._promise = PlacesUtils.livemarks.getLivemark({ id: this.item.id })
|
||||
.then(null, null).then( () => {
|
||||
PlacesUtils.bookmarks.removeItem(this.item.id);
|
||||
});
|
||||
|
@ -4234,6 +4234,11 @@
|
||||
"extended_statistics_ok": true,
|
||||
"description": "Session restore: Time spent blocking the main thread while restoring a window state (ms)"
|
||||
},
|
||||
"FX_TABLET_MODE_USED_DURING_SESSION": {
|
||||
"expires_in_version": "46",
|
||||
"kind": "count",
|
||||
"description": "Windows 10+ only: The number of times tablet-mode is used during a session"
|
||||
},
|
||||
"FX_URLBAR_SELECTED_RESULT_INDEX": {
|
||||
"expires_in_version": "45",
|
||||
"kind": "enumerated",
|
||||
|
74
toolkit/devtools/heapsnapshot/HeapAnalysesClient.js
Normal file
74
toolkit/devtools/heapsnapshot/HeapAnalysesClient.js
Normal file
@ -0,0 +1,74 @@
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
const { DevToolsWorker } = require("devtools/toolkit/shared/worker.js");
|
||||
|
||||
const WORKER_URL = "resource://gre/modules/devtools/heapsnapshot/HeapAnalysesWorker.js";
|
||||
let workerCounter = 0;
|
||||
|
||||
/**
|
||||
* A HeapAnalysesClient instance provides a developer-friendly interface for
|
||||
* interacting with a HeapAnalysesWorker. This enables users to be ignorant of
|
||||
* the message passing protocol used to communicate with the worker. The
|
||||
* HeapAnalysesClient owns the worker, and terminating the worker is done by
|
||||
* terminating the client (see the `destroy` method).
|
||||
*/
|
||||
const HeapAnalysesClient = module.exports = function () {
|
||||
this._worker = new DevToolsWorker(WORKER_URL, {
|
||||
name: `HeapAnalyses-${workerCounter++}`,
|
||||
verbose: DevToolsUtils.dumpn.wantLogging
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the worker, causing it to release its resources (such as heap
|
||||
* snapshots it has deserialized and read into memory). The client is no longer
|
||||
* usable after calling this method.
|
||||
*/
|
||||
HeapAnalysesClient.prototype.destroy = function () {
|
||||
this._worker.destroy();
|
||||
};
|
||||
|
||||
/**
|
||||
* Tell the worker to read into memory the heap snapshot at the given file
|
||||
* path. This is a prerequisite for asking the worker to perform various
|
||||
* analyses on a heap snapshot.
|
||||
*
|
||||
* @param {String} snapshotFilePath
|
||||
*
|
||||
* @returns Promise
|
||||
* The promise is fulfilled if the heap snapshot is successfully
|
||||
* deserialized and read into memory. The promise is rejected if that
|
||||
* does not happen, eg due to a bad file path or malformed heap
|
||||
* snapshot file.
|
||||
*/
|
||||
HeapAnalysesClient.prototype.readHeapSnapshot = function (snapshotFilePath) {
|
||||
return this._worker.performTask("readHeapSnapshot", { snapshotFilePath });
|
||||
};
|
||||
|
||||
/**
|
||||
* Ask the worker to perform a census analysis on the heap snapshot with the
|
||||
* given path. The heap snapshot at the given path must have already been read
|
||||
* into memory by the worker (see `readHeapSnapshot`).
|
||||
*
|
||||
* @param {String} snapshotFilePath
|
||||
*
|
||||
* @param {Object} censusOptions
|
||||
* A structured-cloneable object specifying the requested census's
|
||||
* breakdown. See the "takeCensus" section of
|
||||
* `js/src/doc/Debugger/Debugger.Memory.md` for detailed documentation.
|
||||
*
|
||||
* @returns Promise<census report>
|
||||
* The report generated by the given census breakdown.
|
||||
*/
|
||||
HeapAnalysesClient.prototype.takeCensus = function (snapshotFilePath,
|
||||
censusOptions) {
|
||||
return this._worker.performTask("takeCensus", {
|
||||
snapshotFilePath,
|
||||
censusOptions
|
||||
});
|
||||
};
|
37
toolkit/devtools/heapsnapshot/HeapAnalysesWorker.js
Normal file
37
toolkit/devtools/heapsnapshot/HeapAnalysesWorker.js
Normal file
@ -0,0 +1,37 @@
|
||||
/* 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/. */
|
||||
/*global ThreadSafeChromeUtils*/
|
||||
|
||||
// This is a worker which reads offline heap snapshots into memory and performs
|
||||
// heavyweight analyses on them without blocking the main thread. A
|
||||
// HeapAnalysesWorker is owned and communicated with by a HeapAnalysesClient
|
||||
// instance. See HeapAnalysesClient.js.
|
||||
|
||||
"use strict";
|
||||
|
||||
importScripts("resource://gre/modules/devtools/shared/worker-helper.js");
|
||||
|
||||
// The set of HeapSnapshot instances this worker has read into memory. Keyed by
|
||||
// snapshot file path.
|
||||
const snapshots = Object.create(null);
|
||||
|
||||
/**
|
||||
* @see HeapAnalysesClient.prototype.readHeapSnapshot
|
||||
*/
|
||||
workerHelper.createTask(self, "readHeapSnapshot", ({ snapshotFilePath }) => {
|
||||
snapshots[snapshotFilePath] =
|
||||
ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath);
|
||||
return true;
|
||||
});
|
||||
|
||||
/**
|
||||
* @see HeapAnalysesClient.prototype.takeCensus
|
||||
*/
|
||||
workerHelper.createTask(self, "takeCensus", ({ snapshotFilePath, censusOptions }) => {
|
||||
if (!snapshots[snapshotFilePath]) {
|
||||
throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
|
||||
}
|
||||
|
||||
return snapshots[snapshotFilePath].takeCensus(censusOptions);
|
||||
});
|
37
toolkit/devtools/heapsnapshot/moz.build
Normal file
37
toolkit/devtools/heapsnapshot/moz.build
Normal file
@ -0,0 +1,37 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
if CONFIG['ENABLE_TESTS']:
|
||||
DIRS += ['tests/gtest']
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
|
||||
MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
|
||||
|
||||
EXPORTS.mozilla.devtools += [
|
||||
'AutoMemMap.h',
|
||||
'CoreDump.pb.h',
|
||||
'DeserializedNode.h',
|
||||
'HeapSnapshot.h',
|
||||
'ZeroCopyNSIOutputStream.h',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'AutoMemMap.cpp',
|
||||
'CoreDump.pb.cc',
|
||||
'DeserializedNode.cpp',
|
||||
'HeapSnapshot.cpp',
|
||||
'ZeroCopyNSIOutputStream.cpp',
|
||||
]
|
||||
|
||||
# Disable RTTI in google protocol buffer
|
||||
DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
EXTRA_JS_MODULES.devtools.heapsnapshot += [
|
||||
'HeapAnalysesClient.js',
|
||||
'HeapAnalysesWorker.js',
|
||||
]
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user