tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 });