forked from mirrors/gecko-dev
		
	 c276bb9375
			
		
	
	
		c276bb9375
		
	
	
	
	
		
			
			This was done using the following script:
37e3803c7a/processors/chromeutils-import.jsm
MozReview-Commit-ID: 1Nc3XDu0wGl
--HG--
extra : rebase_source : c004a023389f1f6bf3d2f3efe93c13d423b23ccd
		
	
			
		
			
				
	
	
		
			307 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			307 lines
		
	
	
	
		
			10 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/. */
 | |
| const { interfaces: Ci, utils: Cu, results: Cr } = Components;
 | |
| 
 | |
| ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 | |
| ChromeUtils.import("resource://gre/modules/Services.jsm");
 | |
| 
 | |
| ChromeUtils.defineModuleGetter(this, "SiteDataManager",
 | |
|                                "resource:///modules/SiteDataManager.jsm");
 | |
| ChromeUtils.defineModuleGetter(this, "DownloadUtils",
 | |
|                                "resource://gre/modules/DownloadUtils.jsm");
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| let gSiteDataSettings = {
 | |
| 
 | |
|   // Array of metadata of sites. Each array element is object holding:
 | |
|   // - uri: uri of site; instance of nsIURI
 | |
|   // - status: persistent-storage permission status
 | |
|   // - usage: disk usage which site uses
 | |
|   // - userAction: "remove" or "update-permission"; the action user wants to take.
 | |
|   //               If not specified, means no action to take
 | |
|   _sites: null,
 | |
| 
 | |
|   _list: null,
 | |
|   _searchBox: null,
 | |
|   _prefStrBundle: null,
 | |
| 
 | |
|   init() {
 | |
|     function setEventListener(id, eventType, callback) {
 | |
|       document.getElementById(id)
 | |
|               .addEventListener(eventType, callback.bind(gSiteDataSettings));
 | |
|     }
 | |
| 
 | |
|     this._list = document.getElementById("sitesList");
 | |
|     this._searchBox = document.getElementById("searchBox");
 | |
|     this._prefStrBundle = document.getElementById("bundlePreferences");
 | |
|     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");
 | |
|     });
 | |
| 
 | |
|     let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
 | |
|     let settingsDescription = document.getElementById("settingsDescription");
 | |
|     settingsDescription.textContent = this._prefStrBundle.getFormattedString("siteDataSettings2.description", [brandShortName]);
 | |
| 
 | |
|     setEventListener("hostCol", "click", this.onClickTreeCol);
 | |
|     setEventListener("usageCol", "click", this.onClickTreeCol);
 | |
|     setEventListener("statusCol", "click", this.onClickTreeCol);
 | |
|     setEventListener("cancel", "command", this.close);
 | |
|     setEventListener("save", "command", this.saveChanges);
 | |
|     setEventListener("searchBox", "command", this.onCommandSearch);
 | |
|     setEventListener("removeAll", "command", this.onClickRemoveAll);
 | |
|     setEventListener("removeSelected", "command", this.onClickRemoveSelected);
 | |
|   },
 | |
| 
 | |
|   _updateButtonsState() {
 | |
|     let items = this._list.getElementsByTagName("richlistitem");
 | |
|     let removeSelectedBtn = document.getElementById("removeSelected");
 | |
|     let removeAllBtn = document.getElementById("removeAll");
 | |
|     removeSelectedBtn.disabled = items.length == 0;
 | |
|     removeAllBtn.disabled = removeSelectedBtn.disabled;
 | |
| 
 | |
|     let removeAllBtnLabelStringID = "removeAllSiteData.label";
 | |
|     let removeAllBtnAccesskeyStringID = "removeAllSiteData.accesskey";
 | |
|     if (this._searchBox.value) {
 | |
|       removeAllBtnLabelStringID = "removeAllSiteDataShown.label";
 | |
|       removeAllBtnAccesskeyStringID = "removeAllSiteDataShown.accesskey";
 | |
|     }
 | |
|     removeAllBtn.setAttribute("label", this._prefStrBundle.getString(removeAllBtnLabelStringID));
 | |
|     removeAllBtn.setAttribute("accesskey", this._prefStrBundle.getString(removeAllBtnAccesskeyStringID));
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * @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.host.toLowerCase();
 | |
|           let bHost = b.host.toLowerCase();
 | |
|           return aHost.localeCompare(bHost);
 | |
|         };
 | |
|         break;
 | |
| 
 | |
|       case "statusCol":
 | |
|         sortFunc = (a, b) => {
 | |
|           if (a.persisted && !b.persisted) {
 | |
|             return 1;
 | |
|           } else if (!a.persisted && b.persisted) {
 | |
|             return -1;
 | |
|           }
 | |
|           return 0;
 | |
|         };
 | |
|         break;
 | |
| 
 | |
|       case "usageCol":
 | |
|         sortFunc = (a, b) => a.usage - b.usage;
 | |
|         break;
 | |
|     }
 | |
|     if (sortDirection === "descending") {
 | |
|       sites.sort((a, b) => sortFunc(b, a));
 | |
|     } else {
 | |
|       sites.sort(sortFunc);
 | |
|     }
 | |
| 
 | |
|     let cols = this._list.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();
 | |
|     for (let site of sites) {
 | |
|       let host = site.host;
 | |
|       if (keyword && !host.includes(keyword)) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       if (site.userAction === "remove") {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       let size = DownloadUtils.convertByteUnits(site.usage);
 | |
|       let item = document.createElement("richlistitem");
 | |
|       item.setAttribute("host", host);
 | |
|       item.setAttribute("usage", this._prefStrBundle.getFormattedString("siteUsage", size));
 | |
|       if (site.persisted) {
 | |
|         item.setAttribute("status", this._prefStrBundle.getString("persistent"));
 | |
|       }
 | |
|       this._list.appendChild(item);
 | |
|     }
 | |
|     this._updateButtonsState();
 | |
|   },
 | |
| 
 | |
|   _removeSiteItems(items) {
 | |
|     for (let i = items.length - 1; i >= 0; --i) {
 | |
|       let item = items[i];
 | |
|       let host = item.getAttribute("host");
 | |
|       let siteForHost = this._sites.find(site => site.host == host);
 | |
|       if (siteForHost) {
 | |
|         siteForHost.userAction = "remove";
 | |
|       }
 | |
|       item.remove();
 | |
|     }
 | |
|     this._updateButtonsState();
 | |
|   },
 | |
| 
 | |
|   _getBaseDomainFromHost(host) {
 | |
|     let result = host;
 | |
|     try {
 | |
|       result = Services.eTLD.getBaseDomainFromHost(host);
 | |
|     } catch (e) {
 | |
|       if (e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
 | |
|           e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
 | |
|         // For this 2 expected errors, just take the host as the result.
 | |
|         // - NS_ERROR_HOST_IS_IP_ADDRESS: the host is in ipv4/ipv6.
 | |
|         // - NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS: not enough domain part to extract.
 | |
|         result = host;
 | |
|       } else {
 | |
|         throw e;
 | |
|       }
 | |
|     }
 | |
|     return result;
 | |
|   },
 | |
| 
 | |
|   saveChanges() {
 | |
|     let allowed = true;
 | |
| 
 | |
|     // Confirm user really wants to remove site data starts
 | |
|     let removals = new Set();
 | |
|     this._sites = this._sites.filter(site => {
 | |
|       if (site.userAction === "remove") {
 | |
|         removals.add(site.host);
 | |
|         return false;
 | |
|       }
 | |
|       return true;
 | |
|     });
 | |
| 
 | |
|     if (removals.size > 0) {
 | |
|       if (this._sites.length == 0) {
 | |
|         // User selects all sites so equivalent to clearing all data
 | |
|         let flags =
 | |
|           Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
 | |
|           Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1 +
 | |
|           Services.prompt.BUTTON_POS_0_DEFAULT;
 | |
|         let prefStrBundle = document.getElementById("bundlePreferences");
 | |
|         let title = prefStrBundle.getString("clearSiteDataPromptTitle");
 | |
|         let text = prefStrBundle.getString("clearSiteDataPromptText");
 | |
|         let btn0Label = prefStrBundle.getString("clearSiteDataNow");
 | |
|         let result = Services.prompt.confirmEx(window, title, text, flags, btn0Label, null, null, null, {});
 | |
|         allowed = result == 0;
 | |
|         if (allowed) {
 | |
|           SiteDataManager.removeAll();
 | |
|         }
 | |
|       } else {
 | |
|         // User only removes partial sites.
 | |
|         // We will remove cookies based on base domain, say, user selects "news.foo.com" to remove.
 | |
|         // The cookies under "music.foo.com" will be removed together.
 | |
|         // We have to prompt user about this action.
 | |
|         let hostsTable = new Map();
 | |
|         // Group removed sites by base domain
 | |
|         for (let host of removals) {
 | |
|           let baseDomain = this._getBaseDomainFromHost(host);
 | |
|           let hosts = hostsTable.get(baseDomain);
 | |
|           if (!hosts) {
 | |
|             hosts = [];
 | |
|             hostsTable.set(baseDomain, hosts);
 | |
|           }
 | |
|           hosts.push(host);
 | |
|         }
 | |
| 
 | |
|         // Pick out sites with the same base domain as removed sites
 | |
|         for (let site of this._sites) {
 | |
|           let baseDomain = this._getBaseDomainFromHost(site.host);
 | |
|           let hosts = hostsTable.get(baseDomain);
 | |
|           if (hosts) {
 | |
|             hosts.push(site.host);
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         let args = {
 | |
|           hostsTable,
 | |
|           allowed: false
 | |
|         };
 | |
|         let features = "centerscreen,chrome,modal,resizable=no";
 | |
|         window.openDialog("chrome://browser/content/preferences/siteDataRemoveSelected.xul", "", features, args);
 | |
|         allowed = args.allowed;
 | |
|         if (allowed) {
 | |
|           try {
 | |
|             SiteDataManager.remove(removals);
 | |
|           } catch (e) {
 | |
|             // Hit error, maybe remove unknown site.
 | |
|             // Let's print out the error, then proceed to close this settings dialog.
 | |
|             // When we next open again we will once more get sites from the SiteDataManager and refresh the list.
 | |
|             Cu.reportError(e);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     // Confirm user really wants to remove site data ends
 | |
| 
 | |
|     this.close();
 | |
|   },
 | |
| 
 | |
|   close() {
 | |
|     window.close();
 | |
|   },
 | |
| 
 | |
|   onClickTreeCol(e) {
 | |
|     this._sortSites(this._sites, e.target);
 | |
|     this._buildSitesList(this._sites);
 | |
|   },
 | |
| 
 | |
|   onCommandSearch() {
 | |
|     this._buildSitesList(this._sites);
 | |
|   },
 | |
| 
 | |
|   onClickRemoveSelected() {
 | |
|     let selected = this._list.selectedItem;
 | |
|     if (selected) {
 | |
|       this._removeSiteItems([selected]);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onClickRemoveAll() {
 | |
|     let siteItems = this._list.getElementsByTagName("richlistitem");
 | |
|     if (siteItems.length > 0) {
 | |
|       this._removeSiteItems(siteItems);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onKeyPress(e) {
 | |
|     if (e.keyCode == KeyEvent.DOM_VK_ESCAPE) {
 | |
|       this.close();
 | |
|     }
 | |
|   }
 | |
| };
 |