forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			335 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			335 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- 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
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| var { AppConstants } = ChromeUtils.importESModule(
 | |
|   "resource://gre/modules/AppConstants.sys.mjs"
 | |
| );
 | |
| 
 | |
| ChromeUtils.defineModuleGetter(
 | |
|   this,
 | |
|   "SiteDataManager",
 | |
|   "resource:///modules/SiteDataManager.jsm"
 | |
| );
 | |
| ChromeUtils.defineESModuleGetters(this, {
 | |
|   DownloadUtils: "resource://gre/modules/DownloadUtils.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();
 | |
|   },
 | |
| };
 | 
