Revision control
Copy as Markdown
Other Tools
/* 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
// Load DownloadUtils module for convertByteUnits
// locally loaded services
var gLocSvc = {};
ChromeUtils.defineModuleGetter(gLocSvc, "FormHistory",
"FormHistory");
XPCOMUtils.defineLazyServiceGetter(gLocSvc, "url",
"@mozilla.org/network/url-parser;1?auth=maybe",
"nsIURLParser");
XPCOMUtils.defineLazyServiceGetter(gLocSvc, "clipboard",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
XPCOMUtils.defineLazyServiceGetter(gLocSvc, "idn",
"@mozilla.org/network/idn-service;1",
"nsIIDNService");
XPCOMUtils.defineLazyServiceGetter(gLocSvc, "appcache",
"@mozilla.org/network/application-cache-service;1",
"nsIApplicationCacheService");
XPCOMUtils.defineLazyServiceGetter(gLocSvc, "domstoremgr",
"@mozilla.org/dom/storagemanager;1",
"nsIDOMStorageManager");
XPCOMUtils.defineLazyServiceGetter(gLocSvc, "idxdbmgr",
"@mozilla.org/dom/indexeddb/manager;1",
"nsIIndexedDatabaseManager");
XPCOMUtils.defineLazyServiceGetter(gLocSvc, "ssm",
"@mozilla.org/scriptsecuritymanager;1",
"nsIScriptSecurityManager");
// From nsContentBlocker.cpp
const NOFOREIGN = 3;
// :::::::::::::::::::: general functions ::::::::::::::::::::
var gDataman = {
bundle: null,
debug: false,
timer: null,
viewToLoad: ["*", "formdata"],
initialize: function dataman_initialize() {
try {
this.debug = Services.prefs.getBoolPref("data_manager.debug");
}
catch (e) {}
this.bundle = document.getElementById("datamanBundle");
Services.obs.addObserver(this, "cookie-changed");
Services.obs.addObserver(this, "perm-changed");
Services.obs.addObserver(this, "passwordmgr-storage-changed");
Services.contentPrefs2.addObserverForName(null, this);
Services.obs.addObserver(this, "satchel-storage-changed");
Services.obs.addObserver(this, "dom-storage-changed");
Services.obs.addObserver(this, "dom-storage2-changed");
this.timer = Cc["@mozilla.org/timer;1"]
.createInstance(Ci.nsITimer);
gTabs.initialize();
gDomains.initialize();
if ("arguments" in window &&
window.arguments.length >= 1 &&
window.arguments[0]) {
this.loadView(window.arguments[0])
}
},
shutdown: function dataman_shutdown() {
Services.obs.removeObserver(this, "cookie-changed");
Services.obs.removeObserver(this, "perm-changed");
Services.obs.removeObserver(this, "passwordmgr-storage-changed");
Services.contentPrefs2.removeObserverForName(null, this);
Services.obs.removeObserver(this, "satchel-storage-changed");
Services.obs.removeObserver(this, "dom-storage-changed");
Services.obs.removeObserver(this, "dom-storage2-changed");
gDomains.shutdown();
},
loadView: function dataman_loadView(aView) {
// Set variable, used in initizalization routine.
// Syntax: <domain>|<pane> (|<pane> is optional)
// Examples: example.com
// example.org|permissions
// example.org:8888|permissions|add|popup
// |cookies
// Allowed pane names:
// cookies, permissions, preferences, passwords, formdata
// Invalid views fall back to the default available ones
// Full host names (even including ports) for domain are allowed
// Empty domain with a pane specified will only list this data type
// Permissions allow specifying "add" and type to prefill the adding field
this.viewToLoad = aView.split('|');
if (gDomains.listLoadCompleted)
gDomains.loadView();
// Else will call this at the end of loading the list.
},
handleKeyPress: function dataman_handleKeyPress(aEvent) {
if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE &&
gTabs.tabbox.selectedPanel &&
gTabs.tabbox.selectedPanel.id == "forgetPanel") {
gForget.handleKeyPress(aEvent);
}
},
debugMsg: function dataman_debugMsg(aLogMessage) {
if (this.debug)
Services.console.logStringMessage(aLogMessage);
},
debugError: function dataman_debugError(aLogMessage) {
if (this.debug)
Cu.reportError(aLogMessage);
},
// :::::::::: data change observers ::::::::::
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsIContentPrefObserver]),
observe: function co_observe(aSubject, aTopic, aData) {
gDataman.debugMsg("Observed: " + aTopic + " - " + aData);
switch (aTopic) {
case "cookie-changed":
gCookies.reactToChange(aSubject, aData);
break;
case "perm-changed":
gPerms.reactToChange(aSubject, aData);
break;
case "passwordmgr-storage-changed":
gPasswords.reactToChange(aSubject, aData);
break;
case "satchel-storage-changed":
gFormdata.reactToChange(aSubject, aData);
break;
case "dom-storage2-changed": // sessionStorage, localStorage
gStorage.reactToChange(aSubject, aData);
break;
default:
gDataman.debugError("Unexpected change topic observed: " + aTopic);
break;
}
},
// Compat with nsITimerCallback so we can be used in a timer.
notify: function(timer) {
gDataman.debugMsg("Timer fired, reloading storage: " + Date.now()/1000);
gStorage.reloadList();
},
onContentPrefSet: function co_onContentPrefSet(aGroup, aName, aValue) {
gDataman.debugMsg("Observed: content pref set");
gPrefs.reactToChange({host: aGroup, name: aName, value: aValue}, "prefSet");
},
onContentPrefRemoved: function co_onContentPrefRemoved(aGroup, aName) {
gDataman.debugMsg("Observed: content pref removed");
gPrefs.reactToChange({host: aGroup, name: aName}, "prefRemoved");
},
// :::::::::: utility functions ::::::::::
getTreeSelections: function dataman_getTreeSelections(aTree) {
let selections = [];
let select = aTree.view.selection;
if (select && aTree.view.rowCount) {
let count = select.getRangeCount();
let min = {};
let max = {};
for (let i = 0; i < count; i++) {
select.getRangeAt(i, min, max);
for (let k = min.value; k <= max.value; k++)
if (k != -1)
selections.push(k);
}
}
return selections;
},
getSelectedIDs: function dataman_getSelectedIDs(aTree, aIDFunction) {
// Get IDs of selected elements for later restoration.
let selectionCache = [];
if (aTree.view.selection.count < 1 || aTree.view.rowCount < 1)
return selectionCache;
// Walk all selected rows and cache their IDs.
let start = {};
let end = {};
let numRanges = aTree.view.selection.getRangeCount();
for (let rg = 0; rg < numRanges; rg++){
aTree.view.selection.getRangeAt(rg, start, end);
for (let row = start.value; row <= end.value; row++)
selectionCache.push(aIDFunction(row));
}
return selectionCache;
},
restoreSelectionFromIDs: function dataman_restoreSelectionFromIDs(aTree, aIDFunction, aCachedIDs) {
// Restore selection from cached IDs (as possible).
if (!aCachedIDs.length)
return;
aTree.view.selection.clearSelection();
// Find out which current rows match a cached selection and add them to the selection.
for (let row = 0; row < aTree.view.rowCount; row++)
if (aCachedIDs.includes(aIDFunction(row)))
aTree.view.selection.toggleSelect(row);
},
}
// :::::::::::::::::::: base object to use as a prototype for all others ::::::::::::::::::::
var gBaseTreeView = {
setTree: function(aTree) {},
getImageSrc: function(aRow, aColumn) {},
getProgressMode: function(aRow, aColumn) {},
getCellValue: function(aRow, aColumn) {},
isSeparator: function(aIndex) { return false; },
isSorted: function() { return false; },
isContainer: function(aIndex) { return false; },
cycleHeader: function(aCol) {},
getRowProperties: function(aRow) { return ""; },
getColumnProperties: function(aColumn) { return ""; },
getCellProperties: function(aRow, aColumn) { return ""; }
};
// :::::::::::::::::::: domain list ::::::::::::::::::::
var gDomains = {
tree: null,
selectfield: null,
searchfield: null,
domains: {},
domainObjects: {},
displayedDomains: [],
selectedDomain: {},
xlcache: {},
ignoreSelect: false,
ignoreUpdate: false,
listLoadCompleted: false,
initialize: function domain_initialize() {
gDataman.debugMsg("Start building domain list: " + Date.now()/1000);
this.tree = document.getElementById("domainTree");
this.tree.view = this;
this.selectfield = document.getElementById("typeSelect");
this.searchfield = document.getElementById("domainSearch");
// global "domain"
Services.contentPrefs2.hasPrefs(null, null, {
handleResult(resultPref) {
gDomains.domainObjects["*"] = {title: "*",
displayTitle: "*",
hasPermissions: true,
hasPreferences: resultPref.value,
hasFormData: true};
},
handleCompletion: () => {
},
});
this.search("");
if (!gDataman.viewToLoad.length)
this.tree.view.selection.select(0);
let loaderInstance;
function nextStep() {
loaderInstance.next();
}
function* loader() {
// Add domains for all cookies we find.
gDataman.debugMsg("Add cookies to domain list: " + Date.now()/1000);
gDomains.ignoreUpdate = true;
gCookies.loadList();
for (let cookie of gCookies.cookies)
gDomains.addDomainOrFlag(cookie.rawHost, "hasCookies");
gDomains.ignoreUpdate = false;
gDomains.search(gDomains.searchfield.value);
yield setTimeout(nextStep, 0);
// Add domains for permissions.
gDataman.debugMsg("Add permissions to domain list: " + Date.now()/1000);
gDomains.ignoreUpdate = true;
let enumerator = Services.perms.enumerator;
while (enumerator.hasMoreElements()) {
let nextPermission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
if (!gDomains.commonScheme(nextPermission.principal.URI.scheme)) {
gDomains.addDomainOrFlag("*", "hasPermissions");
}
else {
gDomains.addDomainOrFlag(nextPermission.principal.URI.host.replace(/^\./, ""), "hasPermissions");
}
}
gDomains.ignoreUpdate = false;
gDomains.search(gDomains.searchfield.value);
yield setTimeout(nextStep, 0);
let domains = [];
Services.contentPrefs2.getDomains(null, {
handleResult(resultPref) {
domains.push(resultPref.domain);
},
handleCompletion: () => {
// Add domains for content prefs.
gDataman.debugMsg("Add content prefs to domain list: " +
Date.now()/1000);
gDomains.ignoreUpdate = true;
for (let domain of domains) {
gDataman.debugMsg("Found pref: " + domain);
let prefHost = gDomains.getDomainFromHostWithCheck(domain);
gDomains.addDomainOrFlag(prefHost, "hasPreferences");
}
gDomains.ignoreUpdate = false;
gDomains.search(gDomains.searchfield.value);
},
});
// Add domains for passwords.
gDataman.debugMsg("Add passwords to domain list: " + Date.now()/1000);
gDomains.ignoreUpdate = true;
gPasswords.loadList();
for (let pSignon of gPasswords.allSignons) {
gDomains.addDomainOrFlag(pSignon.hostname, "hasPasswords");
}
gDomains.ignoreUpdate = false;
gDomains.search(gDomains.searchfield.value);
yield setTimeout(nextStep, 0);
// Add domains for web storages.
gDataman.debugMsg("Add storages to domain list: " + Date.now()/1000);
// Force DOM Storage to write its data to the disk.
Services.obs.notifyObservers(window, "domstorage-flush-timer");
yield setTimeout(nextStep, 0);
gStorage.loadList();
for (let sStorage of gStorage.storages) {
gDomains.addDomainOrFlag(sStorage.rawHost, "hasStorage");
}
gDomains.search(gDomains.searchfield.value);
// As we don't get notified of storage changes properly, reload on timer.
// The repeat time is in milliseconds, we're using 10 min for now.
gDataman.timer.initWithCallback(gDataman, 10 * 60000,
Ci.nsITimer.TYPE_REPEATING_SLACK);
yield setTimeout(nextStep, 0);
gDataman.debugMsg("Domain list built: " + Date.now()/1000);
gDomains.listLoadCompleted = true;
gDomains.loadView();
yield undefined;
}
loaderInstance = loader();
setTimeout(nextStep, 0);
},
shutdown: function domain_shutdown() {
gDataman.timer.cancel();
gTabs.shutdown();
this.tree.view = null;
},
loadView: function domain_loadView() {
// Load the view set in the dataman object.
gDataman.debugMsg("Load View: " + gDataman.viewToLoad.join(", "));
let loaderInstance;
function nextStep() {
loaderInstance.next();
}
function* loader() {
if (gDataman.viewToLoad.length) {
if (gDataman.viewToLoad[0] == "" && gDataman.viewToLoad.length > 1) {
let sType = gDataman.viewToLoad[1].substr(0,1).toUpperCase() +
gDataman.viewToLoad[1].substr(1);
gDataman.debugMsg("Select a specific data type: " + sType);
gDomains.selectfield.value = sType;
gDomains.selectType(sType);
yield setTimeout(nextStep, 0);
if (gDomains.tree.view.rowCount) {
// Select first domain and panel fitting selected type.
gDomains.tree.view.selection.select(0);
gDomains.tree.treeBoxObject.ensureRowIsVisible(0);
yield setTimeout(nextStep, 0);
// This should always exist and be enabled, but play safe.
let loadTabID = gDataman.viewToLoad[1] + "Tab";
if (gTabs[loadTabID] && !gTabs[loadTabID].disabled)
gTabs.tabbox.selectedTab = gTabs[loadTabID];
}
}
else {
gDataman.debugMsg("Domain for view found");
gDomains.selectfield.value = "all";
gDomains.selectType("all");
let host = gDataman.viewToLoad[0];
// Might have a host:port case, fake a scheme when none present.
if (!/:\//.test(host))
host = "foo://" + host;
gDataman.debugMsg("host: " + host);
let viewdomain = "*";
// avoid error message in log for the generic entry
viewdomain = gDomains.getDomainFromHost(host);
gDataman.debugMsg("viewDomain: " + viewdomain);
let selectIdx = 0; // tree index to be selected
for (let i = 0; i < gDomains.displayedDomains.length; i++) {
if (gDomains.displayedDomains[i].title == viewdomain) {
selectIdx = i;
break;
}
}
let permAdd = (gDataman.viewToLoad[1] &&
gDataman.viewToLoad[1] == "permissions" &&
gDataman.viewToLoad[2] &&
gDataman.viewToLoad[2] == "add");
if (permAdd && selectIdx != 0 &&
(!(viewdomain in gDomains.domainObjects) ||
!gDomains.domainObjects[viewdomain].hasPermissions)) {
selectIdx = 0; // Force * domain as we have a perm panel there.
}
if (gDomains.tree.currentIndex != selectIdx) {
gDomains.tree.view.selection.select(selectIdx);
gDomains.tree.treeBoxObject.ensureRowIsVisible(selectIdx);
}
yield setTimeout(nextStep, 0);
if (gDataman.viewToLoad.length > 1) {
gDataman.debugMsg("Pane for view found");
let loadTabID = gDataman.viewToLoad[1] + "Tab";
if (gTabs[loadTabID] && !gTabs[loadTabID].disabled)
gTabs.tabbox.selectedTab = gTabs[loadTabID];
yield setTimeout(nextStep, 0);
if (permAdd) {
gDataman.debugMsg("Adding permission");
if (gPerms.addSelBox.hidden)
gPerms.addButtonClick();
gPerms.addHost.value = gDataman.viewToLoad[0];
if (gDataman.viewToLoad[3])
gPerms.addType.value = gDataman.viewToLoad[3];
gPerms.addCheck();
gPerms.addButton.focus();
}
}
}
}
yield setTimeout(nextStep, 0);
// Send a notification that we have finished.
Services.obs.notifyObservers(window, "dataman-loaded");
yield undefined;
}
loaderInstance = loader();
setTimeout(nextStep, 0);
},
_getObjID: function domain__getObjID(aIdx) {
return gDomains.displayedDomains[aIdx].title;
},
getDomainFromHostWithCheck: function domain_getDomainFromHostWithCheck(aHost) {
// Global content pref changes and others might not have a host.
if (!aHost) {
return '*';
}
let host = gDomains.getDomainFromHost(aHost);
// Host couldn't be found or is an internal page or data.
if (!host ||
host.trim().length == 0 ||
aHost.startsWith("about:") ||
aHost.startsWith("jar:"))
return '*';
return host.trim();
},
getDomainFromHost: function domain_getDomainFromHost(aHostname) {
// Find the base domain name for the given host name.
if (!this.xlcache[aHostname]) {
// aHostname is not always an actual host name, but potentially something
// need to display entries for schemes that are not supported (any more).
// nsIURLParser is a fast way to generically ensure a pure host name.
var hostName;
// Return vars for nsIURLParser must all be objects,
var schemePos = {}, schemeLen = {}, authPos = {}, authLen = {}, pathPos = {},
pathLen = {}, usernamePos = {}, usernameLen = {}, passwordPos = {},
passwordLen = {}, hostnamePos = {}, hostnameLen = {}, port = {};
try {
gLocSvc.url.parseURL(aHostname, -1, schemePos, schemeLen, authPos, authLen,
pathPos, pathLen);
var auth = aHostname.substring(authPos.value, authPos.value + authLen.value);
gLocSvc.url.parseAuthority(auth, authLen.value, usernamePos, usernameLen,
passwordPos, passwordLen, hostnamePos, hostnameLen, port);
hostName = auth.substring(hostnamePos.value, hostnamePos.value + hostnameLen.value);
}
catch (e) {
// IPv6 host names can come in without [] around them and therefore
// cause an error. Those consist of at least two colons and else only
// hexadecimal digits. Fix them by putting [] around them.
if (/^[a-f0-9]*:[a-f0-9]*:[a-f0-9:]*$/.test(aHostname)) {
gDataman.debugMsg("bare IPv6 address found: " + aHostname);
hostName = "[" + aHostname + "]";
}
else {
gDataman.debugError("Error while trying to get hostname from input: " + aHostname);
gDataman.debugError(e);
hostName = aHostname;
}
}
var domain;
try {
domain = Services.eTLD.getBaseDomainFromHost(hostName);
}
catch (e) {
gDataman.debugMsg("Unable to get domain from host name: " + hostName);
domain = hostName;
}
this.xlcache[aHostname] = domain;
gDataman.debugMsg("cached: " + aHostname + " -> " + this.xlcache[aHostname]);
} // end hostname not cached
return this.xlcache[aHostname];
},
// Used for checking if * global data domain should be used.
commonScheme: function domain_commonScheme(aScheme) {
// case intensitive search for domain schemes
return /^(https?|ftp|gopher)/i.test(aScheme);
},
hostMatchesSelected: function domain_hostMatchesSelected(aHostname) {
return this.getDomainFromHost(aHostname) == this.selectedDomain.title;
},
hostMatchesSelectedURI: function domain_hostMatchesSelectedURI(aURI) {
// default to * global data domain.
let mScheme = "*";
// First, try to get the scheme.
try {
mScheme = aURI.scheme;
}
catch (e) {
gDataman.debugError("Invalid permission found: " + aUri);
}
// See if his is a scheme which does not go into the global data domain.
if (!this.commonScheme(mScheme)) {
return ("*") == this.selectedDomain.title;
}
rawHost = aURI.host.replace(/^\./, "");
return this.getDomainFromHost(rawHost) == this.selectedDomain.title;
},
addDomainOrFlag: function domain_addDomainOrFlag(aHostname, aFlag) {
let domain;
// For existing domains, add flags, for others, add them to the object.
if (aHostname == "*")
domain = aHostname;
else
domain = this.getDomainFromHost(aHostname);
if (!this.domainObjects[domain]) {
this.domainObjects[domain] = {title: domain};
if (/xn--/.test(domain))
this.domainObjects[domain].displayTitle = gLocSvc.idn.convertToDisplayIDN(domain, {});
else
this.domainObjects[domain].displayTitle = this.domainObjects[domain].title;
this.domainObjects[domain][aFlag] = true;
gDataman.debugMsg("added domain: " + domain + " (with flag " + aFlag + ")");
if (!this.ignoreUpdate)
this.search(this.searchfield.value);
}
else if (!this.domainObjects[domain][aFlag]) {
this.domainObjects[domain][aFlag] = true;
gDataman.debugMsg("added flag " + aFlag + " to " + domain);
if (domain == this.selectedDomain.title) {
// Just update the tab states.
this.select(true);
}
}
},
removeDomainOrFlag: function domain_removeDomainOrFlag(aDomain, aFlag) {
// Remove a flag from the given domain,
// remove the whole domain if it doesn't have any flags left.
if (!this.domainObjects[aDomain])
return;
gDataman.debugMsg("removed flag " + aFlag + " from " + aDomain);
this.domainObjects[aDomain][aFlag] = false;
if (!this.domainObjects[aDomain].hasCookies &&
!this.domainObjects[aDomain].hasPermissions &&
!this.domainObjects[aDomain].hasPreferences &&
!this.domainObjects[aDomain].hasPasswords &&
!this.domainObjects[aDomain].hasStorage &&
!this.domainObjects[aDomain].hasFormData) {
gDataman.debugMsg("removed domain: " + aDomain);
// Get index in display tree.
let disp_idx = -1;
for (let i = 0; i < this.displayedDomains.length; i++) {
if (this.displayedDomains[i] == this.domainObjects[aDomain]) {
disp_idx = i;
break;
}
}
this.displayedDomains.splice(disp_idx, 1);
this.tree.treeBoxObject.rowCountChanged(disp_idx, -1);
delete this.domainObjects[aDomain];
// Make sure we clear the data pane when selection has been removed.
if (!this.tree.view.selection.count)
this.select();
}
else {
// Just update the tab states.
this.select(true);
}
},
resetFlagToDomains: function domain_resetFlagToDomains(aFlag, aDomainList) {
// Reset a flag to be only set on a specific set of domains,
// purging then-emtpy domain in the process.
// Needed when we need to reload a complete set of items.
gDataman.debugMsg("resetting domains for flag: " + aFlag);
this.ignoreSelect = true;
var selectionCache = gDataman.getSelectedIDs(this.tree, this._getObjID);
this.tree.view.selection.clearSelection();
// First, clear all domains of this flag.
for (let domain in this.domainObjects) {
this.domainObjects[domain][aFlag] = false;
}
// Then, set it again on all domains in the new list.
for (let domain of aDomainList) {
this.addDomainOrFlag(domain, aFlag);
}
// Now, purge all empty domains.
for (let domain in this.domainObjects) {
if (!this.domainObjects[domain].hasCookies &&
!this.domainObjects[domain].hasPermissions &&
!this.domainObjects[domain].hasPreferences &&
!this.domainObjects[domain].hasPasswords &&
!this.domainObjects[domain].hasStorage &&
!this.domainObjects[domain].hasFormData) {
delete this.domainObjects[domain];
}
}
this.search(this.searchfield.value);
this.ignoreSelect = false;
gDataman.restoreSelectionFromIDs(this.tree, this._getObjID, selectionCache);
// Make sure we clear the data pane when selection has been removed.
if (!this.tree.view.selection.count && selectionCache.length)
this.select();
},
select: function domain_select(aNoTabSelect) {
if (this.ignoreSelect) {
if (this.tree.view.selection.count == 1)
this.selectedDomain = this.displayedDomains[this.tree.currentIndex];
return;
}
gDataman.debugMsg("Domain selected: " + Date.now()/1000);
if (!this.tree.view.selection.count) {
gTabs.cookiesTab.disabled = true;
gTabs.permissionsTab.disabled = true;
gTabs.preferencesTab.disabled = true;
gTabs.passwordsTab.disabled = true;
gTabs.storageTab.disabled = true;
gTabs.formdataTab.hidden = true;
gTabs.formdataTab.disabled = true;
gTabs.forgetTab.hidden = true;
gTabs.forgetTab.disabled = true;
gTabs.shutdown();
this.selectedDomain = {title: null};
gDataman.debugMsg("Domain select aborted (no selection)");
return;
}
if (this.tree.view.selection.count > 1) {
gDataman.debugError("Data Manager doesn't support anything but one selected domain");
this.tree.view.selection.clearSelection();
this.selectedDomain = {title: null};
return;
}
this.selectedDomain = this.displayedDomains[this.tree.currentIndex];
// Disable/enable and hide/show the tabs as needed.
gTabs.cookiesTab.disabled = !this.selectedDomain.hasCookies;
gTabs.permissionsTab.disabled = !this.selectedDomain.hasPermissions;
gTabs.preferencesTab.disabled = !this.selectedDomain.hasPreferences;
gTabs.passwordsTab.disabled = !this.selectedDomain.hasPasswords;
gTabs.storageTab.disabled = !this.selectedDomain.hasStorage;
gTabs.formdataTab.hidden = !this.selectedDomain.hasFormData;
gTabs.formdataTab.disabled = !this.selectedDomain.hasFormData;
gTabs.forgetTab.disabled = true;
gTabs.forgetTab.hidden = true;
// Switch to the first non-disabled tab if the one that's showing is
// disabled, otherwise, you can't use the keyboard to switch tabs.
if (gTabs.tabbox.selectedTab.disabled) {
for (let i = 0; i < gTabs.tabbox.tabs.childNodes.length; ++i) {
if (!gTabs.tabbox.tabs.childNodes[i].disabled) {
gTabs.tabbox.selectedIndex = i;
break;
}
}
}
if (!aNoTabSelect)
gTabs.select();
// Ensure the focus stays on our tree.
this.tree.focus();
gDataman.debugMsg("Domain select finished: " + Date.now()/1000);
},
handleKeyPress: function domain_handleKeyPress(aEvent) {
if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
(AppConstants.platform == "macosx" &&
aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE)) {
this.forget();
}
else if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE &&
gTabs.activePanel == "forgetPanel") {
gForget.handleKeyPress(aEvent);
}
},
sort: function domain_sort() {
if (!this.displayedDomains.length)
return;
// compare function for two domain items
let compfunc = function domain_sort_compare(aOne, aTwo) {
// Make sure "*" is always first.
if (aOne.displayTitle == "*")
return -1;
if (aTwo.displayTitle == "*")
return 1;
return aOne.displayTitle.localeCompare(aTwo.displayTitle);
};
// Do the actual sorting of the array.
this.displayedDomains.sort(compfunc);
this.tree.treeBoxObject.invalidate();
},
forget: function domain_forget() {
gTabs.forgetTab.hidden = false;
gTabs.forgetTab.disabled = false;
gTabs.tabbox.selectedTab = gTabs.forgetTab;
},
selectType: function domain_selectType(aType) {
this.search(this.searchfield.value, aType);
},
search: function domain_search(aSearchString, aType) {
this.ignoreSelect = true;
this.tree.treeBoxObject.beginUpdateBatch();
var selectionCache = gDataman.getSelectedIDs(this.tree, this._getObjID);
this.tree.view.selection.clearSelection();
this.displayedDomains = [];
var lcSearch = aSearchString.toLocaleLowerCase();
var sType = aType || this.selectfield.value;
for (let domain in this.domainObjects) {
if (this.domainObjects[domain].displayTitle
.toLocaleLowerCase().includes(lcSearch) &&
(sType == "all" || this.domainObjects[domain]["has" + sType]))
this.displayedDomains.push(this.domainObjects[domain]);
}
this.sort();
gDataman.restoreSelectionFromIDs(this.tree, this._getObjID, selectionCache);
this.tree.treeBoxObject.endUpdateBatch();
this.ignoreSelect = false;
// Make sure we clear the data pane when selection has been removed.
if (!this.tree.view.selection.count && selectionCache.length)
this.select();
},
focusSearch: function domain_focusSearch() {
this.searchfield.focus();
},
updateContext: function domain_updateContext() {
let forgetCtx = document.getElementById("domain-context-forget");
forgetCtx.disabled = !this.selectedDomain.title;
forgetCtx.label = this.selectedDomain.title == "*" ?
forgetCtx.getAttribute("label_global") :
forgetCtx.getAttribute("label_domain");
forgetCtx.accesskey = this.selectedDomain.title == "*" ?
forgetCtx.getAttribute("accesskey_global") :
forgetCtx.getAttribute("accesskey_domain");
},
// nsITreeView
__proto__: gBaseTreeView,
get rowCount() {
return this.displayedDomains.length;
},
getCellText: function(aRow, aColumn) {
switch (aColumn.id) {
case "domainCol":
return this.displayedDomains[aRow].displayTitle;
}
},
};
// :::::::::::::::::::: tab management ::::::::::::::::::::
var gTabs = {
tabbox: null,
tabs: null,
cookiesTab: null,
permissionsTab: null,
preferencesTab: null,
passwordsTab: null,
storageTab: null,
formdataTab: null,
forgetTab: null,
panels: {},
activePanel: null,
initialize: function tabs_initialize() {
gDataman.debugMsg("Initializing tabs");
this.tabbox = document.getElementById("tabbox");
this.cookiesTab = document.getElementById("cookiesTab");
this.permissionsTab = document.getElementById("permissionsTab");
this.preferencesTab = document.getElementById("preferencesTab");
this.passwordsTab = document.getElementById("passwordsTab");
this.storageTab = document.getElementById("storageTab");
this.formdataTab = document.getElementById("formdataTab");
this.forgetTab = document.getElementById("forgetTab");
this.panels = {
cookiesPanel: gCookies,
permissionsPanel: gPerms,
preferencesPanel: gPrefs,
passwordsPanel: gPasswords,
storagePanel: gStorage,
formdataPanel: gFormdata,
forgetPanel: gForget
};
},
shutdown: function tabs_shutdown() {
gDataman.debugMsg("Shutting down tabs");
if (this.activePanel) {
this.panels[this.activePanel].shutdown();
this.activePanel = null;
}
},
select: function tabs_select() {
gDataman.debugMsg("Selecting tab");
if (this.activePanel) {
this.panels[this.activePanel].shutdown();
this.activePanel = null;
}
if (!this.tabbox || this.tabbox.selectedPanel.disabled)
return;
this.activePanel = this.tabbox.selectedPanel.id;
this.panels[this.activePanel].initialize();
},
selectAll: function tabs_selectAll() {
try {
this.panels[this.activePanel].selectAll();
}
catch (e) {
gDataman.debugError("SelectAll didn't work for " + this.activePanel + ": " + e);
}
},
focusSearch: function tabs_focusSearch() {
try {
this.panels[this.activePanel].focusSearch();
}
catch (e) {
gDataman.debugError("focusSearch didn't work for " + this.activePanel + ": " + e);
}
},
};
// :::::::::::::::::::: cookies panel ::::::::::::::::::::
var gCookies = {
tree: null,
cookieInfoName: null,
cookieInfoValue: null,
cookieInfoHostLabel: null,
cookieInfoHost: null,
cookieInfoPath: null,
cookieInfoSendType: null,
cookieInfoExpires: null,
removeButton: null,
blockOnRemove: null,
cookies: [],
displayedCookies: [],
initialize: function cookies_initialize() {
gDataman.debugMsg("Initializing cookies panel");
this.tree = document.getElementById("cookiesTree");
this.tree.view = this;
this.cookieInfoName = document.getElementById("cookieInfoName");
this.cookieInfoValue = document.getElementById("cookieInfoValue");
this.cookieInfoHostLabel = document.getElementById("cookieInfoHostLabel");
this.cookieInfoHost = document.getElementById("cookieInfoHost");
this.cookieInfoPath = document.getElementById("cookieInfoPath");
this.cookieInfoSendType = document.getElementById("cookieInfoSendType");
this.cookieInfoExpires = document.getElementById("cookieInfoExpires");
this.removeButton = document.getElementById("cookieRemove");
this.blockOnRemove = document.getElementById("cookieBlockOnRemove");
// this.loadList() is being called in gDomains.initialize() already
this.tree.treeBoxObject.beginUpdateBatch();
this.displayedCookies = this.cookies.filter(
function (aCookie) {
return gDomains.hostMatchesSelected(aCookie.rawHost);
});
this.sort(null, false, false);
this.tree.treeBoxObject.endUpdateBatch();
},
shutdown: function cookies_shutdown() {
gDataman.debugMsg("Shutting down cookies panel");
this.tree.view.selection.clearSelection();
this.tree.view = null;
this.displayedCookies = [];
},
loadList: function cookies_loadList() {
this.cookies = [];
let enumerator = Services.cookies.enumerator;
while (enumerator.hasMoreElements()) {
let nextCookie = enumerator.getNext();
if (!nextCookie) break;
nextCookie = nextCookie.QueryInterface(Ci.nsICookie2);
this.cookies.push(this._makeCookieObject(nextCookie));
}
},
_makeCookieObject: function cookies__makeCookieObject(aCookie) {
return {host: aCookie.host,
name: aCookie.name,
path: aCookie.path,
originAttributes: aCookie.originAttributes,
value: aCookie.value,
isDomain: aCookie.isDomain,
rawHost: aCookie.rawHost,
displayHost: gLocSvc.idn.convertToDisplayIDN(aCookie.rawHost, {}),
isSecure: aCookie.isSecure,
isSession: aCookie.isSession,
isHttpOnly: aCookie.isHttpOnly,
expires: this._getExpiresString(aCookie.expires),
expiresSortValue: aCookie.expires};
},
_getObjID: function cookies__getObjID(aIdx) {
var curCookie = gCookies.displayedCookies[aIdx];
return curCookie.host + "|" + curCookie.path + "|" + curCookie.name;
},
_getExpiresString: function cookies__getExpiresString(aExpires) {
if (aExpires) {
let date = new Date(1000 * aExpires);
// If a server manages to set a really long-lived cookie, the dateservice
// can't cope with it properly, so we'll just return a blank string.
let expiry = "";
try {
const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
dateStyle: "full", timeStyle: "long" });
expiry = dateTimeFormatter.format(date);
}
catch (e) {}
return expiry;
}
return gDataman.bundle.getString("cookies.expireAtEndOfSession");
},
select: function cookies_select() {
var selections = gDataman.getTreeSelections(this.tree);
this.removeButton.disabled = !selections.length;
if (!selections.length) {
this._clearCookieInfo();
return true;
}
if (selections.length > 1) {
this._clearCookieInfo();
return true;
}
// At this point, we have a single cookie selected.
var showCookie = this.displayedCookies[selections[0]];
this.cookieInfoName.value = showCookie.name;
this.cookieInfoValue.value = showCookie.value;
this.cookieInfoHostLabel.value = showCookie.isDomain ?
this.cookieInfoHostLabel.getAttribute("value_domain") :
this.cookieInfoHostLabel.getAttribute("value_host");
this.cookieInfoHost.value = showCookie.host;
this.cookieInfoPath.value = showCookie.path;
var typestringID = "cookies." +
(showCookie.isSecure ? "secureOnly" : "anyConnection") +
(showCookie.isHttpOnly ? ".httponly" : ".all");
this.cookieInfoSendType.value = gDataman.bundle.getString(typestringID);
this.cookieInfoExpires.value = showCookie.expires;
return true;
},
selectAll: function cookies_selectAll() {
this.tree.view.selection.selectAll();
},
_clearCookieInfo: function cookies__clearCookieInfo() {
var fields = ["cookieInfoName", "cookieInfoValue", "cookieInfoHost",
"cookieInfoPath", "cookieInfoSendType", "cookieInfoExpires"];
for (let field of fields) {
this[field].value = "";
}
this.cookieInfoHostLabel.value = this.cookieInfoHostLabel.getAttribute("value_host");
},
handleKeyPress: function cookies_handleKeyPress(aEvent) {
if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
(AppConstants.platform == "macosx" &&
aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE)) {
this.delete();
}
},
sort: function cookies_sort(aColumn, aUpdateSelection, aInvertDirection) {
// Make sure we have a valid column.
let column = aColumn;
if (!column) {
let sortedCol = this.tree.columns.getSortedColumn();
if (sortedCol)
column = sortedCol.element;
else
column = document.getElementById("cookieHostCol");
}
else if (column.localName == "treecols" || column.localName == "splitter")
return;
if (!column || column.localName != "treecol") {
Cu.reportError("No column found to sort cookies by");
return;
}
let dirAscending = column.getAttribute("sortDirection") !=
(aInvertDirection ? "ascending" : "descending");
let dirFactor = dirAscending ? 1 : -1;
// Clear attributes on all columns, we're setting them again after sorting.
for (let node = column.parentNode.firstChild; node; node = node.nextSibling) {
node.removeAttribute("sortActive");
node.removeAttribute("sortDirection");
}
// compare function for two formdata items
let compfunc = function formdata_sort_compare(aOne, aTwo) {
switch (column.id) {
case "cookieHostCol":
return dirFactor * aOne.displayHost.localeCompare(aTwo.displayHost);
case "cookieNameCol":
return dirFactor * aOne.name.localeCompare(aTwo.name);
case "cookieExpiresCol":
return dirFactor * (aOne.expiresSortValue - aTwo.expiresSortValue);
}
return 0;
};
if (aUpdateSelection) {
var selectionCache = gDataman.getSelectedIDs(this.tree, this._getObjID);
}
this.tree.view.selection.clearSelection();
// Do the actual sorting of the array.
this.displayedCookies.sort(compfunc);
this.tree.treeBoxObject.invalidate();
if (aUpdateSelection) {
gDataman.restoreSelectionFromIDs(this.tree, this._getObjID, selectionCache);
}
// Set attributes to the sorting we did.
column.setAttribute("sortActive", "true");
column.setAttribute("sortDirection", dirAscending ? "ascending" : "descending");
},
delete: function cookies_delete() {
var selections = gDataman.getTreeSelections(this.tree);
if (selections.length > 1) {
let title = gDataman.bundle.getString("cookies.deleteSelectedTitle");
let msg = gDataman.bundle.getString("cookies.deleteSelected");
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_1_DEFAULT)
let yes = gDataman.bundle.getString("cookies.deleteSelectedYes");
if (Services.prompt.confirmEx(window, title, msg, flags, yes, null, null,
null, {value: 0}) == 1) // 1=="Cancel" button
return;
}
this.tree.view.selection.clearSelection();
// Loop backwards so later indexes in the list don't change.
for (let i = selections.length - 1; i >= 0; i--) {
let delCookie = this.displayedCookies[selections[i]];
this.cookies.splice(this.cookies.indexOf(this.displayedCookies[selections[i]]), 1);
this.displayedCookies.splice(selections[i], 1);
this.tree.treeBoxObject.rowCountChanged(selections[i], -1);
Services.cookies.remove(delCookie.host, delCookie.name, delCookie.path,
this.blockOnRemove.checked, delCookie.originAttributes);
}
if (!this.displayedCookies.length)
gDomains.removeDomainOrFlag(gDomains.selectedDomain.title, "hasCookies");
// Select the entry after the first deleted one or the last of all entries.
if (selections.length && this.displayedCookies.length)
this.tree.view.selection.toggleSelect(selections[0] < this.displayedCookies.length ?
selections[0] :
this.displayedCookies.length - 1);
},
updateContext: function cookies_updateContext() {
document.getElementById("cookies-context-remove").disabled =
this.removeButton.disabled;
document.getElementById("cookies-context-selectall").disabled =
this.tree.view.selection.count >= this.tree.view.rowCount;
},
reactToChange: function cookies_reactToChange(aSubject, aData) {
// aData: added, changed, deleted, batch-deleted, cleared, reload
if (aData == "batch-deleted" || aData == "cleared" || aData == "reload") {
// Go for re-parsing the whole thing, as cleared and reload need that anyhow
// (batch-deleted has an nsIArray of cookies, we could in theory do better there).
var selectionCache = [];
if (this.displayedCookies.length) {
selectionCache = gDataman.getSelectedIDs(this.tree, this._getObjID);
this.displayedCookies = [];
}
this.loadList();
var domainList = [];
for (let cookie of this.cookies) {
let domain = gDomains.getDomainFromHost(cookie.rawHost);
if (!domainList.includes(domain))
domainList.push(domain);
}
gDomains.resetFlagToDomains("hasCookies", domainList);
// Restore the local panel display if needed.
if (gTabs.activePanel == "cookiesPanel" &&
gDomains.selectedDomain.hasCookies) {
this.tree.treeBoxObject.beginUpdateBatch();
this.displayedCookies = this.cookies.filter(
function (aCookie) {
return gDomains.hostMatchesSelected(aCookie.rawHost);
});
this.sort(null, false, false);
gDataman.restoreSelectionFromIDs(this.tree, this._getObjID, selectionCache);
this.tree.treeBoxObject.endUpdateBatch();
}
return;
}
// Usual notifications for added, changed, deleted - do "surgical" updates.
aSubject.QueryInterface(Ci.nsICookie2);
let domain = gDomains.getDomainFromHost(aSubject.rawHost);
// Does change affect possibly loaded Cookies pane?
let affectsLoaded = this.displayedCookies.length &&
gDomains.hostMatchesSelected(aSubject.rawHost);
if (aData == "added") {
this.cookies.push(this._makeCookieObject(aSubject));
if (affectsLoaded) {
this.displayedCookies.push(this.cookies[this.cookies.length - 1]);
this.tree.treeBoxObject.rowCountChanged(this.cookies.length - 1, 1);
this.sort(null, true, false);
}
else {
gDomains.addDomainOrFlag(aSubject.rawHost, "hasCookies");
}
}
else {
let idx = -1, disp_idx = -1, domainCookies = 0;
if (affectsLoaded) {
for (let i = 0; i < this.displayedCookies.length; i++) {
let cookie = this.displayedCookies[i];
if (cookie.host == aSubject.host && cookie.name == aSubject.name &&
cookie.path == aSubject.path) {
idx = this.cookies.indexOf(this.displayedCookies[i]);
disp_idx = i;
break;
}
}
if (aData == "deleted")
domainCookies = this.displayedCookies.length;
}
else {
for (let i = 0; i < this.cookies.length; i++) {
let cookie = this.cookies[i];
if (cookie.host == aSubject.host && cookie.name == aSubject.name &&
cookie.path == aSubject.path) {
idx = i;
if (aData != "deleted")
break;
}
if (aData == "deleted" &&
gDomains.getDomainFromHost(cookie.rawHost) == domain)
domainCookies++;
}
}
if (idx >= 0) {
if (aData == "changed") {
this.cookies[idx] = this._makeCookieObject(aSubject);
if (affectsLoaded)
this.tree.treeBoxObject.invalidateRow(disp_idx);
}
else if (aData == "deleted") {
this.cookies.splice(idx, 1);
if (affectsLoaded) {
this.displayedCookies.splice(disp_idx, 1);
this.tree.treeBoxObject.rowCountChanged(disp_idx, -1);
}
if (domainCookies == 1)
gDomains.removeDomainOrFlag(domain, "hasCookies");
}
}
}
},
forget: function cookies_forget() {
// Loop backwards so later indexes in the list don't change.
for (let i = this.cookies.length - 1; i >= 0; i--) {
if (gDomains.hostMatchesSelected(this.cookies[i].rawHost)) {
// Remove from internal list needs to be before actually deleting.
let delCookie = this.cookies[i];
this.cookies.splice(i, 1);
Services.cookies.remove(delCookie.host, delCookie.name,
delCookie.path, false);
}
}
gDomains.removeDomainOrFlag(gDomains.selectedDomain.title, "hasCookies");
},
// nsITreeView
__proto__: gBaseTreeView,
get rowCount() {
return this.displayedCookies.length;
},
getCellText: function(aRow, aColumn) {
let cookie = this.displayedCookies[aRow];
switch (aColumn.id) {
case "cookieHostCol":
return cookie.displayHost;
case "cookieNameCol":
return cookie.name;
case "cookieExpiresCol":
return cookie.expires;
}
},
};
// :::::::::::::::::::: permissions panel ::::::::::::::::::::
var gPerms = {
list: null,
listPermission: [],
initialize: function permissions_initialize() {
gDataman.debugMsg("Initializing permissions panel");
this.list = document.getElementById("permList");
this.addSelBox = document.getElementById("permSelectionBox");
this.addHost = document.getElementById("permHost");
this.addType = document.getElementById("permType");
this.addButton = document.getElementById("permAddButton");
let enumerator = Services.perms.enumerator;
while (enumerator.hasMoreElements()) {
let nextPermission = enumerator.getNext();
nextPermission = nextPermission.QueryInterface(Ci.nsIPermission);
if (gDomains.hostMatchesSelectedURI(nextPermission.principal.URI)) {
let permElem = document.createElement("richlistitem");
permElem.setAttribute("type", nextPermission.type);
permElem.setAttribute("host", nextPermission.principal.origin);
permElem.setAttribute("displayHost", nextPermission.principal.origin);
permElem.setAttribute("capability", nextPermission.capability);
permElem.setAttribute("class", "permission");
gDataman.debugMsg("Adding Origin: " + nextPermission.principal.origin);
this.list.appendChild(permElem);
this.listPermission.push({id: nextPermission.length,
origin: nextPermission.principal.origin,
principal: nextPermission.principal,
type: nextPermission.type});
}
}
this.list.disabled = !this.list.itemCount;
this.addButton.disabled = false;
},
shutdown: function permissions_shutdown() {
gDataman.debugMsg("Shutting down permissions panel");
// XXX: Here we could detect if we still hold any non-default settings and
// trigger the removeDomainOrFlag if not.
while (this.list.hasChildNodes())
this.list.lastChild.remove();
this.addSelBox.hidden = true;
this.listPermission.length = 0;
},
// Find an item in the permissionsList by origin and type.
getPrincipalListItem: function permissions_getPrincipalListItem(aOrigin, aType) {
gDataman.debugMsg("Getting list item: " + aOrigin + " " + aType);
for (let elem of this.listPermission) {
gDataman.debugMsg("elem: " + elem.type);
// check if this is the one
if (elem.type == aType &&
elem.origin == aOrigin) {
gDataman.debugMsg("Found Element " + elem.origin);
return elem;
}
}
return null;
},
// Directly remove a permission.
// This function is called when the user checks the 'Use Default' button on the permissions panel.
// The item will be removed and the default permissions for the origin will be in place afterwards.
// This function will only handle the deletion. The remove will trigger an Observer message.
// Because the permission might be removed outside of this panel the code in there needs to clean
// up the panel and lists.
removeItem: function permissions_removeItem(aOrigin, aType) {
gDataman.debugMsg("Removing an Item: " + aOrigin + " " + aType);
let permElem = this.getPrincipalListItem(aOrigin, aType);
// This happens when we add a new permission.
if (permElem == null) {
gDataman.debugMsg("Unable to find an Item: " + aOrigin + " " + aType);
return;
}
gDataman.debugMsg("Found Element " + permElem.origin);
// It might be a new element. In this case the principal is null and we do not need to do
// anything here. We can not remove the list entry because it might be a new permission the
// user wants to change.
if (permElem.principal != null) {
// Delete the permission. We will deactivate the list item in the subsequent observer message.
try {
gDataman.debugMsg("Removing permission");
Services.perms.removeFromPrincipal(permElem.principal, permElem.type);
}
catch (e) {
gDataman.debugError("Permission could not be removed " +
permElem.principal.origin + " " +
permElem.principal.type
);
}
}
},
// Directly change a permission.
// This function is called when the user changes the value of a permission on the permissions panel.
// This function will only handle the update. The update will trigger an Observer message.
// Because the permission might be changed outside of this panel the code in there needs to handle
// further generic changes.
updateItem: function permissions_updateItem(aOrigin, aType, aValue) {
gDataman.debugMsg("Updating an Item: " + aOrigin + " " + aType + " " + aValue);
let permElem = this.getPrincipalListItem(aOrigin, aType);
if (permElem == null) {
gDataman.debugMsg("Unable to find an Item: " + aOrigin + " " + aType);
return;
}
// If this is a completely new permission we do not have a principal yet.
// This happens when we add a new item. We need to create a new permission
// from scratch.
// Maybe use
// principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {})
// but this might be undocumented?
if (permElem.principal == null)
{
// This can currently fail for some schemes like 'file://'.
// Maybe fix it later if needed.
try {
let uri = Services.io.newURI(new URL(aOrigin));
Services.perms.add(uri, aType, aValue);
}
catch (e) {
gDataman.debugError("New Permission could not be added " +
permElem.origin + " " +
permElem.type
);
}
} else {
Services.perms.addFromPrincipal(permElem.principal, permElem.type, aValue);
}
},
// Most functions of permissions are in the XBL items!
addButtonClick: function permissions_addButtonClick() {
gDataman.debugMsg("Add permissions button clicked!");
if (this.addSelBox.hidden) {
// Show addition box, disable button.
this.addButton.disabled = true;
this.addType.removeAllItems(); // Make sure list is clean.
let permTypes = ["allowXULXBL", "cookie", "geo", "image",
"install", "login-saving", "object", "offline-app",
"persistent-storage", "popup", "script", "shortcuts",
"stylesheet", "trackingprotection"];
// Look for a translation.
for (let permType of permTypes) {
let typeDesc = permType;
try {
typeDesc = SitePermissions.getPermissionLabel(permType);
}
catch (e) {
}
let menuitem = this.addType.appendItem(typeDesc, permType);
}
this.addType.setAttribute("label",
gDataman.bundle.getString("perm.type.default"));
this.addHost.value =
this.addSelBox.hidden = false;
}
else {
// Let the backend do the validation of the input field.
let nOrigin = "";
try {
nOrigin = new URL(this.addHost.value).origin;
} catch (e) {
// Show an error if URL is invalid.
window.alert(gDataman.bundle.getString("perm.validation.invalidurl"));
return;
}
// Url could be validated but User did probably enter half valid nonsense
// because the origin is undefined.
if ((nOrigin == null) || (nOrigin == "")) {
window.alert(gDataman.bundle.getString("perm.validation.invalidurl"));
return;
}
gDataman.debugMsg("New origin: " + nOrigin);
// Add entry to list, hide addition box.
let permElem = document.createElement("richlistitem");
permElem.setAttribute("type", this.addType.value);
permElem.setAttribute("host", nOrigin);
permElem.setAttribute("displayHost", nOrigin);
permElem.setAttribute("capability",
SitePermissions.getDefault(this.addType.value));
permElem.setAttribute("class", "permission");
this.list.appendChild(permElem);
this.list.disabled = false;
permElem.useDefault(true);
// Add a new entry to the permissions list.
// We do not have a principal yet so we use only the origin as identification.
this.listPermission.push({id: this.listPermission.length + 1,
origin: nOrigin,
principal: null,
type: this.addType.value});
this.addSelBox.hidden = true;
this.addType.removeAllItems();
}
},
addCheck: function permissions_addCheck() {
// Only enable button if both fields have (reasonable) values.
this.addButton.disabled = !(this.addType.value &&
gDomains.getDomainFromHost(this.addHost.value));
},
reactToChange: function permissions_reactToChange(aSubject, aData) {
// aData: added, changed, deleted, cleared
// aSubject: the subject which is the permission to be changed
if (aData == "cleared") {
gDataman.debugMsg("something has been cleared but why in permission?");
gDomains.resetFlagToDomains("hasPermissions", domainList);
return;
}
gDataman.debugMsg("react to change: " + aSubject.principal.origin + " " + aData);
aSubject.QueryInterface(Ci.nsIPermission);
let rawHost;
let domain;
if (!gDomains.commonScheme(aSubject.principal.URI.scheme)) {
rawHost = "*";
domain = "*";
}
else {
rawHost = aSubject.principal.URI.host.replace(/^\./, "");
domain = gDomains.getDomainFromHost(rawHost);
}
// Does change affect possibly loaded Preferences pane?
let affectsLoaded = this.list && this.list.childElementCount &&
gDomains.hostMatchesSelectedURI(aSubject.principal.URI);
let permElem = null;
if (affectsLoaded) {
for (let lChild of this.list.children) {
gDataman.debugMsg("checking type: " + lChild.getAttribute("class") + " " +
lChild.getAttribute("type") + " " + aSubject.type);
// Check type and host (origin) first.
if (lChild.getAttribute("type") == aSubject.type &&
lChild.getAttribute("host") == aSubject.principal.origin)
permElem = lChild;
}
}
if (aData == "deleted") {
if (affectsLoaded) {
permElem.useDefault(true, true);
}
else {
// Only remove if domain is not shown, note that this may leave an empty domain.
let haveDomainPerms = false;
let enumerator = Services.perms.enumerator;
while (enumerator.hasMoreElements()) {
let nextPermission = enumerator.getNext();
nextPermission = nextPermission.QueryInterface(Ci.nsIPermission);
let dDomain;
if (!gDomains.commonScheme(nextPermission.principal.URI.scheme)) {
dDomain = "*";
}
else {
dDomain = gDomains.getDomainFromHost(nextPermission.principal.URI.host.replace(/^\./, ""));
}
if (domain == dDomain) {
haveDomainPerms = true;
break;
}
}
if (!haveDomainPerms)
gDomains.removeDomainOrFlag(domain, "hasPermissions");
}
}
else if (aData == "changed" && affectsLoaded) {
permElem.setCapability(aSubject.capability, true);
}
else if (aData == "added") {
if (affectsLoaded) {
if (permElem) {
// Check if them permission list contains the principal.
// If not adding it to the permissions list.
// This might be the case for newly created items.
let permElem2 = this.getPrincipalListItem(aSubject.principal.origin, aSubject.type);
if (permElem2 != null &&
permElem2.principal == null) {
permElem2.principal = aSubject.principal;
}
permElem.useDefault(false, true);
permElem.setCapability(aSubject.capability, true);
}
else {
gDataman.debugMsg("Adding completely new item: " + aSubject.principal.origin + " " + aSubject.type);
permElem = document.createElement("richlistitem");
permElem.setAttribute("type", aSubject.type);
permElem.setAttribute("host", aSubject.principal.origin);
permElem.setAttribute("displayHost", aSubject.principal.origin);
permElem.setAttribute("capability", aSubject.capability);
permElem.setAttribute("class", "permission");
permElem.setAttribute("orient", "vertical");
this.list.appendChild(permElem);
// add an entry to the permissions list
this.listPermission.push({id: this.listPermission.length + 1,
origin: aSubject.principal.origin,
principal: aSubject.principal,
type: aSubject.type});
}
}
gDomains.addDomainOrFlag(rawHost, "hasPermissions");
}
this.list.disabled = !this.list.itemCount;
},
// This function is a called when you check that all permissions for the given domain should be
// deleted (forget).
forget: function permissions_forget() {
let delPerms = [];
let enumerator = Services.perms.enumerator;
while (enumerator.hasMoreElements()) {
let nextPermission = enumerator.getNext();
nextPermission = nextPermission.QueryInterface(Ci.nsIPermission);
if (gDomains.hostMatchesSelectedURI(nextPermission.principal.URI)) {
delPerms.push({principal: nextPermission.principal, type: nextPermission.type});
}
}
// Loop backwards so later indexes in the list don't change.
for (let i = delPerms.length - 1; i >= 0; i--) {
Services.perms.removeFromPrincipal(delPerms[i].principal, delPerms[i].type);
}
gDomains.removeDomainOrFlag(gDomains.selectedDomain.title, "hasPermissions");
},
};
// :::::::::::::::::::: content prefs panel ::::::::::::::::::::
var gPrefs = {
tree: null,
removeButton: null,
prefs: [],
initialize: function prefs_initialize() {
gDataman.debugMsg("Initializing prefs panel");
this.tree = document.getElementById("prefsTree");
this.tree.view = this;
this.removeButton = document.getElementById("prefsRemove");
// Get all groups (hosts) that match the domain.
let domain = gDomains.selectedDomain.title;
if (domain == "*") {
domain = null;
}
let prefs = [];
Services.contentPrefs2.getBySubdomain(domain, null, {
handleResult(resultPref) {
prefs.push(resultPref);
},
handleCompletion: () => {
gPrefs.tree.treeBoxObject.beginUpdateBatch();
gPrefs.prefs = [];
for (let pref of prefs) {
if (!domain) {
gPrefs.prefs.push({host: null, displayHost: "", name: pref.name,
value: pref.value});
}
else {
let display = gLocSvc.idn.convertToDisplayIDN(pref.domain, {});
gPrefs.prefs.push({host: pref.domain, displayHost: display,
name: pref.name, value: pref.value});
}
}
gPrefs.sort(null, false, false);
gPrefs.tree.treeBoxObject.endUpdateBatch();
},
});
},
shutdown: function prefs_shutdown() {
gDataman.debugMsg("Shutting down prefs panel");
this.tree.view.selection.clearSelection();
this.tree.view = null;
this.prefs = [];
},
_getObjID: function prefs__getObjID(aIdx) {
var curPref = gPrefs.prefs[aIdx];
return curPref.host + "|" + curPref.name;
},
select: function prefs_select() {
var selections = gDataman.getTreeSelections(this.tree);
this.removeButton.disabled = !selections.length;
return true;
},
selectAll: function prefs_selectAll() {
this.tree.view.selection.selectAll();
},
handleKeyPress: function prefs_handleKeyPress(aEvent) {
if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
(AppConstants.platform == "macosx" &&
aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE)) {
this.delete();
}
},
sort: function prefs_sort(aColumn, aUpdateSelection, aInvertDirection) {
// Make sure we have a valid column.
let column = aColumn;
if (!column) {
let sortedCol = this.tree.columns.getSortedColumn();
if (sortedCol)
column = sortedCol.element;
else
column = document.getElementById("prefsHostCol");
}
else if (column.localName == "treecols" || column.localName == "splitter")
return;
if (!column || column.localName != "treecol") {
Cu.reportError("No column found to sort form data by");
return;
}
let dirAscending = column.getAttribute("sortDirection") !=
(aInvertDirection ? "ascending" : "descending");
let dirFactor = dirAscending ? 1 : -1;
// Clear attributes on all columns, we're setting them again after sorting.
for (let node = column.parentNode.firstChild; node; node = node.nextSibling) {
node.removeAttribute("sortActive");
node.removeAttribute("sortDirection");
}
// compare function for two content prefs
let compfunc = function prefs_sort_compare(aOne, aTwo) {
switch (column.id) {
case "prefsHostCol":
return dirFactor * aOne.displayHost.localeCompare(aTwo.displayHost);
case "prefsNameCol":
return dirFactor * aOne.name.localeCompare(aTwo.name);
case "prefsValueCol":
return dirFactor * aOne.value.toString().localeCompare(aTwo.value);
}
return 0;
};
if (aUpdateSelection) {
var selectionCache = gDataman.getSelectedIDs(this.tree, this._getObjID);
}
this.tree.view.selection.clearSelection();
// Do the actual sorting of the array.
this.prefs.sort(compfunc);
this.tree.treeBoxObject.invalidate();
if (aUpdateSelection) {
gDataman.restoreSelectionFromIDs(this.tree, this._getObjID, selectionCache);
}
// Set attributes to the sorting we did.
column.setAttribute("sortActive", "true");
column.setAttribute("sortDirection", dirAscending ? "ascending" : "descending");
},
delete: function prefs_delete() {
var selections = gDataman.getTreeSelections(this.tree);
if (selections.length > 1) {
let title = gDataman.bundle.getString("prefs.deleteSelectedTitle");
let msg = gDataman.bundle.getString("prefs.deleteSelected");
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_1_DEFAULT)
let yes = gDataman.bundle.getString("prefs.deleteSelectedYes");
if (Services.prompt.confirmEx(window, title, msg, flags, yes, null, null,
null, {value: 0}) == 1) // 1=="Cancel" button
return;
}
this.tree.view.selection.clearSelection();
// Loop backwards so later indexes in the list don't change.
for (let i = selections.length - 1; i >= 0; i--) {
let delPref = this.prefs[selections[i]];
this.prefs.splice(selections[i], 1);
this.tree.treeBoxObject.rowCountChanged(selections[i], -1);
Services.contentPrefs2.removeByDomainAndName(delPref.host, delPref.name, null);
}
if (!this.prefs.length)
gDomains.removeDomainOrFlag(gDomains.selectedDomain.title, "hasPreferences");
// Select the entry after the first deleted one or the last of all entries.
if (selections.length && this.prefs.length)
this.tree.view.selection.toggleSelect(selections[0] < this.prefs.length ?
selections[0] :
this.prefs.length - 1);
},
updateContext: function prefs_updateContext() {
document.getElementById("prefs-context-remove").disabled =
this.removeButton.disabled;
document.getElementById("prefs-context-selectall").disabled =
this.tree.view.selection.count >= this.tree.view.rowCount;
},
reactToChange: function prefs_reactToChange(aSubject, aData) {
// aData: prefSet, prefRemoved
gDataman.debugMsg("Observed pref change for: " + aSubject.host);
// Do "surgical" updates.
let domain = gDomains.getDomainFromHostWithCheck(aSubject.host);
gDataman.debugMsg("domain: " + domain);
// Does change affect possibly loaded Preferences pane?
let affectsLoaded = this.prefs.length &&
(domain == gDomains.selectedDomain.title);
let idx = -1, domainPrefs = 0;
if (affectsLoaded) {
gDataman.debugMsg("affects loaded");
for (let i = 0; i < this.prefs.length; i++) {
let cpref = this.prefs[i];
if (cpref && cpref.host == aSubject.host && cpref.name == aSubject.name) {
idx = i;
break;
}
}
if (aData == "prefRemoved")
domainPrefs = this.prefs.length;
}
else if (aData == "prefRemoved") {
// See if there are any prefs left for that domain.
Services.contentPrefs2.hasPrefs(domain != "*" ? domain : null, null, {
handleResult(prefResult) {
if (!prefResult.value) {
gDomains.removeDomainOrFlag(domain, "hasPreferences");
}
},
handleCompletion: () => {
},
});
}
if (aData == "prefSet")
aSubject.displayHost = gLocSvc.idn.convertToDisplayIDN(aSubject.host, {});
// Affects loaded domain and is an existing pref.
if (idx >= 0) {
if (aData == "prefSet") {
this.prefs[idx] = aSubject;
if (affectsLoaded)
this.tree.treeBoxObject.invalidateRow(idx);
}
else if (aData == "prefRemoved") {
this.prefs.splice(idx, 1);
if (affectsLoaded) {
this.tree.treeBoxObject.rowCountChanged(idx, -1);
}
if (domainPrefs == 1)
gDomains.removeDomainOrFlag(domain, "hasPreferences");
}
}
else if (aData == "prefSet") {
// Affects loaded domain but is not an existing pref.
// Pref set, no prev index known - either new or existing pref domain.
if (affectsLoaded) {
this.prefs.push(aSubject);
this.tree.treeBoxObject.rowCountChanged(this.prefs.length - 1, 1);
this.sort(null, true, false);
}
// Not the loaded domain but it now has a preference.
else {
gDomains.addDomainOrFlag(domain, "hasPreferences");
}
}
},
forget: function prefs_forget() {
let callbacks = {
handleResult(resultDomain) {
},
handleCompletion: () => {
gDomains.removeDomainOrFlag(domain, "hasPreferences");
},
};
let domain = gDomains.selectedDomain.title;
if (domain == "*") {
Services.contentPrefs2.removeAllGlobals(null, callbacks);
}
else {
Services.contentPrefs2.removeBySubdomain(domain, null, callbacks);
}
},
// nsITreeView
__proto__: gBaseTreeView,
get rowCount() {
return this.prefs.length;
},
getCellText: function(aRow, aColumn) {
let cpref = this.prefs[aRow];
switch (aColumn.id) {
case "prefsHostCol":
return cpref.displayHost || "*";
case "prefsNameCol":
return cpref.name;
case "prefsValueCol":
return cpref.value;
}
},
};
// :::::::::::::::::::: passwords panel ::::::::::::::::::::
var gPasswords = {
tree: null,
removeButton: null,
toggleButton: null,
pwdCol: null,
allSignons: [],
displayedSignons: [],
showPasswords: false,
initialize: function passwords_initialize() {
gDataman.debugMsg("Initializing passwords panel");
this.tree = document.getElementById("passwordsTree");
this.tree.view = this;
this.removeButton = document.getElementById("pwdRemove");
this.toggleButton = document.getElementById("pwdToggle");
this.toggleButton.label = gDataman.bundle.getString("pwd.showPasswords");
this.toggleButton.accessKey = gDataman.bundle.getString("pwd.showPasswords.accesskey");
this.pwdCol = document.getElementById("pwdPasswordCol");
this.tree.treeBoxObject.beginUpdateBatch();
// this.loadList() is being called in gDomains.initialize() already
this.displayedSignons = this.allSignons.filter(
function (aSignon) {
return gDomains.hostMatchesSelected(aSignon.hostname);
});
this.sort(null, false, false);
this.tree.treeBoxObject.endUpdateBatch();
},
shutdown: function passwords_shutdown() {
gDataman.debugMsg("Shutting down passwords panel");
if (this.showPasswords)
this.togglePasswordVisible();
this.tree.view.selection.clearSelection();
this.tree.view = null;
this.displayedSignons = [];
},
loadList: function passwords_loadList() {
this.allSignons = Services.logins.getAllLogins();
},
_getObjID: function passwords__getObjID(aIdx) {
var curSignon = gPasswords.displayedSignons[aIdx];
return curSignon.hostname + "|" + curSignon.httpRealm + "|" + curSignon.username;
},
select: function passwords_select() {
var selections = gDataman.getTreeSelections(this.tree);
this.removeButton.disabled = !selections.length;
return true;
},
selectAll: function passwords_selectAll() {
this.tree.view.selection.selectAll();
},
handleKeyPress: function passwords_handleKeyPress(aEvent) {
if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
(AppConstants.platform == "macosx" &&
aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE)) {
this.delete();
}
},
sort: function passwords_sort(aColumn, aUpdateSelection, aInvertDirection) {
// Make sure we have a valid column.
let column = aColumn;
if (!column) {
let sortedCol = this.tree.columns.getSortedColumn();
if (sortedCol)
column = sortedCol.element;
else
column = document.getElementById("pwdHostCol");
}
else if (column.localName == "treecols" || column.localName == "splitter")
return;
if (!column || column.localName != "treecol") {
Cu.reportError("No column found to sort form data by");
return;
}
let dirAscending = column.getAttribute("sortDirection") !=
(aInvertDirection ? "ascending" : "descending");
let dirFactor = dirAscending ? 1 : -1;
// Clear attributes on all columns, we're setting them again after sorting.
for (let node = column.parentNode.firstChild; node; node = node.nextSibling) {
node.removeAttribute("sortActive");
node.removeAttribute("sortDirection");
}
// compare function for two signons
let compfunc = function passwords_sort_compare(aOne, aTwo) {
switch (column.id) {
case "pwdHostCol":
return dirFactor * aOne.hostname.localeCompare(aTwo.hostname);
case "pwdUserCol":
return dirFactor * aOne.username.localeCompare(aTwo.username);
case "pwdPasswordCol":
return dirFactor * aOne.password.localeCompare(aTwo.password);
}
return 0;
};
if (aUpdateSelection) {
var selectionCache = gDataman.getSelectedIDs(this.tree, this._getObjID);
}
this.tree.view.selection.clearSelection();
// Do the actual sorting of the array.
this.displayedSignons.sort(compfunc);
this.tree.treeBoxObject.invalidate();
if (aUpdateSelection) {
gDataman.restoreSelectionFromIDs(this.tree, this._getObjID, selectionCache);
}
// Set attributes to the sorting we did.
column.setAttribute("sortActive", "true");
column.setAttribute("sortDirection", dirAscending ? "ascending" : "descending");
},
delete: function passwords_delete() {
var selections = gDataman.getTreeSelections(this.tree);
if (selections.length > 1) {
let title = gDataman.bundle.getString("pwd.deleteSelectedTitle");
let msg = gDataman.bundle.getString("pwd.deleteSelected");
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_1_DEFAULT)
let yes = gDataman.bundle.getString("pwd.deleteSelectedYes");
if (Services.prompt.confirmEx(window, title, msg, flags, yes, null, null,
null, {value: 0}) == 1) // 1=="Cancel" button
return;
}
this.tree.view.selection.clearSelection();
// Loop backwards so later indexes in the list don't change.
for (let i = selections.length - 1; i >= 0; i--) {
let delSignon = this.displayedSignons[selections[i]];
this.allSignons.splice(this.allSignons.indexOf(this.displayedSignons[selections[i]]), 1);
this.displayedSignons.splice(selections[i], 1);
this.tree.treeBoxObject.rowCountChanged(selections[i], -1);
Services.logins.removeLogin(delSignon);
}
if (!this.displayedSignons.length)
gDomains.removeDomainOrFlag(gDomains.selectedDomain.title, "hasPasswords");
// Select the entry after the first deleted one or the last of all entries.
if (selections.length && this.displayedSignons.length)
this.tree.view.selection.toggleSelect(selections[0] < this.displayedSignons.length ?
selections[0] :
this.displayedSignons.length - 1);
},
togglePasswordVisible: function passwords_togglePasswordVisible() {
if (this.showPasswords || this._confirmShowPasswords()) {
this.showPasswords = !this.showPasswords;
this.toggleButton.label = gDataman.bundle.getString(this.showPasswords ?
"pwd.hidePasswords" :
"pwd.showPasswords");
this.toggleButton.accessKey = gDataman.bundle.getString(this.showPasswords ?
"pwd.hidePasswords.accesskey" :
"pwd.showPasswords.accesskey");
this.pwdCol.hidden = !this.showPasswords;
}
},
_confirmShowPasswords: function passwords__confirmShowPasswords() {
// This doesn't harm if passwords are not encrypted.
let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"]
.createInstance(Ci.nsIPK11TokenDB);
let token = tokendb.getInternalKeyToken();
// If there is no master password, still give the user a chance to opt-out
// of displaying passwords
if (token.checkPassword(""))
return this._askUserShowPasswords();
// So there's a master password. But since checkPassword didn't succeed,
// we're logged out (per nsIPK11Token.idl).
try {
// Relogin and ask for the master password.
token.login(true); // 'true' means always prompt for token password. User
// will be prompted until clicking 'Cancel' or
// entering the correct password.
}
catch (e) {
// An exception will be thrown if the user cancels the login prompt dialog.
// User is also logged out of Software Security Device.
}
return token.isLoggedIn();
},
_askUserShowPasswords: function passwords__askUserShowPasswords() {
// Confirm the user wants to display passwords.
return Services.prompt.confirmEx(window, null,
gDataman.bundle.getString("pwd.noMasterPasswordPrompt"),
Services.prompt.STD_YES_NO_BUTTONS,
null, null, null, null, { value: false }) == 0; // 0=="Yes" button
},
updateContext: function passwords_updateContext() {
document.getElementById("pwd-context-remove").disabled =
this.removeButton.disabled;
document.getElementById("pwd-context-copypassword").disabled =
this.tree.view.selection.count != 1;
document.getElementById("pwd-context-selectall").disabled =
this.tree.view.selection.count >= this.tree.view.rowCount;
},
copySelPassword: function passwords_copySelPassword() {
// Copy selected signon's password to clipboard.
let row = this.tree.currentIndex;
let password = gPasswords.displayedSignons[row].password;
gLocSvc.clipboard.copyString(password);
},
copyPassword: function passwords_copyPassword() {
// Prompt for the master password upfront.
let token = Cc["@mozilla.org/security/pk11tokendb;1"]
.getService(Ci.nsIPK11TokenDB)
.getInternalKeyToken();
if (this.showPasswords || token.checkPassword(""))
this.copySelPassword();
else {
try {
token.login(true);
this.copySelPassword();
} catch (ex) {
// If user cancels an exception is expected.
}
}
},
reactToChange: function passwords_reactToChange(aSubject, aData) {
// Not interested in legacy hostsaving changes here.
// They will be handled in perm-changed.
if (/^hostSaving/.test(aData))
return;
// aData: addLogin, modifyLogin, removeLogin, removeAllLogins
if (aData == "removeAllLogins") {
// Go for re-parsing the whole thing.
if (this.displayedSignons.length) {
this.tree.treeBoxObject.beginUpdateBatch();
this.tree.view.selection.clearSelection();
this.displayedSignons = [];
this.tree.treeBoxObject.endUpdateBatch();
}
this.loadList();
let domainList = [];
for (let lSignon of this.allSignons) {
let domain = gDomains.getDomainFromHost(lSignon.hostname);
if (!domainList.includes(domain))
domainList.push(domain);
}
gDomains.resetFlagToDomains("hasPasswords", domainList);
return;
}
// Usual notifications for addLogin, modifyLogin, removeLogin - do "surgical" updates.
let curLogin = null, oldLogin = null;
if (aData == "modifyLogin" &&
aSubject instanceof Ci.nsIArray) {
let enumerator = aSubject.enumerate();
if (enumerator.hasMoreElements()) {
oldLogin = enumerator.getNext();
oldLogin.QueryInterface(Ci.nsILoginInfo);
}
if (enumerator.hasMoreElements()) {
curLogin = enumerator.getNext();
curLogin.QueryInterface(Ci.nsILoginInfo);
}
}
else if (aSubject instanceof Ci.nsILoginInfo) {
curLogin = aSubject; oldLogin = aSubject;
}
else {
Cu.reportError("Observed an unrecognized signon change of type " + aData);
}
let domain = gDomains.getDomainFromHost(curLogin.hostname);
// Does change affect possibly loaded Passwords pane?
let affectsLoaded = this.displayedSignons.length &&
gDomains.hostMatchesSelected(curLogin.hostname);
if (aData == "addLogin") {
this.allSignons.push(curLogin);
if (affectsLoaded) {
this.displayedSignons.push(this.allSignons[this.allSignons.length - 1]);
this.tree.treeBoxObject.rowCountChanged(this.allSignons.length - 1, 1);
this.sort(null, true, false);
}
else {
gDomains.addDomainOrFlag(curLogin.hostname, "hasPasswords");
}
}
else {
let idx = -1, disp_idx = -1, domainPasswords = 0;
if (affectsLoaded) {
for (let i = 0; i < this.displayedSignons.length; i++) {
let signon = this.displayedSignons[i];
if (signon && signon.equals(oldLogin)) {
idx = this.allSignons.indexOf(this.displayedSignons[i]);
disp_idx = i;
break;
}
}
if (aData == "removeLogin")
domainPasswords = this.displayedSignons.length;
}
else {
for (let i = 0; i < this.allSignons.length; i++) {
let signon = this.allSignons[i];
if (signon && signon.equals(oldLogin)) {
idx = i;
if (aData != "removeLogin")
break;
}
if (aData == "removeLogin" &&
gDomains.getDomainFromHost(signon.hostname) == domain)
domainPasswords++;
}
}
if (idx >= 0) {
if (aData == "modifyLogin") {
this.allSignons[idx] = curLogin;
if (affectsLoaded)
this.tree.treeBoxObject.invalidateRow(disp_idx);
}
else if (aData == "removeLogin") {
this.allSignons.splice(idx, 1);
if (affectsLoaded) {
this.displayedSignons.splice(disp_idx, 1);
this.tree.treeBoxObject.rowCountChanged(disp_idx, -1);
}
if (domainPasswords == 1)
gDomains.removeDomainOrFlag(domain, "hasPasswords");
}
}
}
},
forget: function passwords_forget() {
// Loop backwards so later indexes in the list don't change.
for (let i = this.allSignons.length - 1; i >= 0; i--) {
if (gDomains.hostMatchesSelected(this.allSignons[i].hostname)) {
// Remove from internal list needs to be before actually deleting.
let delSignon = this.allSignons[i];
this.allSignons.splice(i, 1);
Services.logins.removeLogin(delSignon);
}
}
gDomains.removeDomainOrFlag(gDomains.selectedDomain.title, "hasPasswords");
},
// nsITreeView
__proto__: gBaseTreeView,
get rowCount() {
return this.displayedSignons.length;
},
getCellText: function(aRow, aColumn) {
let signon = this.displayedSignons[aRow];
switch (aColumn.id) {
case "pwdHostCol":
return signon.httpRealm ?
(signon.hostname + " (" + signon.httpRealm + ")") :
signon.hostname;
case "pwdUserCol":
return signon.username || "";
case "pwdPasswordCol":
return signon.password || "";
}
},
};
// :::::::::::::::::::: web storage panel ::::::::::::::::::::
var gStorage = {
tree: null,
removeButton: null,
storages: [],
displayedStorages: [],
initialize: function storage_initialize() {
gDataman.debugMsg("Initializing storage panel");
this.tree = document.getElementById("storageTree");
this.tree.view = this;
this.removeButton = document.getElementById("storageRemove");
this.tree.treeBoxObject.beginUpdateBatch();
// this.loadList() is being called in gDomains.initialize() already
this.displayedStorages = this.storages.filter(
function (aStorage) {
return gDomains.hostMatchesSelected(aStorage.rawHost);
});
this.sort(null, false, false);
this.tree.treeBoxObject.endUpdateBatch();
},
shutdown: function storage_shutdown() {
gDataman.debugMsg("Shutting down storage panel");
this.tree.view.selection.clearSelection();
this.tree.view = null;
this.displayedStorages = [];
},
loadList: function storage_loadList() {
this.storages = [];
// Load appCache entries.
let groups = gLocSvc.appcache.getGroups();
gDataman.debugMsg("Loading " + groups.length + " appcache entries");
for (let lGroup of groups) {
let uri = Services.io.newURI(lGroup);
let cache = gLocSvc.appcache.getActiveCache(lGroup);
this.storages.push({host: uri.host,
rawHost: uri.host,
type: "appCache",
size: cache.usage,
groupID: lGroup});
}
// Load DOM storage entries, unfortunately need to go to the DB. :(
let domstorelist = [];
let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
file.append("webappsstore.sqlite");
if (file.exists()) {
var connection = Services.storage.openDatabase(file);
try {
if (connection.tableExists("webappsstore2")) {
var statement =
connection.createStatement("SELECT scope, key FROM webappsstore2");
while (statement.executeStep())
domstorelist.push({scope: statement.getString(0),
key: statement.getString(1)});
statement.reset();
statement.finalize();
}
} finally {
connection.close();
}
}
gDataman.debugMsg("Loading " + domstorelist.length + " DOM Storage entries");
// Scopes are reversed, e.g. |moc.elgoog.www.:http:80| (for localStorage).
for (let i = 0; i < domstorelist.length; i++) {
// Get the host from the reversed scope.
let scopeparts = domstorelist[i].scope.split(":");
let host = "", type = "unknown";
let origHost = scopeparts[0].split("").reverse().join("");
let rawHost = host = origHost.replace(/^\./, "");
if (scopeparts.length > 1) {
// This is a localStore, [1] is protocol, [2] is port.
type = "localStorage";
host = scopeparts[1].length ? scopeparts[1] + "://" + host : host;
// Add port if it's not the default for this protocol.
if (scopeparts[2] &&
!((scopeparts[1] == "http" && scopeparts[2] == 80) ||
(scopeparts[1] == "https" && scopeparts[2] == 443))) {
host = host + ":" + scopeparts[2];
}
}
// Make sure we only add known/supported types
if (type != "unknown") {
// Merge entries for one scope into a single entry if possible.
let scopefound = false;
for (let j = 0; j < this.storages.length; j++) {
if (this.storages[j].type == type && this.storages[j].host == host) {
this.storages[j].keys.push(domstorelist[i].key);
scopefound = true;
break;
}
}
if (!scopefound) {
this.storages.push({host: host,
rawHost: rawHost,
type: type,
// FIXME if you want getUsage no longer exists
// But I think it's not worth it. Seems the only way
// to do this is to get all the key names and values
// and add the string lengths together
// size: gLocSvc.domstoremgr.getUsage(rawHost),
size: 0,
origHost: origHost,
keys: [domstorelist[i].key]});
}
}
}
// Load indexedDB entries, unfortunately need to read directory for now. :(
let dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
dir.append("indexedDB");
if (dir.exists() && dir.isDirectory()) {
// Enumerate subdir entries, names are like "http+++davidflanagan.com" or
// "https+++mochi.test+8888", and filter out the domain name and protocol
// from that.
// gLocSvc.idxdbmgr is usable as soon as we have a URI.
let files = dir.directoryEntries
.QueryInterface(Ci.nsIDirectoryEnumerator);
gDataman.debugMsg("Loading IndexedDB entries");
while (files.hasMoreElements()) {
let file = files.nextFile;
// Convert directory name to a URI.
let host = file.leafName.replace(/\+\+\+/, "://").replace(/\+(\d+)$/, ":$1");
let uri = Services.io.newURI(host);
this.storages.push({host: host,
rawHost: uri.host,
type: "indexedDB",
size: 0,
path: file.path});
// Get IndexedDB usage (DB size)
gLocSvc.idxdbmgr.getUsageForURI(uri,
function(aUri, aUsage) {
gStorage.storages.forEach(function(aElement) {
if (aUri.host == aElement.rawHost)
aElement.size = aUsage;
});
});
}
}
},
_getObjID: function storage__getObjID(aIdx) {
var curStorage = gStorage.displayedStorages[aIdx];
return curStorage.host + "|" + curStorage.type;
},
select: function storage_select() {
var selections = gDataman.getTreeSelections(this.tree);
this.removeButton.disabled = !selections.length;
return true;
},
selectAll: function storage_selectAll() {
this.tree.view.selection.selectAll();
},
handleKeyPress: function storage_handleKeyPress(aEvent) {
if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
(AppConstants.platform == "macosx" &&
aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE)) {
this.delete();
}
},
sort: function storage_sort(aColumn, aUpdateSelection, aInvertDirection) {
// Make sure we have a valid column.
let column = aColumn;
if (!column) {
let sortedCol = this.tree.columns.getSortedColumn();
if (sortedCol)
column = sortedCol.element;
else
column = document.getElementById("storageHostCol");
}
else if (column.localName == "treecols" || column.localName == "splitter")
return;
if (!column || column.localName != "treecol") {
Cu.reportError("No column found to sort form data by");
return;
}
let dirAscending = column.getAttribute("sortDirection") !=
(aInvertDirection ? "ascending" : "descending");
let dirFactor = dirAscending ? 1 : -1;
// Clear attributes on all columns, we're setting them again after sorting.
for (let node = column.parentNode.firstChild; node; node = node.nextSibling) {
node.removeAttribute("sortActive");
node.removeAttribute("sortDirection");
}
// compare function for two content prefs
let compfunc = function storage_sort_compare(aOne, aTwo) {
switch (column.id) {
case "storageHostCol":
return dirFactor * aOne.host.localeCompare(aTwo.host);
case "storageTypeCol":
return dirFactor * aOne.type.localeCompare(aTwo.type);
}
return 0;
};
if (aUpdateSelection) {
var selectionCache = gDataman.getSelectedIDs(this.tree, this._getObjID);
}
this.tree.view.selection.clearSelection();
// Do the actual sorting of the array.
this.displayedStorages.sort(compfunc);
this.tree.treeBoxObject.invalidate();
if (aUpdateSelection) {
gDataman.restoreSelectionFromIDs(this.tree, this._getObjID, selectionCache);
}
// Set attributes to the sorting we did.
column.setAttribute("sortActive", "true");
column.setAttribute("sortDirection", dirAscending ? "ascending" : "descending");
},
delete: function storage_delete() {
var selections = gDataman.getTreeSelections(this.tree);
if (selections.length > 1) {
let title = gDataman.bundle.getString("storage.deleteSelectedTitle");
let msg = gDataman.bundle.getString("storage.deleteSelected");
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_1_DEFAULT)
let yes = gDataman.bundle.getString("storage.deleteSelectedYes");
if (Services.prompt.confirmEx(window, title, msg, flags, yes, null, null,
null, {value: 0}) == 1) // 1=="Cancel" button
return;
}
this.tree.view.selection.clearSelection();
// Loop backwards so later indexes in the list don't change.
for (let i = selections.length - 1; i >= 0; i--) {
let delStorage = this.displayedStorages[selections[i]];
this.storages.splice(
this.storages.indexOf(this.displayedStorages[selections[i]]), 1);
this.displayedStorages.splice(selections[i], 1);
this.tree.treeBoxObject.rowCountChanged(selections[i], -1);
// Remove the actual entry.
this._deleteItem(delStorage);
}
if (!this.displayedStorages.length)
gDomains.removeDomainOrFlag(gDomains.selectedDomain.title, "hasStorage");
// Select the entry after the first deleted one or the last of all entries.
if (selections.length && this.displayedStorages.length)
this.tree.view.selection.toggleSelect(selections[0] < this.displayedStorages.length ?
selections[0] :
this.displayedStorages.length - 1);
},
_deleteItem: function storage__deleteItem(aStorageItem) {
switch (aStorageItem.type) {
case "appCache":
gLocSvc.appcache.getActiveCache(aStorageItem.groupID).discard();
break;
case "localStorage":
let testHost = aStorageItem.host;
if (!/:/.test(testHost))
testHost = "http://" + testHost;
let uri = Services.io.newURI(testHost);
let principal = gLocSvc.ssm.createCodebasePrincipal(uri, {});
let storage = gLocSvc.domstoremgr.createStorage(null, principal, "");
storage.clear();
break;
case "indexedDB":
gLocSvc.idxdbmgr.clearDatabasesForURI(
Services.io.newURI(aStorageItem.host));
break;
}
},
updateContext: function storage_updateContext() {
document.getElementById("storage-context-remove").disabled =
this.removeButton.disabled;
document.getElementById("storage-context-selectall").disabled =
this.tree.view.selection.count >= this.tree.view.rowCount;
},
reloadList: function storage_reloadList() {
// As many storage types don't have app-wide functions to notify us of
// changes, call this one periodically to completely redo the storage
// list and so keep the Data Manager up to date.
var selectionCache = [];
if (this.displayedStorages.length) {
selectionCache = gDataman.getSelectedIDs(this.tree, this._getObjID);
this.displayedStorages = [];
}
this.loadList();
var domainList = [];
for (let lStorage of this.storages) {
let domain = gDomains.getDomainFromHost(lStorage.rawHost);
if (!domainList.includes(domain))
domainList.push(domain);
}
gDomains.resetFlagToDomains("hasStorage", domainList);
// Restore the local panel display if needed.
if (gTabs.activePanel == "storagePanel" &&
gDomains.selectedDomain.hasStorage) {
this.tree.treeBoxObject.beginUpdateBatch();
this.displayedStorages = this.storages.filter(
function (aStorage) {
return gDomains.hostMatchesSelected(aStorage.rawHost);
});
this.sort(null, false, false);
gDataman.restoreSelectionFromIDs(this.tree, this._getObjID, selectionCache);
this.tree.treeBoxObject.endUpdateBatch();
}
},
reactToChange: function storage_reactToChange(aSubject, aData) {
// aData: null (sessionStorage, localStorage) + nsIDOMStorageEvent in aSubject
// --- for appCache and indexedDB, no change notifications are known!
// --- because of that, we don't do anything here and instead use
// reloadList periodically
// session storage also comes here, but currently not supported
// aData: null, all data in aSubject
switch (aData) {
case "localStorage":
case "sessionStorage":
break;
default:
Cu.reportError("Observed an unrecognized storage change of type " + aData);
}
gDataman.debugMsg("Found storage event for: " + aData);
},
forget: function storage_forget() {
// Loop backwards so later indexes in the list don't change.
for (let i = this.storages.length - 1; i >= 0; i--) {
if (gDomains.hostMatchesSelected(this.storages[i].hostname)) {
// Remove from internal list should be before actually deleting.
let delStorage = this.storages[i];
this.storages.splice(i, 1);
this._deleteItem(delStorage);
}
}
gDomains.removeDomainOrFlag(gDomains.selectedDomain.title, "hasStorage");
},
// nsITreeView
__proto__: gBaseTreeView,
get rowCount() {
return this.displayedStorages.length;
},
getCellText: function(aRow, aColumn) {
let storage = this.displayedStorages[aRow];
switch (aColumn.id) {
case "storageHostCol":
return storage.host;
case "storageTypeCol":
return storage.type;
case "storageSizeCol":
return gDataman.bundle.getFormattedString("storageUsage",
DownloadUtils.convertByteUnits(storage.size));
}
},
};
// :::::::::::::::::::: form data panel ::::::::::::::::::::
var gFormdata = {
tree: null,
removeButton: null,
searchfield: null,
formdata: [],
displayedFormdata: [],
initialize: function formdata_initialize() {
gDataman.debugMsg("Initializing form data panel");
this.tree = document.getElementById("formdataTree");
this.tree.view = this;
this.searchfield = document.getElementById("fdataSearch");
this.removeButton = document.getElementById("fdataRemove");
// Always load fresh list, no need to react to changes when pane not open.
this.loadList();
this.search("");
},
shutdown: function formdata_shutdown() {
gDataman.debugMsg("Shutting down form data panel");
this.tree.view.selection.clearSelection();
this.tree.view = null;
this.displayedFormdata = [];
},
_promiseLoadFormHistory: function formdata_promiseLoadFormHistory() {
return new Promise(resolve => {
let callbacks = {
handleResult(result) {
gFormdata.formdata.push({fieldname: result.fieldname,
value: result.value,
timesUsed: result.timesUsed,
firstUsed: gFormdata._getTimeString(result.firstUsed),
firstUsedSortValue: result.firstUsed,
lastUsed: gFormdata._getTimeString(result.lastUsed),
lastUsedSortValue: result.lastUsed,
guid: result.guid});
},
handleError(aError) {
Cu.reportError(aError);
},
handleCompletion(aReason) {
// This needs to stay in or Async.promiseSpinningly will fail.
resolve();
}
};
gLocSvc.FormHistory.search(["fieldname", "value", "timesUsed", "firstUsed", "lastUsed", "guid"],
{},
callbacks);
});
},
loadList: function formdata_loadList() {
this.formdata = [];
// Use Async.promiseSpinningly to Sync the call.
Async.promiseSpinningly(this._promiseLoadFormHistory());
},
_getTimeString: function formdata__getTimeString(aTimestamp) {
if (aTimestamp) {
let date = new Date(aTimestamp / 1000);
// If a date has an extreme value, the dateservice can't cope with it
// properly, so we'll just return a blank string.
let dtString = "";
try {
const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
dateStyle: "full", timeStyle: "long" });
dtString = dateTimeFormatter.format(date);
}
catch (e) {}
return dtString;
}
return "";
},
_getObjID: function formdata__getObjID(aIdx) {
return gFormdata.displayedFormdata[aIdx].guid;
},
select: function formdata_select() {
var selections = gDataman.getTreeSelections(this.tree);
this.removeButton.disabled = !selections.length;
return true;
},
selectAll: function formdata_selectAll() {
this.tree.view.selection.selectAll();
},
handleKeyPress: function formdata_handleKeyPress(aEvent) {
if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
(AppConstants.platform == "macosx" &&
aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE)) {
this.delete();
}
},
sort: function formdata_sort(aColumn, aUpdateSelection, aInvertDirection) {
// Make sure we have a valid column.
let column = aColumn;
if (!column) {
let sortedCol = this.tree.columns.getSortedColumn();
if (sortedCol)
column = sortedCol.element;
else
column = document.getElementById("fdataFieldCol");
}
else if (column.localName == "treecols" || column.localName == "splitter")
return;
if (!column || column.localName != "treecol") {
Cu.reportError("No column found to sort form data by");
return;
}
let dirAscending = column.getAttribute("sortDirection") !=
(aInvertDirection ? "ascending" : "descending");
let dirFactor = dirAscending ? 1 : -1;
// Clear attributes on all columns, we're setting them again after sorting.
for (let node = column.parentNode.firstChild; node; node = node.nextSibling) {
node.removeAttribute("sortActive");
node.removeAttribute("sortDirection");
}
// compare function for two formdata items
let compfunc = function formdata_sort_compare(aOne, aTwo) {
switch (column.id) {
case "fdataFieldCol":
return dirFactor * aOne.fieldname.localeCompare(aTwo.fieldname);
case "fdataValueCol":
return dirFactor * aOne.value.localeCompare(aTwo.value);
case "fdataCountCol":
return dirFactor * (aOne.timesUsed - aTwo.timesUsed);
case "fdataFirstCol":
return dirFactor * (aOne.firstUsedSortValue - aTwo.firstUsedSortValue);
case "fdataLastCol":
return dirFactor * (aOne.lastUsedSortValue - aTwo.lastUsedSortValue);
}
return 0;
};
if (aUpdateSelection) {
var selectionCache = gDataman.getSelectedIDs(this.tree, this._getObjID);
}
this.tree.view.selection.clearSelection();
// Do the actual sorting of the array.
this.displayedFormdata.sort(compfunc);
this.tree.treeBoxObject.invalidate();
if (aUpdateSelection) {
gDataman.restoreSelectionFromIDs(this.tree, this._getObjID, selectionCache);
}
// Set attributes to the sorting we did.
column.setAttribute("sortActive", "true");
column.setAttribute("sortDirection", dirAscending ? "ascending" : "descending");
},
delete: function formdata_delete() {
var selections = gDataman.getTreeSelections(this.tree);
if (selections.length > 1) {
let title = gDataman.bundle.getString("fdata.deleteSelectedTitle");
let msg = gDataman.bundle.getString("fdata.deleteSelected");
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_1_DEFAULT)
let yes = gDataman.bundle.getString("fdata.deleteSelectedYes");
if (Services.prompt.confirmEx(window, title, msg, flags, yes, null, null,
null, {value: 0}) == 1) // 1=="Cancel" button
return;
}
this.tree.view.selection.clearSelection();
// Loop backwards so later indexes in the list don't change.
for (let i = selections.length - 1; i >= 0; i--) {
let delFData = this.displayedFormdata[selections[i]];
this.formdata.splice(this.formdata.indexOf(this.displayedFormdata[selections[i]]), 1);
this.displayedFormdata.splice(selections[i], 1);
this.tree.treeBoxObject.rowCountChanged(selections[i], -1);
let changes = [{op: "remove",
fieldname: delFData.fieldname,
value: delFData.value}];
// Async call but we don't care about the completion just now and remove the entry from the panel.
// If the call fails the entry will just reappear the next time the form panel is opened.
gLocSvc.FormHistory.update(changes);
}
// Select the entry after the first deleted one or the last of all entries.
if (selections.length && this.displayedFormdata.length)
this.tree.view.selection.toggleSelect(selections[0] < this.displayedFormdata.length ?
selections[0] :
this.displayedFormdata.length - 1);
},
search: function formdata_search(aSearchString) {
let selectionCache = gDataman.getSelectedIDs(this.tree, this._getObjID);
this.tree.treeBoxObject.beginUpdateBatch();
this.tree.view.selection.clearSelection();
var lcSearch = aSearchString.toLocaleLowerCase();
this.displayedFormdata = this.formdata.filter(
function(aFd) {
return aFd.fieldname.toLocaleLowerCase().includes(lcSearch) ||
aFd.value.toLocaleLowerCase().includes(lcSearch);
});
this.sort(null, false, false);
gDataman.restoreSelectionFromIDs(this.tree, this._getObjID, selectionCache);
this.tree.treeBoxObject.endUpdateBatch();
},
focusSearch: function formdata_focusSearch() {
this.searchfield.focus();
},
updateContext: function formdata_updateContext() {
document.getElementById("fdata-context-remove").disabled =
this.removeButton.disabled;
document.getElementById("fdata-context-selectall").disabled =
this.tree.view.selection.count >= this.tree.view.rowCount;
},
/**
* _promiseReadFormHistory
*
* Retrieves the formddata from the data for the given guid.
*
* @param aGuid guid for which form data should be returned.
* @return Promise<null if no row is found with the specified guid,
* or an object containing the row full content values>
*/
_promiseReadFormHistory: function formdata_promiseReadFormHistory(aGuid) {
return new Promise((resolve, reject) => {
var entry = null;
let callbacks = {
handleResult(result) {
// There can be only one entry for a given guid.
// If there are more we will not behead it but instead
// only keep the last returned result.
entry = result;
},
handleError(aError) {
Cu.reportError(aError);
reject(error);
},
handleCompletion(aReason) {
resolve(entry);
}
};
gLocSvc.FormHistory.search(["fieldname", "value", "timesUsed", "firstUsed", "lastUsed", "guid"],
{guid :aGuid},
callbacks);
});
},
// Updates the form data panel when receiving a notification.
//
// The notification type is passed in aData.
//
// The following types are supported:
// formhistory-add formhistory-update formhistory-remove
// formhistory-expireoldentries
//
// The following types will be ignored:
// formhistory-shutdown formhistory-beforeexpireoldentries
reactToChange: function formdata_reactToChange(aSubject, aData) {
// Ignore changes when no form data pane is loaded
// or if we caught an unsupported notification.
if (!this.displayedFormdata.length ||
aData == "formhistory-shutdown" ||
aData == "formhistory-beforeexpireoldentries") {
return;
}
if (aData == "formhistory-expireoldentries") {
// Go for re-parsing the whole thing.
this.tree.treeBoxObject.beginUpdateBatch();
this.tree.view.selection.clearSelection();
this.displayedFormdata = [];
this.tree.treeBoxObject.endUpdateBatch();
this.loadList();
this.search("");
return;
}
if (aData != "formhistory-add" && aData != "formhistory-change" &&
aData != "formhistory-remove") {
Cu.reportError("Observed an unrecognized formdata change of type " + aData);
return;
}
var cGuid = null;
if (aSubject instanceof Ci.nsISupportsString) {
cGuid = aSubject.toString();
}
if (!cGuid) {
// We just let the panel stay the same which might cause minor problems
// because there is no longer a notification when removing all entries.
if (aData != "formhistory-remove") {
Cu.reportError("FormHistory guid is null for " + aData);
}
return;
}
var entryData = null;
if (aData == "formhistory-add" || aData == "formhistory-change") {
// Use Async.promiseSpinningly to Sync the call.
Async.promiseSpinningly(this._promiseReadFormHistory(cGuid).then(entry => {
if (entry) {
entryData = entry;
}
return;
}));
if (!entryData) {
Cu.reportError("Could not find added/modifed formdata entry");
return;
}
}
if (aData == "formhistory-add") {
this.formdata.push(entryData);
this.displayedFormdata.push(this.formdata[this.formdata.length - 1]);
this.tree.treeBoxObject.rowCountChanged(this.formdata.length - 1, 1);
this.search("");
}
else {
let idx = -1, disp_idx = -1;
for (let i = 0; i < this.displayedFormdata.length; i++) {
let fdata = this.displayedFormdata[i];
if (fdata && fdata.guid == cGuid) {
idx = this.formdata.indexOf(this.displayedFormdata[i]);
disp_idx = i;
break;
}
}
if (idx >= 0) {
if (aData == "formhistory-change") {
this.formdata[idx] = entryData;
this.tree.treeBoxObject.invalidateRow(disp_idx);
}
else if (aData == "formhistory-remove") {
this.formdata.splice(idx, 1);
this.displayedFormdata.splice(disp_idx, 1);
this.tree.treeBoxObject.rowCountChanged(disp_idx, -1);
}
}
}
},
forget: function formdata_forget() {
gLocSvc.FormHistory.update({ op: "remove" });
},
// nsITreeView
__proto__: gBaseTreeView,
get rowCount() {
return this.displayedFormdata.length;
},
getCellText: function(aRow, aColumn) {
let fdata = this.displayedFormdata[aRow];
switch (aColumn.id) {
case "fdataFieldCol":
return fdata.fieldname;
case "fdataValueCol":
return fdata.value;
case "fdataCountCol":
return fdata.timesUsed;
case "fdataFirstCol":
return fdata.firstUsed;
case "fdataLastCol":
return fdata.lastUsed;
}
},
};
// :::::::::::::::::::: forget panel ::::::::::::::::::::
var gForget = {
forgetDesc: null,
forgetCookies: null,
forgetPermissions: null,
forgetPreferences: null,
forgetPasswords: null,
forgetStorage: null,
forgetFormdata: null,
forgetCookiesLabel: null,
forgetPermissionsLabel: null,
forgetPreferencesLabel: null,
forgetPasswordsLabel: null,
forgetStorageLabel: null,
forgetFormdataLabel: null,
forgetButton: null,
initialize: function forget_initialize() {
gDataman.debugMsg("Initializing forget panel");
this.forgetDesc = document.getElementById("forgetDesc");
["forgetCookies", "forgetPermissions", "forgetPreferences",
"forgetPasswords", "forgetStorage", "forgetFormdata"]
.forEach(function(elemID) {
gForget[elemID] = document.getElementById(elemID);
gForget[elemID].hidden = false;
gForget[elemID].checked = false;
let labelID = elemID + "Label";
gForget[labelID] = document.getElementById(labelID);
gForget[labelID].hidden = true;
});
this.forgetButton = document.getElementById("forgetButton");
this.forgetButton.hidden = false;
if (gDomains.selectedDomain.title == "*")
this.forgetDesc.value = gDataman.bundle.getString("forget.desc.global.pre");
else
this.forgetDesc.value = gDataman.bundle.getFormattedString("forget.desc.domain.pre",
[gDomains.selectedDomain.title]);
this.forgetCookies.disabled = !gDomains.selectedDomain.hasCookies;
this.forgetPermissions.disabled = !gDomains.selectedDomain.hasPermissions;
this.forgetPreferences.disabled = !gDomains.selectedDomain.hasPreferences;
this.forgetPasswords.disabled = !gDomains.selectedDomain.hasPasswords;
this.forgetStorage.disabled = !gDomains.selectedDomain.hasStorage;
this.forgetFormdata.disabled = !gDomains.selectedDomain.hasFormData;
this.forgetFormdata.hidden = !gDomains.selectedDomain.hasFormData;
this.updateOptions();
},
shutdown: function forget_shutdown() {
gDataman.debugMsg("Shutting down forget panel");
},
updateOptions: function forget_updateOptions() {
this.forgetButton.disabled = !(this.forgetCookies.checked ||
this.forgetPermissions.checked ||
this.forgetPreferences.checked ||
this.forgetPasswords.checked ||
this.forgetStorage.checked ||
this.forgetFormdata.checked);
},
handleKeyPress: function forget_handleKeyPress(aEvent) {
if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE) {
// Make sure we do something that makes this panel go away.
if (gDomains.selectedDomain.title)
gDomains.select();
else
gDomains.tree.view.selection.select(0);
}
},
forget: function forget_forget() {
// Domain might get removed and selected domain changed!
let delDomainTitle = gDomains.selectedDomain.title;
if (this.forgetCookies.checked) {
gCookies.forget();
this.forgetCookiesLabel.hidden = false;
}
this.forgetCookies.hidden = true;
if (this.forgetPermissions.checked) {
gPerms.forget();
this.forgetPermissionsLabel.hidden = false;
}
this.forgetPermissions.hidden = true;
if (this.forgetPreferences.checked) {
gPrefs.forget();
this.forgetPreferencesLabel.hidden = false;
}
this.forgetPreferences.hidden = true;
if (this.forgetPasswords.checked) {
gPasswords.forget();
this.forgetPasswordsLabel.hidden = false;
}
this.forgetPasswords.hidden = true;
if (this.forgetStorage.checked) {
gStorage.forget();
this.forgetStorageLabel.hidden = false;
}
this.forgetStorage.hidden = true;
if (this.forgetFormdata.checked) {
gFormdata.forget();
this.forgetFormdataLabel.hidden = false;
}
this.forgetFormdata.hidden = true;
if (delDomainTitle == "*")
this.forgetDesc.value = gDataman.bundle.getString("forget.desc.global.post");
else
this.forgetDesc.value = gDataman.bundle.getFormattedString("forget.desc.domain.post",
[delDomainTitle]);
this.forgetButton.hidden = true;
},
};