Source code
Revision control
Copy as Markdown
Other Tools
/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
/* 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
"use strict";
var { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
SiteDataManager: "resource:///modules/SiteDataManager.sys.mjs",
});
let gSiteDataSettings = {
// Array of metadata of sites. Each array element is object holding:
// - uri: uri of site; instance of nsIURI
// - baseDomain: base domain of the site
// - cookies: array of cookies of that site
// - usage: disk usage which site uses
// - userAction: "remove" or "update-permission"; the action user wants to take.
_sites: null,
_list: null,
_searchBox: null,
_createSiteListItem(site) {
let item = document.createXULElement("richlistitem");
item.setAttribute("host", site.baseDomain);
let container = document.createXULElement("hbox");
// Creates a new column item with the specified relative width.
function addColumnItem(l10n, flexWidth, tooltipText) {
let box = document.createXULElement("hbox");
box.className = "item-box";
box.setAttribute("style", `flex: ${flexWidth} ${flexWidth};`);
let label = document.createXULElement("label");
label.setAttribute("crop", "end");
if (l10n) {
if (l10n.hasOwnProperty("raw")) {
box.setAttribute("tooltiptext", l10n.raw);
label.setAttribute("value", l10n.raw);
} else {
document.l10n.setAttributes(label, l10n.id, l10n.args);
}
}
if (tooltipText) {
box.setAttribute("tooltiptext", tooltipText);
}
box.appendChild(label);
container.appendChild(box);
}
// Add "Host" column.
let hostData = site.baseDomain
? { raw: site.baseDomain }
: { id: "site-data-local-file-host" };
addColumnItem(hostData, "4");
// Add "Cookies" column.
addColumnItem({ raw: site.cookies.length }, "1");
// Add "Storage" column
if (site.usage > 0 || site.persisted) {
let [value, unit] = DownloadUtils.convertByteUnits(site.usage);
let strName = site.persisted
? "site-storage-persistent"
: "site-storage-usage";
addColumnItem(
{
id: strName,
args: { value, unit },
},
"2"
);
} else {
// Pass null to avoid showing "0KB" when there is no site data stored.
addColumnItem(null, "2");
}
// Add "Last Used" column.
let formattedLastAccessed =
site.lastAccessed > 0
? this._relativeTimeFormat.formatBestUnit(site.lastAccessed)
: null;
let formattedFullDate =
site.lastAccessed > 0
? this._absoluteTimeFormat.format(site.lastAccessed)
: null;
addColumnItem(
site.lastAccessed > 0 ? { raw: formattedLastAccessed } : null,
"2",
formattedFullDate
);
item.appendChild(container);
return item;
},
init() {
function setEventListener(id, eventType, callback) {
document
.getElementById(id)
.addEventListener(eventType, callback.bind(gSiteDataSettings));
}
this._absoluteTimeFormat = new Services.intl.DateTimeFormat(undefined, {
dateStyle: "short",
timeStyle: "short",
});
this._relativeTimeFormat = new Services.intl.RelativeTimeFormat(
undefined,
{}
);
this._list = document.getElementById("sitesList");
this._searchBox = document.getElementById("searchBox");
SiteDataManager.getSites().then(sites => {
this._sites = sites;
let sortCol = document.querySelector(
"treecol[data-isCurrentSortCol=true]"
);
this._sortSites(this._sites, sortCol);
this._buildSitesList(this._sites);
Services.obs.notifyObservers(null, "sitedata-settings-init");
});
setEventListener("sitesList", "select", this.onSelect);
setEventListener("hostCol", "click", this.onClickTreeCol);
setEventListener("usageCol", "click", this.onClickTreeCol);
setEventListener("lastAccessedCol", "click", this.onClickTreeCol);
setEventListener("cookiesCol", "click", this.onClickTreeCol);
setEventListener("searchBox", "command", this.onCommandSearch);
setEventListener("removeAll", "command", this.onClickRemoveAll);
setEventListener("removeSelected", "command", this.removeSelected);
document.addEventListener("dialogaccept", e => this.saveChanges(e));
},
_updateButtonsState() {
let items = this._list.getElementsByTagName("richlistitem");
let removeSelectedBtn = document.getElementById("removeSelected");
let removeAllBtn = document.getElementById("removeAll");
removeSelectedBtn.disabled = !this._list.selectedItems.length;
removeAllBtn.disabled = !items.length;
let l10nId = this._searchBox.value
? "site-data-remove-shown"
: "site-data-remove-all";
document.l10n.setAttributes(removeAllBtn, l10nId);
},
/**
* @param sites {Array}
* @param col {XULElement} the <treecol> being sorted on
*/
_sortSites(sites, col) {
let isCurrentSortCol = col.getAttribute("data-isCurrentSortCol");
let sortDirection =
col.getAttribute("data-last-sortDirection") || "ascending";
if (isCurrentSortCol) {
// Sort on the current column, flip the sorting direction
sortDirection =
sortDirection === "ascending" ? "descending" : "ascending";
}
let sortFunc = null;
switch (col.id) {
case "hostCol":
sortFunc = (a, b) => {
let aHost = a.baseDomain.toLowerCase();
let bHost = b.baseDomain.toLowerCase();
return aHost.localeCompare(bHost);
};
break;
case "cookiesCol":
sortFunc = (a, b) => a.cookies.length - b.cookies.length;
break;
case "usageCol":
sortFunc = (a, b) => a.usage - b.usage;
break;
case "lastAccessedCol":
sortFunc = (a, b) => a.lastAccessed - b.lastAccessed;
break;
}
if (sortDirection === "descending") {
sites.sort((a, b) => sortFunc(b, a));
} else {
sites.sort(sortFunc);
}
let cols = this._list.previousElementSibling.querySelectorAll("treecol");
cols.forEach(c => {
c.removeAttribute("sortDirection");
c.removeAttribute("data-isCurrentSortCol");
});
col.setAttribute("data-isCurrentSortCol", true);
col.setAttribute("sortDirection", sortDirection);
col.setAttribute("data-last-sortDirection", sortDirection);
},
/**
* @param sites {Array} array of metadata of sites
*/
_buildSitesList(sites) {
// Clear old entries.
let oldItems = this._list.querySelectorAll("richlistitem");
for (let item of oldItems) {
item.remove();
}
let keyword = this._searchBox.value.toLowerCase().trim();
let fragment = document.createDocumentFragment();
for (let site of sites) {
if (keyword && !site.baseDomain.includes(keyword)) {
continue;
}
if (site.userAction === "remove") {
continue;
}
let item = this._createSiteListItem(site);
fragment.appendChild(item);
}
this._list.appendChild(fragment);
this._updateButtonsState();
},
_removeSiteItems(items) {
for (let i = items.length - 1; i >= 0; --i) {
let item = items[i];
let baseDomain = item.getAttribute("host");
let siteForBaseDomain = this._sites.find(
site => site.baseDomain == baseDomain
);
if (siteForBaseDomain) {
siteForBaseDomain.userAction = "remove";
}
item.remove();
}
this._updateButtonsState();
},
async saveChanges(event) {
let removals = this._sites
.filter(site => site.userAction == "remove")
.map(site => site.baseDomain);
if (removals.length) {
let removeAll = removals.length == this._sites.length;
let promptArg = removeAll ? undefined : removals;
if (!SiteDataManager.promptSiteDataRemoval(window, promptArg)) {
// If the user cancelled the confirm dialog keep the site data window open,
// they can still press cancel again to exit.
event.preventDefault();
return;
}
try {
if (removeAll) {
await SiteDataManager.removeAll();
} else {
await SiteDataManager.remove(removals);
}
} catch (e) {
console.error(e);
}
}
},
removeSelected() {
let lastIndex = this._list.selectedItems.length - 1;
let lastSelectedItem = this._list.selectedItems[lastIndex];
let lastSelectedItemPosition = this._list.getIndexOfItem(lastSelectedItem);
let nextSelectedItem = this._list.getItemAtIndex(
lastSelectedItemPosition + 1
);
this._removeSiteItems(this._list.selectedItems);
this._list.clearSelection();
if (nextSelectedItem) {
this._list.selectedItem = nextSelectedItem;
} else {
this._list.selectedIndex = this._list.itemCount - 1;
}
},
onClickTreeCol(e) {
this._sortSites(this._sites, e.target);
this._buildSitesList(this._sites);
this._list.clearSelection();
},
onCommandSearch() {
this._buildSitesList(this._sites);
this._list.clearSelection();
},
onClickRemoveAll() {
let siteItems = this._list.getElementsByTagName("richlistitem");
if (siteItems.length) {
this._removeSiteItems(siteItems);
}
},
onKeyPress(e) {
if (
e.keyCode == KeyEvent.DOM_VK_DELETE ||
(AppConstants.platform == "macosx" &&
e.keyCode == KeyEvent.DOM_VK_BACK_SPACE)
) {
if (!e.target.closest("#sitesList")) {
// The user is typing or has not selected an item from the list to remove
return;
}
// The users intention is to delete site data
this.removeSelected();
}
},
onSelect() {
this._updateButtonsState();
},
};