/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const { AppConstants } = ChromeUtils.importESModule( "resource://gre/modules/AppConstants.sys.mjs" ); var gDoHExceptionsManager = { _exceptions: new Set(), _list: null, _prefLocked: false, init() { document.addEventListener("dialogaccept", () => this.onApplyChanges()); this._btnAddException = document.getElementById("btnAddException"); this._removeButton = document.getElementById("removeException"); this._removeAllButton = document.getElementById("removeAllExceptions"); this._list = document.getElementById("permissionsBox"); this._urlField = document.getElementById("url"); this.onExceptionInput(); this._loadExceptions(); this.buildExceptionList(); this._urlField.focus(); this._prefLocked = Services.prefs.prefIsLocked( "network.trr.excluded-domains" ); document.getElementById("exceptionDialog").getButton("accept").disabled = this._prefLocked; this._urlField.disabled = this._prefLocked; }, _loadExceptions() { let exceptionsFromPref = Services.prefs.getStringPref( "network.trr.excluded-domains" ); if (!exceptionsFromPref?.trim()) { return; } let exceptions = exceptionsFromPref.trim().split(","); for (let exception of exceptions) { let trimmed = exception.trim(); if (trimmed) { this._exceptions.add(trimmed); } } }, addException() { if (this._prefLocked) { return; } let textbox = document.getElementById("url"); let inputValue = textbox.value.trim(); // trim any leading and trailing space if (!inputValue.startsWith("http:") && !inputValue.startsWith("https:")) { inputValue = `http://${inputValue}`; } let domain = ""; try { let uri = Services.io.newURI(inputValue); domain = uri.host; } catch (ex) { document.l10n .formatValues([ { id: "permissions-invalid-uri-title" }, { id: "permissions-invalid-uri-label" }, ]) .then(([title, message]) => { Services.prompt.alert(window, title, message); }); return; } if (!this._exceptions.has(domain)) { this._exceptions.add(domain); this.buildExceptionList(); } textbox.value = ""; textbox.focus(); // covers a case where the site exists already, so the buttons don't disable this.onExceptionInput(); // enable "remove all" button as needed this._setRemoveButtonState(); }, onExceptionInput() { this._btnAddException.disabled = !this._urlField.value; }, onExceptionKeyPress(event) { if (event.keyCode == KeyEvent.DOM_VK_RETURN) { this._btnAddException.click(); if (document.activeElement == this._urlField) { event.preventDefault(); } } }, onListBoxKeyPress(event) { if (!this._list.selectedItem) { return; } if (this._prefLocked) { return; } if ( event.keyCode == KeyEvent.DOM_VK_DELETE || (AppConstants.platform == "macosx" && event.keyCode == KeyEvent.DOM_VK_BACK_SPACE) ) { this.onExceptionDelete(); event.preventDefault(); } }, onListBoxSelect() { this._setRemoveButtonState(); }, _removeExceptionFromList(exception) { this._exceptions.delete(exception); let exceptionlistitem = document.getElementsByAttribute( "domain", exception )[0]; if (exceptionlistitem) { exceptionlistitem.remove(); } }, onExceptionDelete() { let richlistitem = this._list.selectedItem; let exception = richlistitem.getAttribute("domain"); this._removeExceptionFromList(exception); this._setRemoveButtonState(); }, onAllExceptionsDelete() { for (let exception of this._exceptions.values()) { this._removeExceptionFromList(exception); } this._setRemoveButtonState(); }, _createExceptionListItem(exception) { let richlistitem = document.createXULElement("richlistitem"); richlistitem.setAttribute("domain", exception); let row = document.createXULElement("hbox"); row.setAttribute("style", "flex: 1"); let hbox = document.createXULElement("hbox"); let website = document.createXULElement("label"); website.setAttribute("class", "website-name-value"); website.setAttribute("value", exception); hbox.setAttribute("class", "website-name"); hbox.setAttribute("style", "flex: 3 3; width: 0"); hbox.appendChild(website); row.appendChild(hbox); richlistitem.appendChild(row); return richlistitem; }, _sortExceptions(list, frag, column) { let sortDirection; if (!column) { column = document.querySelector("treecol[data-isCurrentSortCol=true]"); sortDirection = column.getAttribute("data-last-sortDirection") || "ascending"; } else { sortDirection = column.getAttribute("data-last-sortDirection"); sortDirection = sortDirection === "ascending" ? "descending" : "ascending"; } let sortFunc = (a, b) => { return comp.compare(a.getAttribute("domain"), b.getAttribute("domain")); }; let comp = new Services.intl.Collator(undefined, { usage: "sort", }); let items = Array.from(frag.querySelectorAll("richlistitem")); if (sortDirection === "descending") { items.sort((a, b) => sortFunc(b, a)); } else { items.sort(sortFunc); } // Re-append items in the correct order: items.forEach(item => frag.appendChild(item)); let cols = list.previousElementSibling.querySelectorAll("treecol"); cols.forEach(c => { c.removeAttribute("data-isCurrentSortCol"); c.removeAttribute("sortDirection"); }); column.setAttribute("data-isCurrentSortCol", "true"); column.setAttribute("sortDirection", sortDirection); column.setAttribute("data-last-sortDirection", sortDirection); }, _setRemoveButtonState() { if (!this._list) { return; } if (this._prefLocked) { this._removeAllButton.disabled = true; this._removeButton.disabled = true; return; } let hasSelection = this._list.selectedIndex >= 0; this._removeButton.disabled = !hasSelection; let disabledItems = this._list.querySelectorAll( "label.website-name-value[disabled='true']" ); this._removeAllButton.disabled = this._list.itemCount == disabledItems.length; }, onApplyChanges() { if (this._exceptions.size == 0) { Services.prefs.setStringPref("network.trr.excluded-domains", ""); return; } let exceptions = Array.from(this._exceptions); let exceptionPrefString = exceptions.join(","); Services.prefs.setStringPref( "network.trr.excluded-domains", exceptionPrefString ); }, buildExceptionList(sortCol) { // Clear old entries. let oldItems = this._list.querySelectorAll("richlistitem"); for (let item of oldItems) { item.remove(); } let frag = document.createDocumentFragment(); let exceptions = Array.from(this._exceptions.values()); for (let exception of exceptions) { let richlistitem = this._createExceptionListItem(exception); frag.appendChild(richlistitem); } // Sort exceptions. this._sortExceptions(this._list, frag, sortCol); this._list.appendChild(frag); this._setRemoveButtonState(); }, }; document.addEventListener("DOMContentLoaded", () => { gDoHExceptionsManager.init(); });