fune/browser/components/preferences/siteDataSettings.js
Fischer.json 90c33909cd Bug 1364852 - Handle error from Services.eTLD.getBaseDomainFromHost, r=Gijs
Quota Manager could return origin which being set to eTLD (see bug 1365892 for more details) or in ipv4/ipv6. That could cause error while calling Services.eTLD.getBaseDomainFromHost. This patch handles these expected errors.

MozReview-Commit-ID: qlfb8nh8cV

--HG--
extra : rebase_source : 4072be4379c2228210eba1d643846cc2175c19bc
2017-05-18 00:29:31 +08:00

301 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;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SiteDataManager",
"resource:///modules/SiteDataManager.jsm");
XPCOMUtils.defineLazyModuleGetter(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("siteDataSettings.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);
}
}
};