summaryrefslogtreecommitdiffstats
path: root/browser/components/preferences
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/preferences')
-rw-r--r--browser/components/preferences/preferences.js26
-rw-r--r--browser/components/preferences/preferences.xhtml4
-rw-r--r--browser/components/preferences/privacy.inc.xhtml27
-rw-r--r--browser/components/preferences/privacy.js23
-rw-r--r--browser/components/preferences/sync.inc.xhtml39
-rw-r--r--browser/components/preferences/tests/browser.toml6
-rw-r--r--browser/components/preferences/tests/browser_applications_selection.js20
-rw-r--r--browser/components/preferences/tests/browser_contentblocking.js2
-rw-r--r--browser/components/preferences/tests/browser_privacy_dnsoverhttps.js162
-rw-r--r--browser/components/preferences/tests/browser_subdialogs.js15
-rw-r--r--browser/components/preferences/tests/siteData/browser.toml2
-rw-r--r--browser/components/preferences/tests/siteData/browser_clearSiteData.js41
-rw-r--r--browser/components/preferences/tests/siteData/browser_clearSiteData_v2.js258
-rw-r--r--browser/components/preferences/translations.inc.xhtml54
-rw-r--r--browser/components/preferences/translations.js270
15 files changed, 833 insertions, 116 deletions
diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js
index c30a51c67c..0cd498fd96 100644
--- a/browser/components/preferences/preferences.js
+++ b/browser/components/preferences/preferences.js
@@ -276,22 +276,6 @@ function init_all() {
});
}
-function telemetryBucketForCategory(category) {
- category = category.toLowerCase();
- switch (category) {
- case "containers":
- case "general":
- case "home":
- case "privacy":
- case "search":
- case "sync":
- case "searchresults":
- return category;
- default:
- return "unknown";
- }
-}
-
function onHashChange() {
gotoPref(null, "hash");
}
@@ -454,16 +438,6 @@ function search(aQuery, aAttribute) {
}
element.classList.remove("visually-hidden");
}
-
- let keysets = mainPrefPane.getElementsByTagName("keyset");
- for (let element of keysets) {
- let attributeValue = element.getAttribute(aAttribute);
- if (attributeValue == aQuery) {
- element.removeAttribute("disabled");
- } else {
- element.setAttribute("disabled", true);
- }
- }
}
async function spotlight(subcategory, category) {
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml
index eee227822a..64062f1f77 100644
--- a/browser/components/preferences/preferences.xhtml
+++ b/browser/components/preferences/preferences.xhtml
@@ -85,6 +85,8 @@
<script type="module" src="chrome://global/content/elements/moz-toggle.mjs"/>
<script type="module" src="chrome://global/content/elements/moz-message-bar.mjs" />
<script type="module" src="chrome://global/content/elements/moz-label.mjs"/>
+ <script type="module" src="chrome://global/content/elements/moz-card.mjs"></script>
+ <script type="module" src="chrome://global/content/elements/moz-button.mjs"></script>
</head>
<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
@@ -224,7 +226,7 @@
<hbox class="sticky-inner-container" pack="end" align="start">
<hbox id="policies-container" class="info-box-container smaller-font-size" flex="1" hidden="true">
<hbox class="info-icon-container">
- <html:img class="info-icon"></html:img>
+ <html:img class="info-icon" data-l10n-attrs="alt" data-l10n-id="managed-notice-info-icon"></html:img>
</hbox>
<hbox align="center" flex="1">
<html:a href="about:policies" target="_blank" data-l10n-id="managed-notice"/>
diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml
index 224a5f5cbb..5c45771bc2 100644
--- a/browser/components/preferences/privacy.inc.xhtml
+++ b/browser/components/preferences/privacy.inc.xhtml
@@ -1182,21 +1182,18 @@
<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>
+ <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,
+ "/>
<vbox>
<label><html:h2 id="dohGroupMessage" data-l10n-id="preferences-doh-group-message2"/></label>
<vbox id="dohCategories">
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
index 3b07b9cabf..89fed04e21 100644
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -60,6 +60,13 @@ ChromeUtils.defineLazyGetter(this, "AlertsServiceDND", function () {
}
});
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "gParentalControlsService",
+ "@mozilla.org/parental-controls-service;1",
+ "nsIParentalControlsService"
+);
+
XPCOMUtils.defineLazyPreferenceGetter(
this,
"OS_AUTH_ENABLED",
@@ -682,11 +689,14 @@ var gPrivacyPane = {
function computeStatus() {
let mode = Services.dns.currentTrrMode;
- let confirmationState = Services.dns.currentTrrConfirmationState;
if (
mode == Ci.nsIDNSService.MODE_TRRFIRST ||
mode == Ci.nsIDNSService.MODE_TRRONLY
) {
+ if (lazy.gParentalControlsService.parentalControlsEnabled) {
+ return "preferences-doh-status-not-active";
+ }
+ let confirmationState = Services.dns.currentTrrConfirmationState;
switch (confirmationState) {
case Ci.nsIDNSService.CONFIRM_TRYING_OK:
case Ci.nsIDNSService.CONFIRM_OK:
@@ -702,7 +712,16 @@ var gPrivacyPane = {
let errReason = "";
let confirmationStatus = Services.dns.lastConfirmationStatus;
- if (confirmationStatus != Cr.NS_OK) {
+ let mode = Services.dns.currentTrrMode;
+ if (
+ (mode == Ci.nsIDNSService.MODE_TRRFIRST ||
+ mode == Ci.nsIDNSService.MODE_TRRONLY) &&
+ lazy.gParentalControlsService.parentalControlsEnabled
+ ) {
+ errReason = Services.dns.getTRRSkipReasonName(
+ Ci.nsITRRSkipReason.TRR_PARENTAL_CONTROL
+ );
+ } else if (confirmationStatus != Cr.NS_OK) {
errReason = ChromeUtils.getXPCOMErrorName(confirmationStatus);
} else {
errReason = Services.dns.getTRRSkipReasonName(
diff --git a/browser/components/preferences/sync.inc.xhtml b/browser/components/preferences/sync.inc.xhtml
index d3af690b93..492491a369 100644
--- a/browser/components/preferences/sync.inc.xhtml
+++ b/browser/components/preferences/sync.inc.xhtml
@@ -22,7 +22,7 @@
<description id="noFxaDescription" class="description-deemphasized" flex="1" data-l10n-id="sync-signedout-description2"/>
</vbox>
<vbox>
- <image class="fxaSyncIllustration"/>
+ <image class="fxaSyncIllustration" alt=""/>
</vbox>
</hbox>
<hbox id="fxaNoLoginStatus" align="center" flex="1">
@@ -37,6 +37,7 @@
</hbox>
<label class="fxaMobilePromo" data-l10n-id="sync-mobile-promo">
<html:img
+ role="none"
src="chrome://browser/skin/logo-android.svg"
data-l10n-name="android-icon"
class="androidIcon"/>
@@ -44,6 +45,7 @@
data-l10n-name="android-link"
class="fxaMobilePromo-android text-link" target="_blank"/>
<html:img
+ role="none"
src="chrome://browser/skin/logo-ios.svg"
data-l10n-name="ios-icon"
class="iOSIcon"/>
@@ -66,7 +68,8 @@
<image id="openChangeProfileImage"
class="fxaProfileImage actionable"
role="button"
- data-l10n-id="sync-profile-picture"/>
+ data-l10n-attrs="alt"
+ data-l10n-id="sync-profile-picture-with-alt"/>
<vbox flex="1" pack="center">
<hbox flex="1" align="baseline">
<label id="fxaDisplayName" hidden="true">
@@ -88,11 +91,15 @@
<!-- logged in to an unverified account -->
<hbox id="fxaLoginUnverified">
<vbox>
- <image class="fxaProfileImage"/>
+ <image class="fxaProfileImage"
+ data-l10n-attrs="alt"
+ data-l10n-id="sync-profile-picture-account-problem"/>
</vbox>
<vbox flex="1" pack="center">
<hbox align="center">
- <image class="fxaLoginRejectedWarning"/>
+ <image class="fxaLoginRejectedWarning"
+ data-l10n-attrs="alt"
+ data-l10n-id="fxa-login-rejected-warning"/>
<description flex="1"
class="l10nArgsEmailAddress"
data-l10n-id="sync-signedin-unverified"
@@ -112,11 +119,15 @@
<!-- logged in locally but server rejected credentials -->
<hbox id="fxaLoginRejected">
<vbox>
- <image class="fxaProfileImage"/>
+ <image class="fxaProfileImage"
+ data-l10n-attrs="alt"
+ data-l10n-id="sync-profile-picture-account-problem"/>
</vbox>
<vbox flex="1" pack="center">
<hbox align="center">
- <image class="fxaLoginRejectedWarning"/>
+ <image class="fxaLoginRejectedWarning"
+ data-l10n-attrs="alt"
+ data-l10n-id="fxa-login-rejected-warning"/>
<description flex="1"
class="l10nArgsEmailAddress"
data-l10n-id="sync-signedin-login-failure"
@@ -187,35 +198,35 @@
<label data-l10n-id="sync-syncing-across-devices-heading"/>
<html:div class="sync-engines-list">
<html:div engine_preference="services.sync.engine.bookmarks">
- <image class="sync-engine-image sync-engine-bookmarks"/>
+ <image class="sync-engine-image sync-engine-bookmarks" alt=""/>
<label data-l10n-id="sync-currently-syncing-bookmarks"/>
</html:div>
<html:div engine_preference="services.sync.engine.history">
- <image class="sync-engine-image sync-engine-history"/>
+ <image class="sync-engine-image sync-engine-history" alt=""/>
<label data-l10n-id="sync-currently-syncing-history"/>
</html:div>
<html:div engine_preference="services.sync.engine.tabs">
- <image class="sync-engine-image sync-engine-tabs"/>
+ <image class="sync-engine-image sync-engine-tabs" alt=""/>
<label data-l10n-id="sync-currently-syncing-tabs"/>
</html:div>
<html:div engine_preference="services.sync.engine.passwords">
- <image class="sync-engine-image sync-engine-passwords"/>
+ <image class="sync-engine-image sync-engine-passwords" alt=""/>
<label data-l10n-id="sync-currently-syncing-passwords"/>
</html:div>
<html:div engine_preference="services.sync.engine.addresses">
- <image class="sync-engine-image sync-engine-addresses"/>
+ <image class="sync-engine-image sync-engine-addresses" alt=""/>
<label data-l10n-id="sync-currently-syncing-addresses"/>
</html:div>
<html:div engine_preference="services.sync.engine.creditcards">
- <image class="sync-engine-image sync-engine-creditcards"/>
+ <image class="sync-engine-image sync-engine-creditcards" alt=""/>
<label data-l10n-id="sync-currently-syncing-payment-methods"/>
</html:div>
<html:div engine_preference="services.sync.engine.addons">
- <image class="sync-engine-image sync-engine-addons"/>
+ <image class="sync-engine-image sync-engine-addons" alt=""/>
<label data-l10n-id="sync-currently-syncing-addons"/>
</html:div>
<html:div engine_preference="services.sync.engine.prefs">
- <image class="sync-engine-image sync-engine-prefs"/>
+ <image class="sync-engine-image sync-engine-prefs" alt=""/>
<label data-l10n-id="sync-currently-syncing-settings"/>
</html:div>
</html:div>
diff --git a/browser/components/preferences/tests/browser.toml b/browser/components/preferences/tests/browser.toml
index 9e619ce4be..523110dbc9 100644
--- a/browser/components/preferences/tests/browser.toml
+++ b/browser/components/preferences/tests/browser.toml
@@ -10,6 +10,11 @@ support-files = [
"addons/set_homepage.xpi",
"addons/set_newtab.xpi",
]
+skip-if = [
+ "os == 'linux' && os_version == '18.04' && asan", # manifest runs too long
+ "os == 'linux' && os_version == '18.04' && tsan", # manifest runs too long
+ "win11_2009 && asan", # manifest runs too long
+]
["browser_about_settings.js"]
@@ -276,7 +281,6 @@ support-files = [
"subdialog.xhtml",
"subdialog2.xhtml",
]
-fail-if = ["a11y_checks"] # Bug 1854636 clicked label.dialogTitle, vbox#dialogTemplate.dialogOverlay may not be focusable
["browser_sync_chooseWhatToSync.js"]
diff --git a/browser/components/preferences/tests/browser_applications_selection.js b/browser/components/preferences/tests/browser_applications_selection.js
index 683ce76a89..23f0e00af8 100644
--- a/browser/components/preferences/tests/browser_applications_selection.js
+++ b/browser/components/preferences/tests/browser_applications_selection.js
@@ -335,10 +335,12 @@ add_task(async function sortingCheck() {
"Number of items should not change."
);
for (let i = 0; i < siteItems.length - 1; ++i) {
- let aType = siteItems[i].getAttribute("actionDescription").toLowerCase();
- let bType = siteItems[i + 1]
- .getAttribute("actionDescription")
- .toLowerCase();
+ let aType = (
+ siteItems[i].getAttribute("actionDescription") || ""
+ ).toLowerCase();
+ let bType = (
+ siteItems[i + 1].getAttribute("actionDescription") || ""
+ ).toLowerCase();
let result = 0;
if (aType > bType) {
result = 1;
@@ -375,10 +377,12 @@ add_task(async function sortingCheck() {
"Number of items should not change."
);
for (let i = 0; i < siteItems.length - 1; ++i) {
- let aType = siteItems[i].getAttribute("typeDescription").toLowerCase();
- let bType = siteItems[i + 1]
- .getAttribute("typeDescription")
- .toLowerCase();
+ let aType = (
+ siteItems[i].getAttribute("typeDescription") || ""
+ ).toLowerCase();
+ let bType = (
+ siteItems[i + 1].getAttribute("typeDescription") || ""
+ ).toLowerCase();
let result = 0;
if (aType > bType) {
result = 1;
diff --git a/browser/components/preferences/tests/browser_contentblocking.js b/browser/components/preferences/tests/browser_contentblocking.js
index 3d33f2ed7d..c178233a72 100644
--- a/browser/components/preferences/tests/browser_contentblocking.js
+++ b/browser/components/preferences/tests/browser_contentblocking.js
@@ -1021,7 +1021,7 @@ add_task(async function testDisableTPCheckBoxDisablesEmailTP() {
// Verify the checkbox is unchecked after clicking.
is(
tpCheckbox.getAttribute("checked"),
- "",
+ null,
"Tracking protection checkbox is unchecked"
);
diff --git a/browser/components/preferences/tests/browser_privacy_dnsoverhttps.js b/browser/components/preferences/tests/browser_privacy_dnsoverhttps.js
index 48469cfce4..ebe9c41127 100644
--- a/browser/components/preferences/tests/browser_privacy_dnsoverhttps.js
+++ b/browser/components/preferences/tests/browser_privacy_dnsoverhttps.js
@@ -16,6 +16,10 @@ ChromeUtils.defineESModuleGetters(this, {
DoHTestUtils: "resource://testing-common/DoHTestUtils.sys.mjs",
});
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
const TRR_MODE_PREF = "network.trr.mode";
const TRR_URI_PREF = "network.trr.uri";
const TRR_CUSTOM_URI_PREF = "network.trr.custom_uri";
@@ -106,6 +110,164 @@ function waitForPrefObserver(name) {
});
}
+// Mock parental controls service in order to enable it
+let parentalControlsService = {
+ parentalControlsEnabled: true,
+ QueryInterface: ChromeUtils.generateQI(["nsIParentalControlsService"]),
+};
+let mockParentalControlsServiceCid = undefined;
+
+async function setMockParentalControlEnabled(aEnabled) {
+ if (mockParentalControlsServiceCid != undefined) {
+ MockRegistrar.unregister(mockParentalControlsServiceCid);
+ mockParentalControlsServiceCid = undefined;
+ }
+ if (aEnabled) {
+ mockParentalControlsServiceCid = MockRegistrar.register(
+ "@mozilla.org/parental-controls-service;1",
+ parentalControlsService
+ );
+ }
+ Services.dns.reloadParentalControlEnabled();
+}
+
+add_task(async function testParentalControls() {
+ async function withConfiguration(configuration, fn) {
+ info("testParentalControls");
+
+ await resetPrefs();
+ Services.prefs.setIntPref(TRR_MODE_PREF, configuration.trr_mode);
+ await setMockParentalControlEnabled(configuration.parentalControlsState);
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let statusElement = doc.getElementById("dohStatus");
+
+ await TestUtils.waitForCondition(() => {
+ return (
+ document.l10n.getAttributes(statusElement).args.status ==
+ configuration.wait_for_doh_status
+ );
+ });
+
+ await fn({
+ statusElement,
+ });
+
+ gBrowser.removeCurrentTab();
+ await setMockParentalControlEnabled(false);
+ }
+
+ info("Check parental controls disabled, TRR off");
+ await withConfiguration(
+ {
+ parentalControlsState: false,
+ trr_mode: 0,
+ wait_for_doh_status: "Off",
+ },
+ async res => {
+ is(
+ document.l10n.getAttributes(res.statusElement).args.status,
+ "Off",
+ "expecting status off"
+ );
+ }
+ );
+
+ info("Check parental controls enabled, TRR off");
+ await withConfiguration(
+ {
+ parentalControlsState: true,
+ trr_mode: 0,
+ wait_for_doh_status: "Off",
+ },
+ async res => {
+ is(
+ document.l10n.getAttributes(res.statusElement).args.status,
+ "Off",
+ "expecting status off"
+ );
+ }
+ );
+
+ // Enable the rollout.
+ await DoHTestUtils.loadRemoteSettingsConfig({
+ providers: "example",
+ rolloutEnabled: true,
+ steeringEnabled: false,
+ steeringProviders: "",
+ autoDefaultEnabled: false,
+ autoDefaultProviders: "",
+ id: "global",
+ });
+
+ info("Check parental controls disabled, TRR first");
+ await withConfiguration(
+ {
+ parentalControlsState: false,
+ trr_mode: 2,
+ wait_for_doh_status: "Active",
+ },
+ async res => {
+ is(
+ document.l10n.getAttributes(res.statusElement).args.status,
+ "Active",
+ "expecting status active"
+ );
+ }
+ );
+
+ info("Check parental controls enabled, TRR first");
+ await withConfiguration(
+ {
+ parentalControlsState: true,
+ trr_mode: 2,
+ wait_for_doh_status: "Not active (TRR_PARENTAL_CONTROL)",
+ },
+ async res => {
+ is(
+ document.l10n.getAttributes(res.statusElement).args.status,
+ "Not active (TRR_PARENTAL_CONTROL)",
+ "expecting status not active"
+ );
+ }
+ );
+
+ info("Check parental controls disabled, TRR only");
+ await withConfiguration(
+ {
+ parentalControlsState: false,
+ trr_mode: 3,
+ wait_for_doh_status: "Active",
+ },
+ async res => {
+ is(
+ document.l10n.getAttributes(res.statusElement).args.status,
+ "Active",
+ "expecting status active"
+ );
+ }
+ );
+
+ info("Check parental controls enabled, TRR only");
+ await withConfiguration(
+ {
+ parentalControlsState: true,
+ trr_mode: 3,
+ wait_for_doh_status: "Not active (TRR_PARENTAL_CONTROL)",
+ },
+ async res => {
+ is(
+ document.l10n.getAttributes(res.statusElement).args.status,
+ "Not active (TRR_PARENTAL_CONTROL)",
+ "expecting status not active"
+ );
+ }
+ );
+
+ await resetPrefs();
+});
+
async function testWithProperties(props, startTime) {
info(
Date.now() -
diff --git a/browser/components/preferences/tests/browser_subdialogs.js b/browser/components/preferences/tests/browser_subdialogs.js
index 8763ae9146..b604ac0a7f 100644
--- a/browser/components/preferences/tests/browser_subdialogs.js
+++ b/browser/components/preferences/tests/browser_subdialogs.js
@@ -173,7 +173,7 @@ async function close_subdialog_and_test_generic_end_state(
);
Assert.equal(
frame.getAttribute("style"),
- "",
+ null,
"inline styles should be cleared"
);
Assert.equal(
@@ -407,17 +407,29 @@ add_task(async function background_click_should_close_dialog() {
// Clicking on an inactive part of dialog itself should not close the dialog.
// Click the dialog title bar here to make sure nothing happens.
info("clicking the dialog title bar");
+ // We intentionally turn off this a11y check, because the following click
+ // is purposefully targeting a non-interactive element to confirm the opened
+ // dialog won't be dismissed. It is not meant to be interactive and is not
+ // expected to be accessible, therefore this rule check shall be ignored by
+ // a11y_checks suite.
+ AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
BrowserTestUtils.synthesizeMouseAtCenter(
".dialogTitle",
{},
tab.linkedBrowser
);
+ AccessibilityUtils.resetEnv();
// Close the dialog by clicking on the overlay background. Simulate a click
// at point (2,2) instead of (0,0) so we are sure we're clicking on the
// overlay background instead of some boundary condition that a real user
// would never click.
info("clicking the overlay background");
+ // We intentionally turn off this a11y check, because the following click
+ // is purposefully targeting a non-interactive element to dismiss the opened
+ // dialog with a mouse which can be done by assistive technology and keyboard
+ // by pressing `Esc` key, this rule check shall be ignored by a11y_checks.
+ AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
await close_subdialog_and_test_generic_end_state(
tab.linkedBrowser,
function () {
@@ -432,6 +444,7 @@ add_task(async function background_click_should_close_dialog() {
0,
{ runClosingFnOutsideOfContentTask: true }
);
+ AccessibilityUtils.resetEnv();
});
add_task(async function escape_should_close_dialog() {
diff --git a/browser/components/preferences/tests/siteData/browser.toml b/browser/components/preferences/tests/siteData/browser.toml
index 9f4f8306e1..b7e6ba1b6d 100644
--- a/browser/components/preferences/tests/siteData/browser.toml
+++ b/browser/components/preferences/tests/siteData/browser.toml
@@ -10,6 +10,8 @@ support-files = [
["browser_clearSiteData.js"]
+["browser_clearSiteData_v2.js"]
+
["browser_siteData.js"]
["browser_siteData2.js"]
diff --git a/browser/components/preferences/tests/siteData/browser_clearSiteData.js b/browser/components/preferences/tests/siteData/browser_clearSiteData.js
index 7ae1fda453..4924dccfea 100644
--- a/browser/components/preferences/tests/siteData/browser_clearSiteData.js
+++ b/browser/components/preferences/tests/siteData/browser_clearSiteData.js
@@ -7,10 +7,6 @@ const { PermissionTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/PermissionTestUtils.sys.mjs"
);
-let useOldClearHistoryDialog = Services.prefs.getBoolPref(
- "privacy.sanitize.useOldClearHistoryDialog"
-);
-
async function testClearData(clearSiteData, clearCache) {
PermissionTestUtils.add(
TEST_QUOTA_USAGE_ORIGIN,
@@ -64,9 +60,7 @@ async function testClearData(clearSiteData, clearCache) {
let doc = gBrowser.selectedBrowser.contentDocument;
let clearSiteDataButton = doc.getElementById("clearSiteDataButton");
- let url = useOldClearHistoryDialog
- ? "chrome://browser/content/preferences/dialogs/clearSiteData.xhtml"
- : "chrome://browser/content/sanitize_v2.xhtml";
+ let url = "chrome://browser/content/preferences/dialogs/clearSiteData.xhtml";
let dialogOpened = promiseLoadSubDialog(url);
clearSiteDataButton.doCommand();
let dialogWin = await dialogOpened;
@@ -78,10 +72,8 @@ async function testClearData(clearSiteData, clearCache) {
// since we've had cache intermittently changing under our feet.
let [, convertedCacheUnit] = DownloadUtils.convertByteUnits(cacheUsage);
- let cookiesCheckboxId = useOldClearHistoryDialog
- ? "clearSiteData"
- : "cookiesAndStorage";
- let cacheCheckboxId = useOldClearHistoryDialog ? "clearCache" : "cache";
+ let cookiesCheckboxId = "clearSiteData";
+ let cacheCheckboxId = "clearCache";
let clearSiteDataCheckbox =
dialogWin.document.getElementById(cookiesCheckboxId);
let clearCacheCheckbox = dialogWin.document.getElementById(cacheCheckboxId);
@@ -106,28 +98,13 @@ async function testClearData(clearSiteData, clearCache) {
clearSiteDataCheckbox.checked = clearSiteData;
clearCacheCheckbox.checked = clearCache;
- if (!useOldClearHistoryDialog) {
- // The new clear history dialog has a seperate checkbox for site settings
- let siteSettingsCheckbox =
- dialogWin.document.getElementById("siteSettings");
- siteSettingsCheckbox.checked = clearSiteData;
- // select clear everything to match the old dialog boxes behaviour for this test
- let timespanSelection = dialogWin.document.getElementById(
- "sanitizeDurationChoice"
- );
- timespanSelection.value = 0;
- }
// Some additional promises/assertions to wait for
// when deleting site data.
let acceptPromise;
let updatePromise;
let cookiesClearedPromise;
if (clearSiteData) {
- // the new clear history dialog does not have a extra prompt
- // to clear site data after clicking clear
- if (useOldClearHistoryDialog) {
- acceptPromise = BrowserTestUtils.promiseAlertDialogOpen("accept");
- }
+ acceptPromise = BrowserTestUtils.promiseAlertDialogOpen("accept");
updatePromise = promiseSiteDataManagerSitesUpdated();
cookiesClearedPromise = promiseCookiesCleared();
}
@@ -137,7 +114,7 @@ async function testClearData(clearSiteData, clearCache) {
let clearButton = dialogWin.document
.querySelector("dialog")
.getButton("accept");
- if (!clearSiteData && !clearCache && useOldClearHistoryDialog) {
+ if (!clearSiteData && !clearCache) {
// Simulate user input on one of the checkboxes to trigger the event listener for
// disabling the clearButton.
clearCacheCheckbox.doCommand();
@@ -158,7 +135,7 @@ async function testClearData(clearSiteData, clearCache) {
// For site data we display an extra warning dialog, make sure
// to accept it.
- if (clearSiteData && useOldClearHistoryDialog) {
+ if (clearSiteData) {
await acceptPromise;
}
@@ -222,6 +199,12 @@ async function testClearData(clearSiteData, clearCache) {
await SiteDataManager.removeAll();
}
+add_setup(function () {
+ SpecialPowers.pushPrefEnv({
+ set: [["privacy.sanitize.useOldClearHistoryDialog", true]],
+ });
+});
+
// Test opening the "Clear All Data" dialog and cancelling.
add_task(async function () {
await testClearData(false, false);
diff --git a/browser/components/preferences/tests/siteData/browser_clearSiteData_v2.js b/browser/components/preferences/tests/siteData/browser_clearSiteData_v2.js
new file mode 100644
index 0000000000..8cb8be25b3
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/browser_clearSiteData_v2.js
@@ -0,0 +1,258 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+async function testClearData(clearSiteData, clearCache) {
+ PermissionTestUtils.add(
+ TEST_QUOTA_USAGE_ORIGIN,
+ "persistent-storage",
+ Services.perms.ALLOW_ACTION
+ );
+
+ // Open a test site which saves into appcache.
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_OFFLINE_URL);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // Fill indexedDB with test data.
+ // Don't wait for the page to load, to register the content event handler as quickly as possible.
+ // If this test goes intermittent, we might have to tell the page to wait longer before
+ // firing the event.
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_QUOTA_USAGE_URL, false);
+ await BrowserTestUtils.waitForContentEvent(
+ gBrowser.selectedBrowser,
+ "test-indexedDB-done",
+ false,
+ null,
+ true
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // Register some service workers.
+ await loadServiceWorkerTestPage(TEST_SERVICE_WORKER_URL);
+ await promiseServiceWorkerRegisteredFor(TEST_SERVICE_WORKER_URL);
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ // Test the initial states.
+ let cacheUsage = await SiteDataManager.getCacheSize();
+ let quotaUsage = await SiteDataTestUtils.getQuotaUsage(
+ TEST_QUOTA_USAGE_ORIGIN
+ );
+ let totalUsage = await SiteDataManager.getTotalUsage();
+ Assert.greater(cacheUsage, 0, "The cache usage should not be 0");
+ Assert.greater(quotaUsage, 0, "The quota usage should not be 0");
+ Assert.greater(totalUsage, 0, "The total usage should not be 0");
+
+ let initialSizeLabelValue = await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let sizeLabel = content.document.getElementById("totalSiteDataSize");
+ return sizeLabel.textContent;
+ }
+ );
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let clearSiteDataButton = doc.getElementById("clearSiteDataButton");
+
+ let url = "chrome://browser/content/sanitize_v2.xhtml";
+ let dialogOpened = promiseLoadSubDialog(url);
+ clearSiteDataButton.doCommand();
+ let dialogWin = await dialogOpened;
+
+ // Convert the usage numbers in the same way the UI does it to assert
+ // that they're displayed in the dialog.
+ let [convertedTotalUsage] = DownloadUtils.convertByteUnits(totalUsage);
+ // For cache we just assert that the right unit (KB, probably) is displayed,
+ // since we've had cache intermittently changing under our feet.
+ let [, convertedCacheUnit] = DownloadUtils.convertByteUnits(cacheUsage);
+
+ let cookiesCheckboxId = "cookiesAndStorage";
+ let cacheCheckboxId = "cache";
+ let clearSiteDataCheckbox =
+ dialogWin.document.getElementById(cookiesCheckboxId);
+ let clearCacheCheckbox = dialogWin.document.getElementById(cacheCheckboxId);
+ // The usage details are filled asynchronously, so we assert that they're present by
+ // waiting for them to be filled in.
+ await Promise.all([
+ TestUtils.waitForCondition(
+ () =>
+ clearSiteDataCheckbox.label &&
+ clearSiteDataCheckbox.label.includes(convertedTotalUsage),
+ "Should show the quota usage"
+ ),
+ TestUtils.waitForCondition(
+ () =>
+ clearCacheCheckbox.label &&
+ clearCacheCheckbox.label.includes(convertedCacheUnit),
+ "Should show the cache usage"
+ ),
+ ]);
+
+ // Check the boxes according to our test input.
+ clearSiteDataCheckbox.checked = clearSiteData;
+ clearCacheCheckbox.checked = clearCache;
+
+ // select clear everything to match the old dialog boxes behaviour for this test
+ let timespanSelection = dialogWin.document.getElementById(
+ "sanitizeDurationChoice"
+ );
+ timespanSelection.value = 1;
+
+ // Some additional promises/assertions to wait for
+ // when deleting site data.
+ let updatePromise;
+ if (clearSiteData) {
+ // the new clear history dialog does not have a extra prompt
+ // to clear site data after clicking clear
+ updatePromise = promiseSiteDataManagerSitesUpdated();
+ }
+
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialogWin, "unload");
+
+ let clearButton = dialogWin.document
+ .querySelector("dialog")
+ .getButton("accept");
+ let cancelButton = dialogWin.document
+ .querySelector("dialog")
+ .getButton("cancel");
+
+ if (!clearSiteData && !clearCache) {
+ // Cancel, since we can't delete anything.
+ cancelButton.click();
+ } else {
+ // Delete stuff!
+ clearButton.click();
+ }
+
+ await dialogClosed;
+
+ if (clearCache) {
+ TestUtils.waitForCondition(async function () {
+ let usage = await SiteDataManager.getCacheSize();
+ return usage == 0;
+ }, "The cache usage should be removed");
+ } else {
+ Assert.greater(
+ await SiteDataManager.getCacheSize(),
+ 0,
+ "The cache usage should not be 0"
+ );
+ }
+
+ if (clearSiteData) {
+ await updatePromise;
+ await promiseServiceWorkersCleared();
+
+ TestUtils.waitForCondition(async function () {
+ let usage = await SiteDataManager.getTotalUsage();
+ return usage == 0;
+ }, "The total usage should be removed");
+ } else {
+ quotaUsage = await SiteDataTestUtils.getQuotaUsage(TEST_QUOTA_USAGE_ORIGIN);
+ totalUsage = await SiteDataManager.getTotalUsage();
+ Assert.greater(quotaUsage, 0, "The quota usage should not be 0");
+ Assert.greater(totalUsage, 0, "The total usage should not be 0");
+ }
+
+ if (clearCache || clearSiteData) {
+ // Check that the size label in about:preferences updates after we cleared data.
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [{ initialSizeLabelValue }],
+ async function (opts) {
+ let sizeLabel = content.document.getElementById("totalSiteDataSize");
+ await ContentTaskUtils.waitForCondition(
+ () => sizeLabel.textContent != opts.initialSizeLabelValue,
+ "Site data size label should have updated."
+ );
+ }
+ );
+ }
+
+ let permission = PermissionTestUtils.getPermissionObject(
+ TEST_QUOTA_USAGE_ORIGIN,
+ "persistent-storage"
+ );
+ is(
+ clearSiteData ? permission : permission.capability,
+ clearSiteData ? null : Services.perms.ALLOW_ACTION,
+ "Should have the correct permission state."
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await SiteDataManager.removeAll();
+}
+
+add_setup(function () {
+ SpecialPowers.pushPrefEnv({
+ set: [["privacy.sanitize.useOldClearHistoryDialog", false]],
+ });
+
+ // The tests in this file all test specific interactions with the new clear
+ // history dialog and can't be split up.
+ requestLongerTimeout(2);
+});
+
+// Test opening the "Clear All Data" dialog and cancelling.
+add_task(async function testNoSiteDataNoCacheClearing() {
+ await testClearData(false, false);
+});
+
+// Test opening the "Clear All Data" dialog and removing all site data.
+add_task(async function testSiteDataClearing() {
+ await testClearData(true, false);
+});
+
+// Test opening the "Clear All Data" dialog and removing all cache.
+add_task(async function testCacheClearing() {
+ await testClearData(false, true);
+});
+
+// Test opening the "Clear All Data" dialog and removing everything.
+add_task(async function testSiteDataAndCacheClearing() {
+ await testClearData(true, true);
+});
+
+// Test clearing persistent storage
+add_task(async function testPersistentStorage() {
+ PermissionTestUtils.add(
+ TEST_QUOTA_USAGE_ORIGIN,
+ "persistent-storage",
+ Services.perms.ALLOW_ACTION
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let clearSiteDataButton = doc.getElementById("clearSiteDataButton");
+
+ let url = "chrome://browser/content/sanitize_v2.xhtml";
+ let dialogOpened = promiseLoadSubDialog(url);
+ clearSiteDataButton.doCommand();
+ let dialogWin = await dialogOpened;
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialogWin, "unload");
+
+ let timespanSelection = dialogWin.document.getElementById(
+ "sanitizeDurationChoice"
+ );
+ timespanSelection.value = 1;
+ let clearButton = dialogWin.document
+ .querySelector("dialog")
+ .getButton("accept");
+ clearButton.click();
+ await dialogClosed;
+
+ let permission = PermissionTestUtils.getPermissionObject(
+ TEST_QUOTA_USAGE_ORIGIN,
+ "persistent-storage"
+ );
+ is(permission, null, "Should have the correct permission state.");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/translations.inc.xhtml b/browser/components/preferences/translations.inc.xhtml
index 9463fde707..1143cbc6d0 100644
--- a/browser/components/preferences/translations.inc.xhtml
+++ b/browser/components/preferences/translations.inc.xhtml
@@ -19,45 +19,63 @@
<p id="translations-settings-description" data-l10n-id="translations-settings-description"/>
- <div class="translations-settings-manage-list"
- id="translations-settings-manage-always-translate-list">
+ <moz-card class="translations-settings-manage-section" data-l10n-attrs="heading"
+ id="translations-settings-always-translate-section">
<div class="translations-settings-manage-language">
<h2 id="translations-settings-always-translate" data-l10n-id="translations-settings-always-translate"/>
<xul:menulist id="translations-settings-always-translate-list"
- data-l10n-id="translations-settings-add-language-button">
- <xul:menupopup/>
+ data-l10n-id="translations-settings-add-language-button"
+ aria-labelledby="translations-settings-always-translate">
+ <!-- The list of <menuitem> will be dynamically inserted. -->
+ <xul:menupopup id="translations-settings-always-translate-popup"/>
</xul:menulist>
</div>
- </div>
+ </moz-card>
- <div id="translations-settings-manage-never-translate-list"
- class="translations-settings-manage-list">
+ <moz-card id="translations-settings-never-translate-section"
+ class="translations-settings-manage-section">
<div class="translations-settings-manage-language">
<h2 id="translations-settings-never-translate" data-l10n-id="translations-settings-never-translate"/>
<xul:menulist id="translations-settings-never-translate-list"
- data-l10n-id="translations-settings-add-language-button">
- <xul:menupopup/>
+ data-l10n-id="translations-settings-add-language-button"
+ aria-labelledby="translations-settings-never-translate">
+ <!-- The list of <menuitem> will be dynamically inserted. -->
+ <xul:menupopup id="translations-settings-never-translate-popup"/>
</xul:menulist>
</div>
- </div>
+ </moz-card>
- <div id="translations-settings-never-sites-list" class="translations-settings-manage-list" >
- <div class="translations-settings-manage-list-info" >
+ <moz-card id="translations-settings-never-sites-section"
+ class="translations-settings-manage-section">
+ <div class="translations-settings-manage-section-info" >
<h2 id="translations-settings-never-sites-header"
data-l10n-id="translations-settings-never-sites-header"/>
<p id="translations-settings-never-sites"
data-l10n-id="translations-settings-never-sites-description"/>
</div>
- </div>
+ </moz-card>
- <div id="translations-settings-manage-install-list" class="translations-settings-manage-list">
- <div class="translations-settings-manage-list-info">
- <h2 id="translations-settings-download-languages"
- data-l10n-id="translations-settings-download-languages"/>
+ <moz-card id="translations-settings-download-section"
+ class="translations-settings-manage-section">
+ <div class="translations-settings-manage-section-info">
+ <h2 data-l10n-id="translations-settings-download-languages"/>
<a is="moz-support-link" class="learnMore"
id="download-languages-learn-more"
data-l10n-id="translations-settings-download-languages-link"
support-page="website-translation"/>
</div>
- </div>
+ <div class="translations-settings-languages-card">
+ <h3 class="translations-settings-language-header" data-l10n-id="translations-settings-language-header"></h3>
+ <div class="translations-settings-language-list">
+ <div class="translations-settings-language">
+ <moz-button class="translations-settings-download-icon" type="ghost icon"
+ aria-label="translations-settings-download-all-languages"></moz-button>
+ <!-- The option to "All languages" is added here.
+ In translations.js the option to download individual languages is
+ added dynamically based on the supported language list -->
+ <label id="translations-settings-download-all-languages" data-l10n-id="translations-settings-download-all-languages"></label>
+ </div>
+ </div>
+ </div>
+ </moz-card>
</div>
diff --git a/browser/components/preferences/translations.js b/browser/components/preferences/translations.js
index c9cfe472ac..1bfa021d59 100644
--- a/browser/components/preferences/translations.js
+++ b/browser/components/preferences/translations.js
@@ -4,6 +4,10 @@
/* import-globals-from preferences.js */
+ChromeUtils.defineESModuleGetters(this, {
+ TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
+});
+
let gTranslationsPane = {
init() {
document
@@ -11,5 +15,271 @@ let gTranslationsPane = {
.addEventListener("click", function () {
gotoPref("general");
});
+
+ document
+ .getElementById("translations-settings-always-translate-list")
+ .addEventListener("command", this.addAlwaysLanguage);
+
+ document
+ .getElementById("translations-settings-never-translate-list")
+ .addEventListener("command", this.addNeverLanguage);
+
+ this.buildLanguageDropDowns();
+ this.buildDownloadLanguageList();
+ },
+
+ /**
+ * Populate the Drop down list with the list of supported languages
+ * for the user to choose languages to add to Always translate and
+ * Never translate settings list.
+ */
+ async buildLanguageDropDowns() {
+ const { fromLanguages } = await TranslationsParent.getSupportedLanguages();
+ let alwaysLangPopup = document.getElementById(
+ "translations-settings-always-translate-popup"
+ );
+ let neverLangPopup = document.getElementById(
+ "translations-settings-never-translate-popup"
+ );
+
+ for (const { langTag, displayName } of fromLanguages) {
+ const alwaysLang = document.createXULElement("menuitem");
+ alwaysLang.setAttribute("value", langTag);
+ alwaysLang.setAttribute("label", displayName);
+ alwaysLangPopup.appendChild(alwaysLang);
+ const neverLang = document.createXULElement("menuitem");
+ neverLang.setAttribute("value", langTag);
+ neverLang.setAttribute("label", displayName);
+ neverLangPopup.appendChild(neverLang);
+ }
+ },
+
+ /**
+ * Show a list of languages for the user to be able to install
+ * and uninstall language models for local translation.
+ */
+ async buildDownloadLanguageList() {
+ const supportedLanguages = await TranslationsParent.getSupportedLanguages();
+ const languageList = TranslationsParent.getLanguageList(supportedLanguages);
+
+ let installList = document.querySelector(
+ ".translations-settings-language-list"
+ );
+
+ // The option to download "All languages" is added in xhtml.
+ // Here the option to download individual languages is dynamically added
+ // based on the supported language list
+ installList
+ .querySelector("moz-button")
+ .addEventListener("click", installLanguage);
+
+ for (const language of languageList) {
+ const languageElement = document.createElement("div");
+ languageElement.classList.add("translations-settings-language");
+
+ const languageLabel = document.createElement("label");
+ languageLabel.textContent = language.displayName;
+ languageLabel.setAttribute("value", language.langTag);
+ // Using the language tag suffix to create unique id for each language
+ languageLabel.id = "translations-settings-download-" + language.langTag;
+
+ const installButton = document.createElement("moz-button");
+ installButton.classList.add("translations-settings-download-icon");
+ installButton.setAttribute("type", "ghost icon");
+ installButton.addEventListener("click", installLanguage);
+ installButton.setAttribute("aria-label", languageLabel.id);
+
+ languageElement.appendChild(installButton);
+ languageElement.appendChild(languageLabel);
+ installList.appendChild(languageElement);
+ }
+ },
+
+ /**
+ * Event handler when the user wants to add a language to
+ * Always translate settings list.
+ */
+ addAlwaysLanguage(event) {
+ /* TODO:
+ The function addLanguage adds the HTML element.
+ It will be moved to the observer in the next Bug - 1881259 .
+ It is here just to test the UI.
+ For now a language can be added multiple times.
+
+ In the next bug we will maintain a local state of preferences.
+ When a language is added or removed, the user event updates the preferences in the Services,
+ This triggers the observer which compares the preferences in the local state and the
+ preferences in the Services and adds or removes the language in the local state and updates the
+ UI to reflect the updated Preferences in the Services.
+ */
+ addLanguage(
+ event,
+ "translations-settings-always-translate-section",
+ deleteAlwaysLanguage
+ );
+ },
+
+ /**
+ * Event handler when the user wants to add a language to
+ * Never translate settings list.
+ */
+ addNeverLanguage(event) {
+ /* TODO:
+ The function addLanguage adds the HTML element.
+ It will be moved to the observer in the next Bug - 1881259 .
+ It is here just to test the UI.
+ For now a language can be added multiple times.
+
+ In the next bug we will maintain a local state of preferences.
+ When a language is added or removed, the user event updates the preferences in the Services,
+ This triggers the observer which compares the preferences in the local state and the
+ preferences in the Services and adds or removes the language in the local state and updates the
+ UI to reflect the updated Preferences in the Services.
+ */
+ addLanguage(
+ event,
+ "translations-settings-never-translate-section",
+ deleteNeverLanguage
+ );
},
};
+
+/**
+ * Function to add a language selected by the user to the list of
+ * Always/Never translate settings list.
+ */
+async function addLanguage(event, listClass, delHandler) {
+ const translatePrefix =
+ listClass === "translations-settings-never-translate-section"
+ ? "never"
+ : "always";
+ let translateSection = document.getElementById(listClass);
+ let languageList = translateSection.querySelector(
+ ".translations-settings-language-list"
+ );
+
+ // While adding the first language, add the Header and language List div
+ if (!languageList) {
+ let languageCard = document.createElement("div");
+ languageCard.classList.add("translations-settings-languages-card");
+ translateSection.appendChild(languageCard);
+
+ let languageHeader = document.createElement("h3");
+ languageCard.appendChild(languageHeader);
+ languageHeader.setAttribute(
+ "data-l10n-id",
+ "translations-settings-language-header"
+ );
+ languageHeader.classList.add("translations-settings-language-header");
+
+ languageList = document.createElement("div");
+ languageList.classList.add("translations-settings-language-list");
+ languageCard.appendChild(languageList);
+ }
+ const languageElement = document.createElement("div");
+ languageElement.classList.add("translations-settings-language");
+ // Add the language after the Language Header
+ languageList.insertBefore(languageElement, languageList.firstChild);
+
+ const languageLabel = document.createElement("label");
+ languageLabel.textContent = event.target.getAttribute("label");
+ languageLabel.setAttribute("value", event.target.getAttribute("value"));
+ // Using the language tag suffix to create unique id for each language
+ // add prefix for the always/never translate
+ languageLabel.id =
+ "translations-settings-language-" +
+ translatePrefix +
+ "-" +
+ event.target.getAttribute("value");
+
+ const delButton = document.createElement("moz-button");
+ delButton.classList.add("translations-settings-delete-icon");
+ delButton.setAttribute("type", "ghost icon");
+ delButton.addEventListener("click", delHandler);
+ delButton.setAttribute("aria-label", languageLabel.id);
+
+ languageElement.appendChild(delButton);
+ languageElement.appendChild(languageLabel);
+
+ /* After a language is selected the menulist button display will be set to the
+ selected langauge. After processing the button event the
+ data-l10n-id of the menulist button is restored to "Add Language" */
+ const menuList = translateSection.querySelector("menulist");
+ await document.l10n.translateElements([menuList]);
+}
+
+/**
+ * Event Handler to delete a language selected by the user from the list of
+ * Always translate settings list.
+ */
+function deleteAlwaysLanguage(event) {
+ /* TODO:
+ The function removeLanguage removes the HTML element.
+ It will be moved to the observer in the next Bug - 1881259 .
+ It is here just to test the UI.
+
+ In the next bug we will maintain a local state of preferences.
+ When a language is added or removed, the user event updates the preferences in the Services,
+ This triggers the observer which compares the preferences in the local state and the
+ preferences in the Services and adds or removes the language in the local state and updates the
+ UI to reflect the updated Preferences in the Services.
+ */
+ removeLanguage(event);
+}
+
+/**
+ * Event Handler to delete a language selected by the user from the list of
+ * Never translate settings list.
+ */
+function deleteNeverLanguage(event) {
+ /* TODO:
+ The function removeLanguage removes the HTML element.
+ It will be moved to the observer in the next Bug - 1881259 .
+ It is here just to test the UI.
+
+ In the next bug we will maintain a local state of preferences.
+ When a language is added or removed, the user event updates the preferences in the Services,
+ This triggers the observer which compares the preferences in the local state and the
+ preferences in the Services and adds or removes the language in the local state and updates the
+ UI to reflect the updated Preferences in the Services.
+ */
+ removeLanguage(event);
+}
+
+/**
+ * Function to delete a language selected by the user from the list of
+ * Always/Never translate settings list.
+ */
+function removeLanguage(event) {
+ /* Langauge section moz-card -parent of-> Language card -parent of->
+ Language heading and Language list -parent of->
+ Language Element -parent of-> language button and label
+ */
+ let languageCard = event.target.parentNode.parentNode.parentNode;
+ event.target.parentNode.remove();
+ if (languageCard.children[1].childElementCount === 0) {
+ // If there is no language in the list remove the
+ // Language Header and language list div
+ languageCard.remove();
+ }
+}
+
+/**
+ * Event Handler to install a language model selected by the user
+ */
+function installLanguage(event) {
+ event.target.classList.remove("translations-settings-download-icon");
+ event.target.classList.add("translations-settings-delete-icon");
+ event.target.removeEventListener("click", installLanguage);
+ event.target.addEventListener("click", unInstallLanguage);
+}
+
+/**
+ * Event Handler to install a language model selected by the user
+ */
+function unInstallLanguage(event) {
+ event.target.classList.remove("translations-settings-delete-icon");
+ event.target.classList.add("translations-settings-download-icon");
+ event.target.removeEventListener("click", unInstallLanguage);
+ event.target.addEventListener("click", installLanguage);
+}