Bug 1596847 - DoH settings UI for excluded domains, r=fluent-reviewers,settings-reviewers,Gijs,kershaw

Differential Revision: https://phabricator.services.mozilla.com/D173521
This commit is contained in:
Kershaw Chang 2023-04-06 12:57:51 +00:00
parent d735948483
commit 994ea8292e
7 changed files with 415 additions and 0 deletions

View File

@ -0,0 +1,288 @@
/* 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"
);
this._btnAddException.disabled = this._prefLocked;
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();
});

View File

@ -0,0 +1,69 @@
<?xml version="1.0"?>
<!-- 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/. -->
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/preferences/dialogs/sitePermissions.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
<window id="DoHExceptionsDialog"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
data-l10n-id="permissions-exceptions-doh-window"
data-l10n-attrs="title, style"
persist="width height">
<dialog id="exceptionDialog"
buttons="accept,cancel"
data-l10n-id="permission-dialog"
data-l10n-attrs="buttonlabelaccept, buttonaccesskeyaccept">
<linkset>
<html:link rel="localization" href="branding/brand.ftl"/>
<html:link rel="localization" href="browser/preferences/permissions.ftl"/>
</linkset>
<script src="chrome://browser/content/preferences/dialogs/dohExceptions.js"/>
<keyset>
<key data-l10n-id="permissions-close-key" modifiers="accel" oncommand="window.close();"/>
</keyset>
<vbox class="contentPane">
<description id="dohExceptionText" control="url" data-l10n-id="permissions-exceptions-manage-doh-desc"/>
<separator class="thin"/>
<label id="urlLabel" control="url" data-l10n-id="permissions-doh-entry-field"/>
<hbox align="start">
<html:input id="url" type="text"
style="flex: 1;"
oninput="gDoHExceptionsManager.onExceptionInput();"
onkeypress="gDoHExceptionsManager.onExceptionKeyPress(event);"/>
</hbox>
<hbox pack="end">
<button id="btnAddException" disabled="true" data-l10n-id="permissions-doh-add-exception"
oncommand="gDoHExceptionsManager.addException();"/>
</hbox>
<separator class="thin"/>
<listheader>
<treecol id="siteCol" data-l10n-id="permissions-doh-col" style="flex: 3 3 auto; width: 0"
data-isCurrentSortCol = "true"
onclick="gDoHExceptionsManager.buildExceptionList(event.target)"
/>
</listheader>
<richlistbox id="permissionsBox" selected="false"
onkeypress="gDoHExceptionsManager.onListBoxKeyPress(event);"
onselect="gDoHExceptionsManager.onListBoxSelect();"/>
</vbox>
<hbox class="actionButtons">
<button id="removeException" disabled="true"
data-l10n-id="permissions-doh-remove"
oncommand="gDoHExceptionsManager.onExceptionDelete();"/>
<button id="removeAllExceptions"
data-l10n-id="permissions-doh-remove-all"
oncommand="gDoHExceptionsManager.onAllExceptionsDelete();"/>
</hbox>
</dialog>
</window>

View File

@ -19,6 +19,8 @@ browser.jar:
content/browser/preferences/dialogs/colors.js
content/browser/preferences/dialogs/connection.xhtml
content/browser/preferences/dialogs/connection.js
content/browser/preferences/dialogs/dohExceptions.xhtml
content/browser/preferences/dialogs/dohExceptions.js
content/browser/preferences/dialogs/fonts.xhtml
content/browser/preferences/dialogs/fonts.js
content/browser/preferences/dialogs/handlers.css

View File

@ -1215,6 +1215,21 @@
<label class="doh-status-label" id="dohResolver"/>
<label class="doh-status-label" id="dohSteeringStatus" data-l10n-id="preferences-doh-steering-status" hidden="true"/>
</vbox>
<hbox id="dohExceptionBox">
<label flex="1" data-l10n-id="preferences-doh-exceptions-description"/>
<button id="dohExceptionsButton"
is="highlightable-button"
class="accessory-button"
data-l10n-id="preferences-doh-manage-exceptions"
search-l10n-ids="
permissions-doh-entry-field,
permissions-doh-add-exception.label,
permissions-doh-remove.label,
permissions-doh-remove-all.label,
permissions-exceptions-doh-window.title,
permissions-exceptions-manage-doh-desc,
"/>
</hbox>
<vbox>
<label><html:h2 id="dohGroupMessage" data-l10n-id="preferences-doh-group-message"/></label>
<vbox id="dohCategories">

View File

@ -887,6 +887,11 @@ var gPrivacyPane = {
"command",
gPrivacyPane.showHttpsOnlyModeExceptions
);
setEventListener(
"dohExceptionsButton",
"command",
gPrivacyPane.showDoHExceptions
);
setEventListener(
"clearDataSettings",
"command",
@ -2210,6 +2215,13 @@ var gPrivacyPane = {
);
},
showDoHExceptions() {
gSubDialog.open(
"chrome://browser/content/preferences/dialogs/dohExceptions.xhtml",
undefined
);
},
showSiteDataSettings() {
gSubDialog.open(
"chrome://browser/content/preferences/dialogs/siteDataSettings.xhtml"

View File

@ -197,3 +197,26 @@ permissions-site-speaker-window =
.title = Settings - Speaker Permissions
.style = { permissions-window2.style }
permissions-site-speaker-desc = The following websites have requested to select an audio output device. You can specify which websites are allowed to select an audio output device.
permissions-exceptions-doh-window =
.title = Website Exceptions for DNS over HTTPS
.style = { permissions-window2.style }
permissions-exceptions-manage-doh-desc = { -brand-short-name } wont use secure DNS on these sites and their subdomains.
permissions-doh-entry-field = Enter website domain name
.accesskey = d
permissions-doh-add-exception =
.label = Add
.accesskey = A
permissions-doh-col =
.label = Domain
permissions-doh-remove =
.label = Remove
.accesskey = R
permissions-doh-remove-all =
.label = Remove All
.accesskey = e

View File

@ -1530,6 +1530,12 @@ preferences-doh-checkbox-warn =
preferences-doh-select-resolver = Choose provider:
preferences-doh-exceptions-description = { -brand-short-name } wont use secure DNS on these sites
preferences-doh-manage-exceptions =
.label = Manage Exceptions…
.accesskey = x
## The following strings are used in the Download section of settings
desktop-folder-name = Desktop