/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); const {Async} = ChromeUtils.import("resource://services-common/async.js"); const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); // Load DownloadUtils module for convertByteUnits const {DownloadUtils} = ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm"); // locally loaded services var gLocSvc = {}; ChromeUtils.defineModuleGetter(gLocSvc, "FormHistory", "resource://gre/modules/FormHistory.jsm", "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: | (| 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 if (host != "foo://*") 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 // URI-like, e.g. gopher://example.com and newURI doesn't work there as we // 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, // see bug 568997 for improvements to that interface. 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. // See bug 238045 for details. 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 // see http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/nsICookieService.idl 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", "indexedDB", "install", "login-saving", "object", "offline-app", "popup", "script", "stylesheet", "trackingprotection"]; // Look for a translation. for (let permType of permTypes) { let typeDesc = permType; try { typeDesc = gDataman.bundle.getString("perm." + permType + ".label"); } catch (e) { } let menuitem = this.addType.appendItem(typeDesc, permType); } this.addType.setAttribute("label", gDataman.bundle.getString("perm.type.default")); this.addHost.value = gDomains.selectedDomain.title == "*" ? "" : ("http://www." + gDomains.selectedDomain.title); 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", this.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)); }, getDefault: function permissions_getDefault(aType) { switch (aType) { case "allowXULXBL": return Services.perms.DENY_ACTION; case "cookie": if (Services.prefs.getIntPref("network.cookie.cookieBehavior") == 2) return Services.perms.DENY_ACTION; if (Services.prefs.getIntPref("network.cookie.lifetimePolicy") == 2) return Ci.nsICookiePermission.ACCESS_SESSION; return Services.perms.ALLOW_ACTION; case "geo": return Services.perms.DENY_ACTION; case "indexedDB": return Services.perms.DENY_ACTION; case "install": if (Services.prefs.getBoolPref("xpinstall.whitelist.required")) return Services.perms.DENY_ACTION; return Services.perms.ALLOW_ACTION; case "offline-app": try { if (Services.prefs.getBoolPref("offline-apps.allow_by_default")) return Services.perms.ALLOW_ACTION; } catch(e) { // this pref isn't set by default, ignore failures } if (Services.prefs.getBoolPref("browser.offline-apps.notify")) return Services.perms.DENY_ACTION; return Services.perms.UNKNOWN_ACTION; case "popup": if (Services.prefs.getBoolPref("dom.disable_open_during_load")) return Services.perms.DENY_ACTION; return Services.perms.ALLOW_ACTION; case "trackingprotection": return Services.perms.DENY_ACTION; } // We are not done yet. // This should only be called for new permission types which have not been // added to the Data Manager yet. try { // Look for an nsContentBlocker permission. switch (Services.prefs.getIntPref("permissions.default." + aType)) { case 3: return NOFOREIGN; case 2: return Services.perms.DENY_ACTION; default: return Services.perms.ALLOW_ACTION; } } catch (e) { return Services.perms.UNKNOWN_ACTION; } }, reactToChange: function permissions_reactToChange(aSubject, aData) { // aData: added, changed, deleted, cleared // aSubject: the subject which is the permission to be changed // See http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsIPermissionManager.idl 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. :( // Bug 343163 would make this easier and clean. 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. :( // Bug 630858 would make this easier and clean. 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) // See http://mxr.mozilla.org/mozilla-central/source/dom/indexedDB/nsIIndexedDatabaseManager.idl?mark=39-52#39 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 // see https://developer.mozilla.org/en/DOM/Event/StorageEvent 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. // See bug 238045 for details. 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 */ _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) { // See bug 1346850. Remove has a problem and always sends a null guid. // 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; }, };