diff options
Diffstat (limited to 'browser/components/preferences')
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); +} |