dohExceptions.js (8588B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const { AppConstants } = ChromeUtils.importESModule( 6 "resource://gre/modules/AppConstants.sys.mjs" 7 ); 8 9 var gDoHExceptionsManager = { 10 _exceptions: new Set(), 11 _list: null, 12 _prefLocked: false, 13 14 init() { 15 document.addEventListener("dialogaccept", () => this.onApplyChanges()); 16 17 this._btnAddException = document.getElementById("btnAddException"); 18 this._removeButton = document.getElementById("removeException"); 19 this._removeAllButton = document.getElementById("removeAllExceptions"); 20 21 this._list = document.getElementById("permissionsBox"); 22 this._list.addEventListener("keypress", event => 23 this.onListBoxKeyPress(event) 24 ); 25 this._list.addEventListener("select", () => this.onListBoxSelect()); 26 27 this._urlField = document.getElementById("url"); 28 this._urlField.addEventListener("input", () => this.onExceptionInput()); 29 this._urlField.addEventListener("keypress", event => 30 this.onExceptionKeyPress(event) 31 ); 32 33 document 34 .getElementById("siteCol") 35 .addEventListener("click", event => 36 this.buildExceptionList(event.target) 37 ); 38 39 document.addEventListener("command", this); 40 41 this.onExceptionInput(); 42 this._loadExceptions(); 43 this.buildExceptionList(); 44 45 this._urlField.focus(); 46 47 this._prefLocked = Services.prefs.prefIsLocked( 48 "network.trr.excluded-domains" 49 ); 50 51 document.getElementById("exceptionDialog").getButton("accept").disabled = 52 this._prefLocked; 53 this._urlField.disabled = this._prefLocked; 54 }, 55 56 handleEvent(event) { 57 switch (event.target.id) { 58 case "key_close": 59 window.close(); 60 break; 61 62 case "btnAddException": 63 this.addException(); 64 break; 65 case "removeException": 66 this.onExceptionDelete(); 67 break; 68 case "removeAllExceptions": 69 this.onAllExceptionsDelete(); 70 break; 71 } 72 }, 73 74 _loadExceptions() { 75 let exceptionsFromPref = Services.prefs.getStringPref( 76 "network.trr.excluded-domains" 77 ); 78 79 if (!exceptionsFromPref?.trim()) { 80 return; 81 } 82 83 let exceptions = exceptionsFromPref.trim().split(","); 84 for (let exception of exceptions) { 85 let trimmed = exception.trim(); 86 if (trimmed) { 87 this._exceptions.add(trimmed); 88 } 89 } 90 }, 91 92 addException() { 93 if (this._prefLocked) { 94 return; 95 } 96 97 let textbox = document.getElementById("url"); 98 let inputValue = textbox.value.trim(); // trim any leading and trailing space 99 if (!inputValue.startsWith("http:") && !inputValue.startsWith("https:")) { 100 inputValue = `http://${inputValue}`; 101 } 102 let domain = ""; 103 try { 104 let uri = Services.io.newURI(inputValue); 105 domain = uri.host; 106 } catch (ex) { 107 document.l10n 108 .formatValues([ 109 { id: "permissions-invalid-uri-title" }, 110 { id: "permissions-invalid-uri-label" }, 111 ]) 112 .then(([title, message]) => { 113 Services.prompt.alert(window, title, message); 114 }); 115 return; 116 } 117 118 if (!this._exceptions.has(domain)) { 119 this._exceptions.add(domain); 120 this.buildExceptionList(); 121 } 122 123 textbox.value = ""; 124 textbox.focus(); 125 126 // covers a case where the site exists already, so the buttons don't disable 127 this.onExceptionInput(); 128 129 // enable "remove all" button as needed 130 this._setRemoveButtonState(); 131 }, 132 133 onExceptionInput() { 134 this._btnAddException.disabled = !this._urlField.value; 135 }, 136 137 onExceptionKeyPress(event) { 138 if (event.keyCode == KeyEvent.DOM_VK_RETURN) { 139 this._btnAddException.click(); 140 if (document.activeElement == this._urlField) { 141 event.preventDefault(); 142 } 143 } 144 }, 145 146 onListBoxKeyPress(event) { 147 if (!this._list.selectedItem) { 148 return; 149 } 150 151 if (this._prefLocked) { 152 return; 153 } 154 155 if ( 156 event.keyCode == KeyEvent.DOM_VK_DELETE || 157 (AppConstants.platform == "macosx" && 158 event.keyCode == KeyEvent.DOM_VK_BACK_SPACE) 159 ) { 160 this.onExceptionDelete(); 161 event.preventDefault(); 162 } 163 }, 164 165 onListBoxSelect() { 166 this._setRemoveButtonState(); 167 }, 168 169 _removeExceptionFromList(exception) { 170 this._exceptions.delete(exception); 171 let exceptionlistitem = document.getElementsByAttribute( 172 "domain", 173 exception 174 )[0]; 175 if (exceptionlistitem) { 176 exceptionlistitem.remove(); 177 } 178 }, 179 180 onExceptionDelete() { 181 let richlistitem = this._list.selectedItem; 182 let exception = richlistitem.getAttribute("domain"); 183 184 this._removeExceptionFromList(exception); 185 186 this._setRemoveButtonState(); 187 }, 188 189 onAllExceptionsDelete() { 190 for (let exception of this._exceptions.values()) { 191 this._removeExceptionFromList(exception); 192 } 193 194 this._setRemoveButtonState(); 195 }, 196 197 _createExceptionListItem(exception) { 198 let richlistitem = document.createXULElement("richlistitem"); 199 richlistitem.setAttribute("domain", exception); 200 let row = document.createXULElement("hbox"); 201 row.setAttribute("style", "flex: 1"); 202 203 let hbox = document.createXULElement("hbox"); 204 let website = document.createXULElement("label"); 205 website.setAttribute("class", "website-name-value"); 206 website.setAttribute("value", exception); 207 hbox.setAttribute("class", "website-name"); 208 hbox.setAttribute("style", "flex: 3 3; width: 0"); 209 hbox.appendChild(website); 210 row.appendChild(hbox); 211 212 richlistitem.appendChild(row); 213 return richlistitem; 214 }, 215 216 _sortExceptions(list, frag, column) { 217 let sortDirection; 218 219 if (!column) { 220 column = document.querySelector("treecol[data-isCurrentSortCol=true]"); 221 sortDirection = 222 column.getAttribute("data-last-sortDirection") || "ascending"; 223 } else { 224 sortDirection = column.getAttribute("data-last-sortDirection"); 225 sortDirection = 226 sortDirection === "ascending" ? "descending" : "ascending"; 227 } 228 229 let sortFunc = (a, b) => { 230 return comp.compare(a.getAttribute("domain"), b.getAttribute("domain")); 231 }; 232 233 let comp = new Services.intl.Collator(undefined, { 234 usage: "sort", 235 }); 236 237 let items = Array.from(frag.querySelectorAll("richlistitem")); 238 239 if (sortDirection === "descending") { 240 items.sort((a, b) => sortFunc(b, a)); 241 } else { 242 items.sort(sortFunc); 243 } 244 245 // Re-append items in the correct order: 246 items.forEach(item => frag.appendChild(item)); 247 248 let cols = list.previousElementSibling.querySelectorAll("treecol"); 249 cols.forEach(c => { 250 c.removeAttribute("data-isCurrentSortCol"); 251 c.removeAttribute("sortDirection"); 252 }); 253 column.setAttribute("data-isCurrentSortCol", "true"); 254 column.setAttribute("sortDirection", sortDirection); 255 column.setAttribute("data-last-sortDirection", sortDirection); 256 }, 257 258 _setRemoveButtonState() { 259 if (!this._list) { 260 return; 261 } 262 263 if (this._prefLocked) { 264 this._removeAllButton.disabled = true; 265 this._removeButton.disabled = true; 266 return; 267 } 268 269 let hasSelection = this._list.selectedIndex >= 0; 270 271 this._removeButton.disabled = !hasSelection; 272 let disabledItems = this._list.querySelectorAll( 273 "label.website-name-value[disabled='true']" 274 ); 275 276 this._removeAllButton.disabled = 277 this._list.itemCount == disabledItems.length; 278 }, 279 280 onApplyChanges() { 281 if (this._exceptions.size == 0) { 282 Services.prefs.setStringPref("network.trr.excluded-domains", ""); 283 return; 284 } 285 286 let exceptions = Array.from(this._exceptions); 287 let exceptionPrefString = exceptions.join(","); 288 289 Services.prefs.setStringPref( 290 "network.trr.excluded-domains", 291 exceptionPrefString 292 ); 293 }, 294 295 buildExceptionList(sortCol) { 296 // Clear old entries. 297 let oldItems = this._list.querySelectorAll("richlistitem"); 298 for (let item of oldItems) { 299 item.remove(); 300 } 301 let frag = document.createDocumentFragment(); 302 303 let exceptions = Array.from(this._exceptions.values()); 304 305 for (let exception of exceptions) { 306 let richlistitem = this._createExceptionListItem(exception); 307 frag.appendChild(richlistitem); 308 } 309 310 // Sort exceptions. 311 this._sortExceptions(this._list, frag, sortCol); 312 313 this._list.appendChild(frag); 314 315 this._setRemoveButtonState(); 316 }, 317 }; 318 319 document.addEventListener("DOMContentLoaded", () => { 320 gDoHExceptionsManager.init(); 321 });