diff options
Diffstat (limited to 'browser/components/preferences')
45 files changed, 1137 insertions, 630 deletions
diff --git a/browser/components/preferences/containers.js b/browser/components/preferences/containers.js index 80d1ec7cc3..171fd47ce1 100644 --- a/browser/components/preferences/containers.js +++ b/browser/components/preferences/containers.js @@ -123,7 +123,7 @@ let gContainersPane = { this.openPreferenceDialog(button.getAttribute("value")); }, - onAddButtonCommand(button) { + onAddButtonCommand() { this.openPreferenceDialog(null); }, diff --git a/browser/components/preferences/dialogs/addEngine.js b/browser/components/preferences/dialogs/addEngine.js index 1faf8622b3..6b9d8f663b 100644 --- a/browser/components/preferences/dialogs/addEngine.js +++ b/browser/components/preferences/dialogs/addEngine.js @@ -26,7 +26,7 @@ let gAddEngineDialog = { document.addEventListener("dialogaccept", this.onAddEngine.bind(this)); }, - async onAddEngine(event) { + async onAddEngine() { let url = document .getElementById("engineUrl") .value.replace(/%s/, "{searchTerms}"); diff --git a/browser/components/preferences/dialogs/blocklists.js b/browser/components/preferences/dialogs/blocklists.js index c28ee09f96..89e21c476f 100644 --- a/browser/components/preferences/dialogs/blocklists.js +++ b/browser/components/preferences/dialogs/blocklists.js @@ -26,28 +26,28 @@ var gBlocklistManager = { return ""; }, - isSeparator(index) { + isSeparator() { return false; }, isSorted() { return false; }, - isContainer(index) { + isContainer() { return false; }, - setTree(tree) {}, - getImageSrc(row, column) {}, + setTree() {}, + getImageSrc() {}, getCellValue(row, column) { if (column.id == "selectionCol") { return gBlocklistManager._blockLists[row].selected; } return undefined; }, - cycleHeader(column) {}, - getRowProperties(row) { + cycleHeader() {}, + getRowProperties() { return ""; }, - getColumnProperties(column) { + getColumnProperties() { return ""; }, getCellProperties(row, column) { diff --git a/browser/components/preferences/dialogs/clearSiteData.js b/browser/components/preferences/dialogs/clearSiteData.js index 061534b52a..664ad114fa 100644 --- a/browser/components/preferences/dialogs/clearSiteData.js +++ b/browser/components/preferences/dialogs/clearSiteData.js @@ -58,7 +58,7 @@ var gClearSiteDataDialog = { ); }, - onCheckboxCommand(event) { + onCheckboxCommand() { this._dialog.setAttribute( "buttondisabledaccept", !(this._clearSiteDataCheckbox.checked || this._clearCacheCheckbox.checked) diff --git a/browser/components/preferences/dialogs/colors.xhtml b/browser/components/preferences/dialogs/colors.xhtml index 1720cf3498..3181dd5c6d 100644 --- a/browser/components/preferences/dialogs/colors.xhtml +++ b/browser/components/preferences/dialogs/colors.xhtml @@ -41,7 +41,11 @@ <hbox> <groupbox flex="1"> - <label><html:h2 data-l10n-id="colors-text-and-background" /></label> + <label + ><html:h2 + class="heading-medium" + data-l10n-id="colors-text-and-background" + /></label> <hbox align="center"> <label data-l10n-id="colors-text-header" @@ -74,7 +78,9 @@ </groupbox> <groupbox flex="1"> - <label><html:h2 data-l10n-id="colors-links-header" /></label> + <label + ><html:h2 class="heading-medium" data-l10n-id="colors-links-header" + /></label> <hbox align="center"> <label data-l10n-id="colors-unvisited-links" diff --git a/browser/components/preferences/dialogs/connection.js b/browser/components/preferences/dialogs/connection.js index 0b21b1b5a5..33e8deb279 100644 --- a/browser/components/preferences/dialogs/connection.js +++ b/browser/components/preferences/dialogs/connection.js @@ -16,6 +16,7 @@ Preferences.addAll([ // both initialized when network.proxy.type initialization triggers a call to // gConnectionsDialog.updateReloadButton(). { id: "network.proxy.autoconfig_url", type: "string" }, + { id: "network.proxy.system_wpad", type: "bool" }, { id: "network.proxy.type", type: "int" }, { id: "network.proxy.http", type: "string" }, { id: "network.proxy.http_port", type: "int" }, @@ -130,11 +131,20 @@ var gConnectionsDialog = { checkForSystemProxy() { if ("@mozilla.org/system-proxy-settings;1" in Cc) { document.getElementById("systemPref").removeAttribute("hidden"); + + var systemWpadAllowed = Preferences.get( + "network.proxy.system_wpad.allowed" + ); + if (systemWpadAllowed && Services.appinfo.OS == "WINNT") { + document.getElementById("systemWpad").removeAttribute("hidden"); + } } }, proxyTypeChanged() { var proxyTypePref = Preferences.get("network.proxy.type"); + var systemWpadPref = Preferences.get("network.proxy.system_wpad"); + systemWpadPref.updateControlDisabledState(proxyTypePref.value != 5); // Update http var httpProxyURLPref = Preferences.get("network.proxy.http"); diff --git a/browser/components/preferences/dialogs/connection.xhtml b/browser/components/preferences/dialogs/connection.xhtml index 2a1a3c0115..bb9a932033 100644 --- a/browser/components/preferences/dialogs/connection.xhtml +++ b/browser/components/preferences/dialogs/connection.xhtml @@ -63,7 +63,11 @@ </hbox> <groupbox> - <label><html:h2 data-l10n-id="connection-proxy-configure" /></label> + <label + ><html:h2 + class="heading-medium" + data-l10n-id="connection-proxy-configure" + /></label> <radiogroup id="networkProxyType" preference="network.proxy.type"> <radio value="0" data-l10n-id="connection-proxy-option-no" /> @@ -74,6 +78,14 @@ id="systemPref" hidden="true" /> + <checkbox + value="true" + data-l10n-id="connection-proxy-option-wpad" + id="systemWpad" + hidden="true" + preference="network.proxy.system_wpad" + class="indent" + /> <radio value="1" data-l10n-id="connection-proxy-option-manual" /> <box id="proxy-grid" class="indent" flex="1"> <html:div class="proxy-grid-row"> diff --git a/browser/components/preferences/dialogs/containers.js b/browser/components/preferences/dialogs/containers.js index 14526545b6..87368689a0 100644 --- a/browser/components/preferences/dialogs/containers.js +++ b/browser/components/preferences/dialogs/containers.js @@ -88,7 +88,7 @@ let gContainersManager = { this._dialog.setAttribute("buttondisabledaccept", !name.value.trim()); }, - createIconButtons(defaultIcon) { + createIconButtons() { let radiogroup = document.createXULElement("radiogroup"); radiogroup.setAttribute("id", "icon"); radiogroup.className = "icon-buttons radio-buttons"; @@ -116,7 +116,7 @@ let gContainersManager = { return radiogroup; }, - createColorSwatches(defaultColor) { + createColorSwatches() { let radiogroup = document.createXULElement("radiogroup"); radiogroup.setAttribute("id", "color"); radiogroup.className = "radio-buttons"; diff --git a/browser/components/preferences/dialogs/dohExceptions.js b/browser/components/preferences/dialogs/dohExceptions.js index f4c1d4d80d..9bf64bd4ed 100644 --- a/browser/components/preferences/dialogs/dohExceptions.js +++ b/browser/components/preferences/dialogs/dohExceptions.js @@ -31,7 +31,6 @@ var gDoHExceptionsManager = { "network.trr.excluded-domains" ); - this._btnAddException.disabled = this._prefLocked; document.getElementById("exceptionDialog").getButton("accept").disabled = this._prefLocked; this._urlField.disabled = this._prefLocked; diff --git a/browser/components/preferences/dialogs/fonts.xhtml b/browser/components/preferences/dialogs/fonts.xhtml index 44dc473ccb..cd35371324 100644 --- a/browser/components/preferences/dialogs/fonts.xhtml +++ b/browser/components/preferences/dialogs/fonts.xhtml @@ -43,7 +43,9 @@ <groupbox> <hbox align="center"> <label control="selectLangs" - ><html:h2 data-l10n-id="fonts-langgroup-header" + ><html:h2 + class="heading-medium" + data-l10n-id="fonts-langgroup-header" /></label> </hbox> <menulist id="selectLangs" preference="font.language.group"> diff --git a/browser/components/preferences/dialogs/translationExceptions.js b/browser/components/preferences/dialogs/translationExceptions.js index 27579594c9..a83f7a3de1 100644 --- a/browser/components/preferences/dialogs/translationExceptions.js +++ b/browser/components/preferences/dialogs/translationExceptions.js @@ -46,29 +46,29 @@ Tree.prototype = { get rowCount() { return this._data.length; }, - getCellText(aRow, aColumn) { + getCellText(aRow) { return this._data[aRow]; }, - isSeparator(aIndex) { + isSeparator() { return false; }, isSorted() { return false; }, - isContainer(aIndex) { + isContainer() { return false; }, - setTree(aTree) {}, - getImageSrc(aRow, aColumn) {}, - getCellValue(aRow, aColumn) {}, - cycleHeader(column) {}, - getRowProperties(row) { + setTree() {}, + getImageSrc() {}, + getCellValue() {}, + cycleHeader() {}, + getRowProperties() { return ""; }, - getColumnProperties(column) { + getColumnProperties() { return ""; }, - getCellProperties(row, column) { + getCellProperties() { return ""; }, QueryInterface: ChromeUtils.generateQI(["nsITreeView"]), diff --git a/browser/components/preferences/dialogs/translations.js b/browser/components/preferences/dialogs/translations.js index 826e6efb4b..7af2412b44 100644 --- a/browser/components/preferences/dialogs/translations.js +++ b/browser/components/preferences/dialogs/translations.js @@ -60,29 +60,29 @@ Tree.prototype = { get rowCount() { return this._data.length; }, - getCellText(aRow, aColumn) { + getCellText(aRow) { return this._data[aRow]; }, - isSeparator(aIndex) { + isSeparator() { return false; }, isSorted() { return false; }, - isContainer(aIndex) { + isContainer() { return false; }, - setTree(aTree) {}, - getImageSrc(aRow, aColumn) {}, - getCellValue(aRow, aColumn) {}, - cycleHeader(column) {}, - getRowProperties(row) { + setTree() {}, + getImageSrc() {}, + getCellValue() {}, + cycleHeader() {}, + getRowProperties() { return ""; }, - getColumnProperties(column) { + getColumnProperties() { return ""; }, - getCellProperties(row, column) { + getCellProperties() { return ""; }, QueryInterface: ChromeUtils.generateQI(["nsITreeView"]), diff --git a/browser/components/preferences/findInPage.js b/browser/components/preferences/findInPage.js index 9fcd7b629b..7ee0a0bc06 100644 --- a/browser/components/preferences/findInPage.js +++ b/browser/components/preferences/findInPage.js @@ -117,9 +117,9 @@ var gSearchResultsPane = { }, /** - * Finds and returns text nodes within node and all descendants - * Iterates through all the sibilings of the node object and adds the sibilings - * to an array if sibiling is a TEXT_NODE else checks the text nodes with in current node + * Finds and returns text nodes within node and all descendants. + * Iterates through all the siblings of the node object and adds each sibling to an + * array if it's a TEXT_NODE, and otherwise recurses to check text nodes within it. * Source - http://stackoverflow.com/questions/10730309/find-all-text-nodes-in-html-page * * @param Node nodeObject @@ -414,14 +414,20 @@ var gSearchResultsPane = { let matchesFound = false; if ( nodeObject.childElementCount == 0 || - nodeObject.tagName == "button" || - nodeObject.tagName == "label" || - nodeObject.tagName == "description" || - nodeObject.tagName == "menulist" || - nodeObject.tagName == "menuitem" || - nodeObject.tagName == "checkbox" + nodeObject.localName == "button" || + nodeObject.localName == "label" || + nodeObject.localName == "description" || + nodeObject.localName == "menulist" || + nodeObject.localName == "menuitem" || + nodeObject.localName == "checkbox" || + nodeObject.localName == "moz-toggle" ) { let simpleTextNodes = this.textNodeDescendants(nodeObject); + if (nodeObject.shadowRoot) { + simpleTextNodes.push( + ...this.textNodeDescendants(nodeObject.shadowRoot) + ); + } for (let node of simpleTextNodes) { let result = this.highlightMatches( [node], @@ -440,8 +446,8 @@ var gSearchResultsPane = { let accessKeyTextNodes = []; if ( - nodeObject.tagName == "label" || - nodeObject.tagName == "description" + nodeObject.localName == "label" || + nodeObject.localName == "description" ) { accessKeyTextNodes.push(...simpleTextNodes); } @@ -469,7 +475,7 @@ var gSearchResultsPane = { // Searching some elements, such as xul:label, store their user-visible text in a "value" attribute. // Value will be skipped for menuitem since value in menuitem could represent index number to distinct each item. let valueResult = - nodeObject.tagName !== "menuitem" && nodeObject.tagName !== "radio" + nodeObject.localName !== "menuitem" && nodeObject.localName !== "radio" ? this.queryMatchesContent( nodeObject.getAttribute("value"), searchPhrase @@ -497,12 +503,13 @@ var gSearchResultsPane = { // Creating tooltips for buttons if ( keywordsResult && - (nodeObject.tagName === "button" || nodeObject.tagName == "menulist") + (nodeObject.localName === "button" || + nodeObject.localName == "menulist") ) { this.listSearchTooltips.add(nodeObject); } - if (keywordsResult && nodeObject.tagName === "menuitem") { + if (keywordsResult && nodeObject.localName === "menuitem") { nodeObject.setAttribute("indicator", "true"); this.listSearchMenuitemIndicators.add(nodeObject); let menulist = nodeObject.closest("menulist"); @@ -512,8 +519,8 @@ var gSearchResultsPane = { } if ( - (nodeObject.tagName == "menulist" || - nodeObject.tagName == "menuitem") && + (nodeObject.localName == "menulist" || + nodeObject.localName == "menuitem") && (labelResult || valueResult || keywordsResult) ) { nodeObject.setAttribute("highlightable", "true"); @@ -529,7 +536,7 @@ var gSearchResultsPane = { // Should not search unselected child nodes of a <xul:deck> element // except the "historyPane" <xul:deck> element. - if (nodeObject.tagName == "deck" && nodeObject.id != "historyPane") { + if (nodeObject.localName == "deck" && nodeObject.id != "historyPane") { let index = nodeObject.selectedIndex; if (index != -1) { let result = await this.searchChildNodeIfVisible( @@ -572,7 +579,7 @@ var gSearchResultsPane = { ) { result = await this.searchWithinNode(child, searchPhrase); // Creating tooltips for menulist element - if (result && nodeObject.tagName === "menulist") { + if (result && nodeObject.localName === "menulist") { this.listSearchTooltips.add(nodeObject); } diff --git a/browser/components/preferences/home.js b/browser/components/preferences/home.js index 6aa72b84b8..093248cff6 100644 --- a/browser/components/preferences/home.js +++ b/browser/components/preferences/home.js @@ -160,13 +160,14 @@ var gHomePane = { } let extensionOptions; + await ExtensionSettingsStore.initialize(); if (select.id === "homeMode") { - extensionOptions = await ExtensionSettingsStore.getAllSettings( + extensionOptions = ExtensionSettingsStore.getAllSettings( PREF_SETTING_TYPE, HOMEPAGE_OVERRIDE_KEY ); } else { - extensionOptions = await ExtensionSettingsStore.getAllSettings( + extensionOptions = ExtensionSettingsStore.getAllSettings( URL_OVERRIDES_TYPE, NEW_TAB_KEY ); @@ -344,12 +345,15 @@ var gHomePane = { }, /** - * _isTabAboutPreferences: Is a given tab set to about:preferences? + * _isTabAboutPreferencesOrSettings: Is a given tab set to about:preferences or about:settings? * @param {Element} aTab A tab element - * @returns {bool} Is the linkedBrowser of aElement set to about:preferences? + * @returns {bool} Is the linkedBrowser of aElement set to about:preferences or about:settings? */ - _isTabAboutPreferences(aTab) { - return aTab.linkedBrowser.currentURI.spec.startsWith("about:preferences"); + _isTabAboutPreferencesOrSettings(aTab) { + return ( + aTab.linkedBrowser.currentURI.spec.startsWith("about:preferences") || + aTab.linkedBrowser.currentURI.spec.startsWith("about:settings") + ); }, /** @@ -367,7 +371,7 @@ var gHomePane = { "navigator:browser" ) { tabs = win.gBrowser.visibleTabs.slice(win.gBrowser._numPinnedTabs); - tabs = tabs.filter(tab => !this._isTabAboutPreferences(tab)); + tabs = tabs.filter(tab => !this._isTabAboutPreferencesOrSettings(tab)); // XXX: Bug 1441637 - Fix tabbrowser to report tab.closing before it blurs it tabs = tabs.filter(tab => !tab.closing); } diff --git a/browser/components/preferences/main.inc.xhtml b/browser/components/preferences/main.inc.xhtml index aaacdb6d97..1eb2189c41 100644 --- a/browser/components/preferences/main.inc.xhtml +++ b/browser/components/preferences/main.inc.xhtml @@ -159,14 +159,10 @@ <html:h2 data-l10n-id="preferences-web-appearance-header"/> <html:div id="webAppearanceSettings"> <description class="description-deemphasized" data-l10n-id="preferences-web-appearance-description"/> - <html:div id="web-appearance-override-warning" class="info-box-container"> - <html:div class="info-icon-container"> - <html:img class="info-icon"/> - </html:div> - <description data-l10n-id="preferences-web-appearance-override-warning"> - <html:a class="text-link" data-l10n-name="colors-link" id="web-appearance-manage-colors-link" href="#"/> - </description> - </html:div> + <html:moz-message-bar id="web-appearance-override-warning" data-l10n-id="preferences-web-appearance-override-warning2" + data-l10n-attrs="message"> + <button slot="actions" class="accessory-button" data-l10n-id="preferences-colors-manage-button" id="web-appearance-manage-colors-button"/> + </html:moz-message-bar> <form xmlns="http://www.w3.org/1999/xhtml" id="web-appearance-chooser" autocomplete="off"> <label class="web-appearance-choice" data-l10n-id="preferences-web-appearance-choice-tooltip-auto"> <div class="web-appearance-choice-image-container"><img role="presentation" alt="" width="54" height="42" /></div> @@ -817,6 +813,7 @@ connection-proxy-option-no.label, connection-proxy-option-auto.label, connection-proxy-option-system.label, + connection-proxy-option-wpad.label, connection-proxy-option-manual.label, connection-proxy-http, connection-proxy-https, diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js index b9487ece26..b578e0e29f 100644 --- a/browser/components/preferences/main.js +++ b/browser/components/preferences/main.js @@ -278,7 +278,10 @@ var gMainPane = { let uri = win.gBrowser.currentURI.spec; if ( - (uri == "about:preferences" || uri == "about:preferences#general") && + (uri == "about:preferences" || + uri == "about:preferences#general" || + uri == "about:settings" || + uri == "about:settings#general") && document.visibilityState == "visible" ) { this.updateSetDefaultBrowser(); @@ -879,7 +882,7 @@ var gMainPane = { this.readBrowserContainersCheckbox(); }, - async onGetStarted(aEvent) { + async onGetStarted() { if (!AppConstants.MOZ_DEV_EDITION) { return; } @@ -1915,6 +1918,11 @@ var gMainPane = { if ( Services.prefs.getBoolPref("browser.translations.newSettingsUI.enable") ) { + const translationsSettings = document.getElementById( + "translations-settings-page" + ); + translationsSettings.setAttribute("data-hidden-from-search", "false"); + translationsSettings.hidden = false; gotoPref("translations"); } else { gSubDialog.open( @@ -1980,7 +1988,7 @@ var gMainPane = { } }, - async checkBrowserContainers(event) { + async checkBrowserContainers() { let checkbox = document.getElementById("browserContainersCheckbox"); if (checkbox.checked) { Services.prefs.setBoolPref("privacy.userContext.enabled", true); @@ -2135,7 +2143,7 @@ var gMainPane = { })().catch(console.error); }, - onMigrationButtonCommand(command) { + onMigrationButtonCommand() { // Even though we're going to be showing the migration wizard here in // about:preferences, we'll delegate the call to // `MigrationUtils.showMigrationWizard`, as this will allow us to @@ -2288,7 +2296,7 @@ var gMainPane = { } }, - updatePerformanceSettingsBox({ duringChangeEvent }) { + updatePerformanceSettingsBox() { let defaultPerformancePref = Preferences.get( "browser.preferences.defaultPerformanceSettings.enabled" ); @@ -3333,7 +3341,7 @@ var gMainPane = { // Prompt the user to pick an app. If they pick one, and it's a valid // selection, then add it to the list of possible handlers. - fp.init(window, winTitle, Ci.nsIFilePicker.modeOpen); + fp.init(window.browsingContext, winTitle, Ci.nsIFilePicker.modeOpen); fp.appendFilters(Ci.nsIFilePicker.filterApps); fp.open(fpCallback); } @@ -3451,7 +3459,7 @@ var gMainPane = { let defDownloads = await this._indexToFolder(1); let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - fp.init(window, title, Ci.nsIFilePicker.modeGetFolder); + fp.init(window.browsingContext, title, Ci.nsIFilePicker.modeGetFolder); fp.appendFilters(Ci.nsIFilePicker.filterAll); // First try to open what's currently configured if (currentDirPref && currentDirPref.exists()) { @@ -4189,7 +4197,7 @@ const AppearanceChooser = { if (e.type == "click") { switch (e.target.id) { // Forward the click to the "colors" button. - case "web-appearance-manage-colors-link": + case "web-appearance-manage-colors-button": document.getElementById("colors").click(); e.preventDefault(); break; @@ -4206,7 +4214,7 @@ const AppearanceChooser = { this._update(); }, - observe(subject, topic, data) { + observe() { this._update(); }, diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js index 31ae84f382..c30a51c67c 100644 --- a/browser/components/preferences/preferences.js +++ b/browser/components/preferences/preferences.js @@ -204,7 +204,10 @@ function init_all() { register_module("paneSearch", gSearchPane); register_module("panePrivacy", gPrivacyPane); register_module("paneContainers", gContainersPane); - register_module("paneTranslations", gTranslationsPane); + + if (Services.prefs.getBoolPref("browser.translations.newSettingsUI.enable")) { + register_module("paneTranslations", gTranslationsPane); + } if (Services.prefs.getBoolPref("browser.preferences.experimental")) { // Set hidden based on previous load's hidden value. document.getElementById("category-experimental").hidden = @@ -475,7 +478,7 @@ async function spotlight(subcategory, category) { } } -async function scrollAndHighlight(subcategory, category) { +async function scrollAndHighlight(subcategory) { let element = document.querySelector(`[data-subcategory="${subcategory}"]`); if (!element) { return; @@ -643,7 +646,7 @@ async function ensureScrollPadding() { let stickyContainer = document.querySelector(".sticky-container"); let height = await window.browsingContext.topChromeWindow .promiseDocumentFlushed(() => stickyContainer.clientHeight) - .catch(err => Cu.reportError); // Can reject if the window goes away. + .catch(() => Cu.reportError); // Can reject if the window goes away. // Make it a bit more, to ensure focus rectangles etc. don't get cut off. // This being 8px causes us to end up with 90px if the policies container diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml index 38fba9a726..eee227822a 100644 --- a/browser/components/preferences/preferences.xhtml +++ b/browser/components/preferences/preferences.xhtml @@ -12,7 +12,7 @@ <head> <!-- @CSP: We should remove 'unsafe-inline' from style-src, see Bug 1579160 --> - <meta http-equiv="Content-Security-Policy" content="default-src chrome:; img-src chrome: moz-icon: https: data:; style-src chrome: data: 'unsafe-inline'; object-src 'none'" /> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:; img-src chrome: moz-icon: https: blob: data:; style-src chrome: data: 'unsafe-inline'; object-src 'none'" /> <title data-l10n-id="settings-page-title"></title> @@ -84,6 +84,7 @@ <script src="chrome://browser/content/migration/migration-wizard.mjs" type="module"></script> <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"/> </head> <html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml index a81764e34f..224a5f5cbb 100644 --- a/browser/components/preferences/privacy.inc.xhtml +++ b/browser/components/preferences/privacy.inc.xhtml @@ -985,11 +985,12 @@ <html:input type="checkbox" id="automaticallySubmitCrashesBox" preference="browser.crashReports.unsubmittedCheck.autoSubmit2"/> - <label for="automaticallySubmitCrashesBox" + <html:label is="moz-label" + for="automaticallySubmitCrashesBox" id="crashReporterLabel" - data-l10n-id="collection-backlogged-crash-reports-with-link"> - <html:a data-l10n-name="crash-reports-link" id="crashReporterLearnMore" target="_blank"/> - </label> + data-l10n-id="collection-backlogged-crash-reports" + data-l10n-attrs="accesskey"/> + <html:a id="crashReporterLearnMore" is="moz-support-link" class="learnMore"/> </hbox> #endif </vbox> diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js index 178bc560c7..3b07b9cabf 100644 --- a/browser/components/preferences/privacy.js +++ b/browser/components/preferences/privacy.js @@ -435,7 +435,7 @@ var gPrivacyPane = { ); let trackingProtectionObserver = { - observe(subject, topic, data) { + observe() { gPrivacyPane._updateTrackingProtectionUI(); }, }; @@ -3230,13 +3230,6 @@ var gPrivacyPane = { "toolkit.crashreporter.infoURL", "crashReporterLearnMore" ); - setEventListener("crashReporterLabel", "click", function (event) { - if (event.target.localName == "a") { - return; - } - const checkboxId = event.target.getAttribute("for"); - document.getElementById(checkboxId).click(); - }); }, initPrivacySegmentation() { @@ -3317,7 +3310,7 @@ var gPrivacyPane = { * Initialize the opt-out-study preference checkbox into about:preferences and * handles events coming from the UI for it. */ - initOptOutStudyCheckbox(doc) { + initOptOutStudyCheckbox() { // The checkbox should be disabled if any of the below are true. This // prevents the user from changing the value in the box. // @@ -3361,7 +3354,7 @@ var gPrivacyPane = { }); }, - observe(aSubject, aTopic, aData) { + observe(aSubject, aTopic) { switch (aTopic) { case "sitedatamanager:updating-sites": // While updating, we want to disable this section and display loading message until updated diff --git a/browser/components/preferences/search.js b/browser/components/preferences/search.js index 42776cfa96..a491d6c5ca 100644 --- a/browser/components/preferences/search.js +++ b/browser/components/preferences/search.js @@ -9,6 +9,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { SearchUIUtils: "resource:///modules/SearchUIUtils.sys.mjs", + SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs", }); const PREF_URLBAR_QUICKSUGGEST_BLOCKLIST = @@ -34,16 +35,27 @@ const ENGINE_FLAVOR = "text/x-moz-search-engine"; const SEARCH_TYPE = "default_search"; const SEARCH_KEY = "defaultSearch"; -// The name of in built engines that support trending results. -const TRENDING_ENGINES = ["Google", "Bing"]; - var gEngineView = null; var gSearchPane = { + _engineStore: null, + _engineDropDown: null, + _engineDropDownPrivate: null, + init() { - gEngineView = new EngineView(new EngineStore()); - document.getElementById("engineList").view = gEngineView; - this.buildDefaultEngineDropDowns().catch(console.error); + this._engineStore = new EngineStore(); + gEngineView = new EngineView(this._engineStore); + + this._engineDropDown = new DefaultEngineDropDown( + "normal", + this._engineStore + ); + this._engineDropDownPrivate = new DefaultEngineDropDown( + "private", + this._engineStore + ); + + this._engineStore.init().catch(console.error); if ( Services.policies && @@ -55,12 +67,7 @@ var gSearchPane = { addEnginesLink.setAttribute("href", lazy.SearchUIUtils.searchEnginesURL); } - window.addEventListener("click", this); window.addEventListener("command", this); - window.addEventListener("dragstart", this); - window.addEventListener("keypress", this); - window.addEventListener("select", this); - window.addEventListener("dblclick", this); Services.obs.addObserver(this, "browser-search-engine-modified"); Services.obs.addObserver(this, "intl:app-locales-changed"); @@ -115,7 +122,6 @@ var gSearchPane = { this._initDefaultEngines(); this._initShowSearchTermsCheckbox(); this._updateSuggestionCheckboxes(); - this._showAddEngineButton(); this._initRecentSeachesCheckbox(); this._initAddressBar(); }, @@ -140,7 +146,7 @@ var gSearchPane = { const listener = () => { this._updatePrivateEngineDisplayBoxes(); - this.buildDefaultEngineDropDowns().catch(console.error); + this._engineStore.notifyRebuildViews(); }; this._separatePrivateDefaultEnabledPref.on("change", listener); @@ -256,17 +262,6 @@ var gSearchPane = { this._updateTrendingCheckbox(!suggestsPref.value || permanentPB); }, - _showAddEngineButton() { - let aliasRefresh = Services.prefs.getBoolPref( - "browser.urlbar.update2.engineAliasRefresh", - false - ); - if (aliasRefresh) { - let addButton = document.getElementById("addEngineButton"); - addButton.hidden = false; - } - }, - _initRecentSeachesCheckbox() { this._recentSearchesEnabledPref = Preferences.get( "browser.urlbar.recentsearches.featureGate" @@ -285,38 +280,14 @@ var gSearchPane = { async _updateTrendingCheckbox(suggestDisabled) { let trendingBox = document.getElementById("showTrendingSuggestionsBox"); let trendingCheckBox = document.getElementById("showTrendingSuggestions"); - let trendingSupported = TRENDING_ENGINES.includes( - (await Services.search.getDefault()).name - ); + let trendingSupported = ( + await Services.search.getDefault() + ).supportsResponseType(lazy.SearchUtils.URL_TYPE.TRENDING_JSON); trendingBox.hidden = !Preferences.get("browser.urlbar.trending.featureGate") .value; trendingCheckBox.disabled = suggestDisabled || !trendingSupported; }, - /** - * Builds the default and private engines drop down lists. This is called - * each time something affects the list of engines. - */ - async buildDefaultEngineDropDowns() { - await this._buildEngineDropDown( - document.getElementById("defaultEngine"), - ( - await Services.search.getDefault() - ).name, - false - ); - - if (this._separatePrivateDefaultEnabledPref.value) { - await this._buildEngineDropDown( - document.getElementById("defaultPrivateEngine"), - ( - await Services.search.getDefaultPrivate() - ).name, - true - ); - } - }, - // ADDRESS BAR /** @@ -486,125 +457,24 @@ var gSearchPane = { Services.prefs.clearUserPref(PREF_URLBAR_WEATHER_USER_ENABLED); }, - /** - * Builds a drop down menu of search engines. - * - * @param {DOMMenuList} list - * The menu list element to attach the list of engines. - * @param {string} currentEngine - * The name of the current default engine. - * @param {boolean} isPrivate - * True if we are dealing with the default engine for private mode. - */ - async _buildEngineDropDown(list, currentEngine, isPrivate) { - // If the current engine isn't in the list any more, select the first item. - let engines = gEngineView._engineStore._engines; - if (!engines.length) { + handleEvent(aEvent) { + if (aEvent.type != "command") { return; } - if (!engines.some(e => e.name == currentEngine)) { - currentEngine = engines[0].name; - } - - // Now clean-up and rebuild the list. - list.removeAllItems(); - gEngineView._engineStore._engines.forEach(e => { - let item = list.appendItem(e.name); - item.setAttribute( - "class", - "menuitem-iconic searchengine-menuitem menuitem-with-favicon" - ); - if (e.iconURL) { - item.setAttribute("image", e.iconURL); - } - item.engine = e; - if (e.name == currentEngine) { - list.selectedItem = item; - } - }); - }, - - handleEvent(aEvent) { - switch (aEvent.type) { - case "dblclick": - if (aEvent.target.id == "engineChildren") { - let cell = aEvent.target.parentNode.getCellAt( - aEvent.clientX, - aEvent.clientY - ); - if (cell.col?.id == "engineKeyword") { - this.startEditingAlias(gEngineView.selectedIndex); + switch (aEvent.target.id) { + case "": + if (aEvent.target.parentNode && aEvent.target.parentNode.parentNode) { + if (aEvent.target.parentNode.parentNode.id == "defaultEngine") { + gSearchPane.setDefaultEngine(); + } else if ( + aEvent.target.parentNode.parentNode.id == "defaultPrivateEngine" + ) { + gSearchPane.setDefaultPrivateEngine(); } } break; - case "click": - if ( - aEvent.target.id != "engineChildren" && - !aEvent.target.classList.contains("searchEngineAction") - ) { - let engineList = document.getElementById("engineList"); - // We don't want to toggle off selection while editing keyword - // so proceed only when the input field is hidden. - // We need to check that engineList.view is defined here - // because the "click" event listener is on <window> and the - // view might have been destroyed if the pane has been navigated - // away from. - if (engineList.inputField.hidden && engineList.view) { - let selection = engineList.view.selection; - if (selection?.count > 0) { - selection.toggleSelect(selection.currentIndex); - } - engineList.blur(); - } - } - break; - case "command": - switch (aEvent.target.id) { - case "": - if ( - aEvent.target.parentNode && - aEvent.target.parentNode.parentNode - ) { - if (aEvent.target.parentNode.parentNode.id == "defaultEngine") { - gSearchPane.setDefaultEngine(); - } else if ( - aEvent.target.parentNode.parentNode.id == "defaultPrivateEngine" - ) { - gSearchPane.setDefaultPrivateEngine(); - } - } - break; - case "restoreDefaultSearchEngines": - gSearchPane.onRestoreDefaults(); - break; - case "removeEngineButton": - Services.search.removeEngine( - gEngineView.selectedEngine.originalEngine - ); - break; - case "addEngineButton": - gSubDialog.open( - "chrome://browser/content/preferences/dialogs/addEngine.xhtml", - { features: "resizable=no, modal=yes" } - ); - break; - } - break; - case "dragstart": - if (aEvent.target.id == "engineChildren") { - onDragEngineStart(aEvent); - } - break; - case "keypress": - if (aEvent.target.id == "engineList") { - gSearchPane.onTreeKeyPress(aEvent); - } - break; - case "select": - if (aEvent.target.id == "engineList") { - gSearchPane.onTreeSelect(); - } - break; + default: + gEngineView.handleEvent(aEvent); } }, @@ -617,60 +487,6 @@ var gSearchPane = { }, /** - * Update the default engine UI and engine tree view as appropriate when engine changes - * or locale changes occur. - * - * @param {Object} engine - * @param {string} data - */ - browserSearchEngineModified(engine, data) { - engine.QueryInterface(Ci.nsISearchEngine); - switch (data) { - case "engine-added": - gEngineView._engineStore.addEngine(engine); - gEngineView.rowCountChanged(gEngineView.lastEngineIndex, 1); - gSearchPane.buildDefaultEngineDropDowns(); - break; - case "engine-changed": - gSearchPane.buildDefaultEngineDropDowns(); - gEngineView._engineStore.updateEngine(engine); - gEngineView.invalidate(); - break; - case "engine-removed": - gSearchPane.remove(engine); - break; - case "engine-default": { - // If the user is going through the drop down using up/down keys, the - // dropdown may still be open (eg. on Windows) when engine-default is - // fired, so rebuilding the list unconditionally would get in the way. - let selectedEngine = - document.getElementById("defaultEngine").selectedItem.engine; - if (selectedEngine.name != engine.name) { - gSearchPane.buildDefaultEngineDropDowns(); - } - gSearchPane._updateSuggestionCheckboxes(); - break; - } - case "engine-default-private": { - if ( - this._separatePrivateDefaultEnabledPref.value && - this._separatePrivateDefaultPref.value - ) { - // If the user is going through the drop down using up/down keys, the - // dropdown may still be open (eg. on Windows) when engine-default is - // fired, so rebuilding the list unconditionally would get in the way. - const selectedEngine = document.getElementById("defaultPrivateEngine") - .selectedItem.engine; - if (selectedEngine.name != engine.name) { - gSearchPane.buildDefaultEngineDropDowns(); - } - } - break; - } - } - }, - - /** * nsIObserver implementation. */ observe(subject, topic, data) { @@ -680,144 +496,25 @@ var gSearchPane = { break; } case "browser-search-engine-modified": { - this.browserSearchEngineModified(subject, data); - break; - } - } - }, - - onTreeSelect() { - document.getElementById("removeEngineButton").disabled = - !gEngineView.isEngineSelectedAndRemovable(); - }, - - onTreeKeyPress(aEvent) { - let index = gEngineView.selectedIndex; - let tree = document.getElementById("engineList"); - if (tree.hasAttribute("editing")) { - return; - } - - if (aEvent.charCode == KeyEvent.DOM_VK_SPACE) { - // Space toggles the checkbox. - let newValue = !gEngineView.getCellValue( - index, - tree.columns.getNamedColumn("engineShown") - ); - gEngineView.setCellValue( - index, - tree.columns.getFirstColumn(), - newValue.toString() - ); - // Prevent page from scrolling on the space key. - aEvent.preventDefault(); - } else { - let isMac = Services.appinfo.OS == "Darwin"; - if ( - (isMac && aEvent.keyCode == KeyEvent.DOM_VK_RETURN) || - (!isMac && aEvent.keyCode == KeyEvent.DOM_VK_F2) - ) { - this.startEditingAlias(index); - } else if ( - aEvent.keyCode == KeyEvent.DOM_VK_DELETE || - (isMac && - aEvent.shiftKey && - aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE && - gEngineView.isEngineSelectedAndRemovable()) - ) { - // Delete and Shift+Backspace (Mac) removes selected engine. - Services.search.removeEngine(gEngineView.selectedEngine.originalEngine); + let engine = subject.QueryInterface(Ci.nsISearchEngine); + switch (data) { + case "engine-default": { + // Pass through to the engine store to handle updates. + this._engineStore.browserSearchEngineModified(engine, data); + gSearchPane._updateSuggestionCheckboxes(); + break; + } + default: + this._engineStore.browserSearchEngineModified(engine, data); + } } } }, - startEditingAlias(index) { - // Local shortcut aliases can't be edited. - if (gEngineView._getLocalShortcut(index)) { - return; - } - - let tree = document.getElementById("engineList"); - let engine = gEngineView._engineStore.engines[index]; - tree.startEditing(index, tree.columns.getLastColumn()); - tree.inputField.value = engine.alias || ""; - tree.inputField.select(); - }, - - async onRestoreDefaults() { - let num = await gEngineView._engineStore.restoreDefaultEngines(); - gEngineView.rowCountChanged(0, num); - gEngineView.invalidate(); - }, - showRestoreDefaults(aEnable) { document.getElementById("restoreDefaultSearchEngines").disabled = !aEnable; }, - remove(aEngine) { - let index = gEngineView._engineStore.removeEngine(aEngine); - if (!gEngineView.tree) { - // Only update the selection if it's visible in the UI. - return; - } - - gEngineView.rowCountChanged(index, -1); - gEngineView.invalidate(); - - gEngineView.selection.select(Math.min(index, gEngineView.rowCount - 1)); - gEngineView.ensureRowIsVisible(gEngineView.currentIndex); - - document.getElementById("engineList").focus(); - }, - - async editKeyword(aEngine, aNewKeyword) { - let keyword = aNewKeyword.trim(); - if (keyword) { - let eduplicate = false; - let dupName = ""; - - // Check for duplicates in Places keywords. - let bduplicate = !!(await PlacesUtils.keywords.fetch(keyword)); - - // Check for duplicates in changes we haven't committed yet - let engines = gEngineView._engineStore.engines; - let lc_keyword = keyword.toLocaleLowerCase(); - for (let engine of engines) { - if ( - engine.alias && - engine.alias.toLocaleLowerCase() == lc_keyword && - engine.name != aEngine.name - ) { - eduplicate = true; - dupName = engine.name; - break; - } - } - - // Notify the user if they have chosen an existing engine/bookmark keyword - if (eduplicate || bduplicate) { - let msgids = [{ id: "search-keyword-warning-title" }]; - if (eduplicate) { - msgids.push({ - id: "search-keyword-warning-engine", - args: { name: dupName }, - }); - } else { - msgids.push({ id: "search-keyword-warning-bookmark" }); - } - - let [dtitle, msg] = await document.l10n.formatValues(msgids); - - Services.prompt.alert(window, dtitle, msg); - return false; - } - } - - gEngineView._engineStore.changeEngine(aEngine, "alias", keyword); - gEngineView.invalidate(); - return true; - }, - async setDefaultEngine() { await Services.search.setDefault( document.getElementById("defaultEngine").selectedItem.engine, @@ -840,92 +537,162 @@ var gSearchPane = { }, }; -function onDragEngineStart(event) { - var selectedIndex = gEngineView.selectedIndex; +/** + * Keeps track of the search engine objects and notifies the views for updates. + */ +class EngineStore { + /** + * A list of engines that are currently visible in the UI. + * + * @type {Object[]} + */ + engines = []; - // Local shortcut rows can't be dragged or re-ordered. - if (gEngineView._getLocalShortcut(selectedIndex)) { - event.preventDefault(); - return; - } + /** + * A list of application provided engines used when restoring the list of + * engines to the default set and order. + * + * @type {nsISearchEngine[]} + */ + #appProvidedEngines = []; - var tree = document.getElementById("engineList"); - let cell = tree.getCellAt(event.clientX, event.clientY); - if (selectedIndex >= 0 && !gEngineView.isCheckBox(cell.row, cell.col)) { - event.dataTransfer.setData(ENGINE_FLAVOR, selectedIndex.toString()); - event.dataTransfer.effectAllowed = "move"; - } -} + /** + * A list of listeners to be notified when the engine list changes. + * + * @type {Object[]} + */ + #listeners = []; -function EngineStore() { - this._engines = []; - this._defaultEngines = []; - Promise.all([ - Services.search.getVisibleEngines(), - Services.search.getAppProvidedEngines(), - ]).then(([visibleEngines, defaultEngines]) => { + async init() { + let visibleEngines = await Services.search.getVisibleEngines(); for (let engine of visibleEngines) { this.addEngine(engine); - gEngineView.rowCountChanged(gEngineView.lastEngineIndex, 1); } - this._defaultEngines = defaultEngines.map(this._cloneEngine, this); - gSearchPane.buildDefaultEngineDropDowns(); + + let appProvidedEngines = await Services.search.getAppProvidedEngines(); + this.#appProvidedEngines = appProvidedEngines.map(this._cloneEngine, this); + + this.notifyRowCountChanged(0, visibleEngines.length); // check if we need to disable the restore defaults button - var someHidden = this._defaultEngines.some(e => e.hidden); + var someHidden = this.#appProvidedEngines.some(e => e.hidden); gSearchPane.showRestoreDefaults(someHidden); - }); -} -EngineStore.prototype = { - _engines: null, - _defaultEngines: null, + } - get engines() { - return this._engines; - }, - set engines(val) { - this._engines = val; - }, + /** + * Adds a listener to be notified when the engine list changes. + * + * @param {object} aListener + */ + addListener(aListener) { + this.#listeners.push(aListener); + } + + /** + * Notifies all listeners that the engine list has changed and they should + * rebuild. + */ + notifyRebuildViews() { + for (let listener of this.#listeners) { + try { + listener.rebuild(this.engines); + } catch (ex) { + console.error("Error notifying EngineStore listener", ex); + } + } + } + + /** + * Notifies all listeners that the number of engines in the list has changed. + * + * @param {number} index + * @param {number} count + */ + notifyRowCountChanged(index, count) { + for (let listener of this.#listeners) { + listener.rowCountChanged(index, count, this.engines); + } + } + + /** + * Notifies all listeners that the default engine has changed. + * + * @param {string} type + * @param {object} engine + */ + notifyDefaultEngineChanged(type, engine) { + for (let listener of this.#listeners) { + if ("defaultEngineChanged" in listener) { + listener.defaultEngineChanged(type, engine, this.engines); + } + } + } + + notifyEngineIconUpdated(engine) { + // Check the engine is still in the list. + let index = this._getIndexForEngine(engine); + if (index != -1) { + for (let listener of this.#listeners) { + listener.engineIconUpdated(index, this.engines); + } + } + } _getIndexForEngine(aEngine) { - return this._engines.indexOf(aEngine); - }, + return this.engines.indexOf(aEngine); + } _getEngineByName(aName) { - return this._engines.find(engine => engine.name == aName); - }, + return this.engines.find(engine => engine.name == aName); + } _cloneEngine(aEngine) { var clonedObj = { - iconURL: aEngine.getIconURL(), + iconURL: null, }; for (let i of ["id", "name", "alias", "hidden"]) { clonedObj[i] = aEngine[i]; } clonedObj.originalEngine = aEngine; + + // Trigger getting the iconURL for this engine. + aEngine.getIconURL().then(iconURL => { + if (iconURL) { + clonedObj.iconURL = iconURL; + } else if (window.devicePixelRatio > 1) { + clonedObj.iconURL = + "chrome://browser/skin/search-engine-placeholder@2x.png"; + } else { + clonedObj.iconURL = + "chrome://browser/skin/search-engine-placeholder.png"; + } + + this.notifyEngineIconUpdated(clonedObj); + }); + return clonedObj; - }, + } // Callback for Array's some(). A thisObj must be passed to some() _isSameEngine(aEngineClone) { return aEngineClone.originalEngine.id == this.originalEngine.id; - }, + } addEngine(aEngine) { - this._engines.push(this._cloneEngine(aEngine)); - }, + this.engines.push(this._cloneEngine(aEngine)); + } updateEngine(newEngine) { - let engineToUpdate = this._engines.findIndex( + let engineToUpdate = this.engines.findIndex( e => e.originalEngine.id == newEngine.id ); if (engineToUpdate != -1) { this.engines[engineToUpdate] = this._cloneEngine(newEngine); } - }, + } moveEngine(aEngine, aNewIndex) { - if (aNewIndex < 0 || aNewIndex > this._engines.length - 1) { + if (aNewIndex < 0 || aNewIndex > this.engines.length - 1) { throw new Error("ES_moveEngine: invalid aNewIndex!"); } var index = this._getIndexForEngine(aEngine); @@ -938,41 +705,73 @@ EngineStore.prototype = { } // nothing to do // Move the engine in our internal store - var removedEngine = this._engines.splice(index, 1)[0]; - this._engines.splice(aNewIndex, 0, removedEngine); + var removedEngine = this.engines.splice(index, 1)[0]; + this.engines.splice(aNewIndex, 0, removedEngine); return Services.search.moveEngine(aEngine.originalEngine, aNewIndex); - }, + } removeEngine(aEngine) { - if (this._engines.length == 1) { + if (this.engines.length == 1) { throw new Error("Cannot remove last engine!"); } let engineName = aEngine.name; - let index = this._engines.findIndex(element => element.name == engineName); + let index = this.engines.findIndex(element => element.name == engineName); if (index == -1) { throw new Error("invalid engine?"); } - this._engines.splice(index, 1)[0]; + this.engines.splice(index, 1)[0]; if (aEngine.isAppProvided) { gSearchPane.showRestoreDefaults(true); } - gSearchPane.buildDefaultEngineDropDowns(); - return index; - }, + + this.notifyRowCountChanged(index, -1); + + document.getElementById("engineList").focus(); + } + + /** + * Update the default engine UI and engine tree view as appropriate when engine changes + * or locale changes occur. + * + * @param {nsISearchEngine} engine + * @param {string} data + */ + browserSearchEngineModified(engine, data) { + engine.QueryInterface(Ci.nsISearchEngine); + switch (data) { + case "engine-added": + this.addEngine(engine); + this.notifyRowCountChanged(gEngineView.lastEngineIndex, 1); + break; + case "engine-changed": + this.updateEngine(engine); + this.notifyRebuildViews(); + break; + case "engine-removed": + this.removeEngine(engine); + break; + case "engine-default": + this.notifyDefaultEngineChanged("normal", engine); + break; + case "engine-default-private": + this.notifyDefaultEngineChanged("private", engine); + break; + } + } async restoreDefaultEngines() { var added = 0; - for (var i = 0; i < this._defaultEngines.length; ++i) { - var e = this._defaultEngines[i]; + for (var i = 0; i < this.#appProvidedEngines.length; ++i) { + var e = this.#appProvidedEngines[i]; // If the engine is already in the list, just move it. - if (this._engines.some(this._isSameEngine, e)) { + if (this.engines.some(this._isSameEngine, e)) { await this.moveEngine(this._getEngineByName(e.name), i); } else { // Otherwise, add it back to our internal store @@ -981,7 +780,7 @@ EngineStore.prototype = { // so clear any alias we may have cached before unhiding the engine. e.alias = ""; - this._engines.splice(i, 0, e); + this.engines.splice(i, 0, e); let engine = e.originalEngine; engine.hidden = false; await Services.search.moveEngine(engine, i); @@ -1006,9 +805,9 @@ EngineStore.prototype = { Services.search.resetToAppDefaultEngine(); gSearchPane.showRestoreDefaults(false); - gSearchPane.buildDefaultEngineDropDowns(); + this.notifyRebuildViews(); return added; - }, + } changeEngine(aEngine, aProp, aNewValue) { var index = this._getIndexForEngine(aEngine); @@ -1016,22 +815,31 @@ EngineStore.prototype = { throw new Error("invalid engine?"); } - this._engines[index][aProp] = aNewValue; + this.engines[index][aProp] = aNewValue; aEngine.originalEngine[aProp] = aNewValue; - }, -}; - -function EngineView(aEngineStore) { - this._engineStore = aEngineStore; - - UrlbarPrefs.addObserver(this); - - this.loadL10nNames(); + } } -EngineView.prototype = { - _engineStore: null, - tree: null, +/** + * Manages the view of the Search Shortcuts tree on the search pane of preferences. + */ +class EngineView { + _engineStore = null; + _engineList = null; + tree = null; + + constructor(aEngineStore) { + this._engineStore = aEngineStore; + this._engineList = document.getElementById("engineList"); + this._engineList.view = this; + + UrlbarPrefs.addObserver(this); + aEngineStore.addListener(this); + + this.loadL10nNames(); + this.#addListeners(); + this.#showAddEngineButton(); + } loadL10nNames() { // This maps local shortcut sources to their l10n names. The names are needed @@ -1053,11 +861,33 @@ EngineView.prototype = { // called before name retrieval finished. this.invalidate(); }); - }, + } + + #addListeners() { + this._engineList.addEventListener("click", this); + this._engineList.addEventListener("dragstart", this); + this._engineList.addEventListener("keypress", this); + this._engineList.addEventListener("select", this); + this._engineList.addEventListener("dblclick", this); + } + + /** + * Shows the "Add Search Engine" button if the pref is enabled. + */ + #showAddEngineButton() { + let aliasRefresh = Services.prefs.getBoolPref( + "browser.urlbar.update2.engineAliasRefresh", + false + ); + if (aliasRefresh) { + let addButton = document.getElementById("addEngineButton"); + addButton.hidden = false; + } + } get lastEngineIndex() { return this._engineStore.engines.length - 1; - }, + } get selectedIndex() { var seln = this.selection; @@ -1067,34 +897,52 @@ EngineView.prototype = { return min.value; } return -1; - }, + } get selectedEngine() { return this._engineStore.engines[this.selectedIndex]; - }, + } // Helpers + rebuild() { + this.invalidate(); + } + rowCountChanged(index, count) { - if (this.tree) { - this.tree.rowCountChanged(index, count); + if (!this.tree) { + return; } - }, + this.tree.rowCountChanged(index, count); + + // If we're removing elements, ensure that we still have a selection. + if (count < 0) { + this.selection.select(Math.min(index, this.rowCount - 1)); + this.ensureRowIsVisible(this.currentIndex); + } + } + + engineIconUpdated(index) { + this.tree?.invalidateCell( + index, + this.tree.columns.getNamedColumn("engineName") + ); + } invalidate() { this.tree?.invalidate(); - }, + } ensureRowIsVisible(index) { this.tree.ensureRowIsVisible(index); - }, + } getSourceIndexFromDrag(dataTransfer) { return parseInt(dataTransfer.getData(ENGINE_FLAVOR)); - }, + } isCheckBox(index, column) { return column.id == "engineShown"; - }, + } isEngineSelectedAndRemovable() { let defaultEngine = Services.search.defaultEngine; @@ -1109,7 +957,7 @@ EngineView.prototype = { this.selectedEngine.name != defaultEngine.name && this.selectedEngine.name != defaultPrivateEngine.name ); - }, + } /** * Returns the local shortcut corresponding to a tree row, or null if the row @@ -1126,7 +974,7 @@ EngineView.prototype = { return null; } return UrlbarUtils.LOCAL_SEARCH_MODES[index - engineCount]; - }, + } /** * Called by UrlbarPrefs when a urlbar pref changes. @@ -1141,14 +989,170 @@ EngineView.prototype = { if (parts[0] == "shortcuts" && parts[1] && parts.length == 2) { this.invalidate(); } - }, + } + + handleEvent(aEvent) { + switch (aEvent.type) { + case "dblclick": + if (aEvent.target.id == "engineChildren") { + let cell = aEvent.target.parentNode.getCellAt( + aEvent.clientX, + aEvent.clientY + ); + if (cell.col?.id == "engineKeyword") { + this.#startEditingAlias(this.selectedIndex); + } + } + break; + case "click": + if ( + aEvent.target.id != "engineChildren" && + !aEvent.target.classList.contains("searchEngineAction") + ) { + // We don't want to toggle off selection while editing keyword + // so proceed only when the input field is hidden. + // We need to check that engineList.view is defined here + // because the "click" event listener is on <window> and the + // view might have been destroyed if the pane has been navigated + // away from. + if (this._engineList.inputField.hidden && this._engineList.view) { + let selection = this._engineList.view.selection; + if (selection?.count > 0) { + selection.toggleSelect(selection.currentIndex); + } + this._engineList.blur(); + } + } + break; + case "command": + switch (aEvent.target.id) { + case "restoreDefaultSearchEngines": + this.#onRestoreDefaults(); + break; + case "removeEngineButton": + Services.search.removeEngine(this.selectedEngine.originalEngine); + break; + case "addEngineButton": + gSubDialog.open( + "chrome://browser/content/preferences/dialogs/addEngine.xhtml", + { features: "resizable=no, modal=yes" } + ); + break; + } + break; + case "dragstart": + if (aEvent.target.id == "engineChildren") { + this.#onDragEngineStart(aEvent); + } + break; + case "keypress": + if (aEvent.target.id == "engineList") { + this.#onTreeKeyPress(aEvent); + } + break; + case "select": + if (aEvent.target.id == "engineList") { + this.#onTreeSelect(); + } + break; + } + } + + /** + * Called when the restore default engines button is clicked to reset the + * list of engines to their defaults. + */ + async #onRestoreDefaults() { + let num = await this._engineStore.restoreDefaultEngines(); + this.rowCountChanged(0, num); + } + + #onDragEngineStart(event) { + let selectedIndex = this.selectedIndex; + + // Local shortcut rows can't be dragged or re-ordered. + if (this._getLocalShortcut(selectedIndex)) { + event.preventDefault(); + return; + } + + let tree = document.getElementById("engineList"); + let cell = tree.getCellAt(event.clientX, event.clientY); + if (selectedIndex >= 0 && !this.isCheckBox(cell.row, cell.col)) { + event.dataTransfer.setData(ENGINE_FLAVOR, selectedIndex.toString()); + event.dataTransfer.effectAllowed = "move"; + } + } + + #onTreeSelect() { + document.getElementById("removeEngineButton").disabled = + !this.isEngineSelectedAndRemovable(); + } + + #onTreeKeyPress(aEvent) { + let index = this.selectedIndex; + let tree = document.getElementById("engineList"); + if (tree.hasAttribute("editing")) { + return; + } + + if (aEvent.charCode == KeyEvent.DOM_VK_SPACE) { + // Space toggles the checkbox. + let newValue = !this.getCellValue( + index, + tree.columns.getNamedColumn("engineShown") + ); + this.setCellValue( + index, + tree.columns.getFirstColumn(), + newValue.toString() + ); + // Prevent page from scrolling on the space key. + aEvent.preventDefault(); + } else { + let isMac = Services.appinfo.OS == "Darwin"; + if ( + (isMac && aEvent.keyCode == KeyEvent.DOM_VK_RETURN) || + (!isMac && aEvent.keyCode == KeyEvent.DOM_VK_F2) + ) { + this.#startEditingAlias(index); + } else if ( + aEvent.keyCode == KeyEvent.DOM_VK_DELETE || + (isMac && + aEvent.shiftKey && + aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE && + this.isEngineSelectedAndRemovable()) + ) { + // Delete and Shift+Backspace (Mac) removes selected engine. + Services.search.removeEngine(this.selectedEngine.originalEngine); + } + } + } + + /** + * Triggers editing of an alias in the tree. + * + * @param {number} index + */ + #startEditingAlias(index) { + // Local shortcut aliases can't be edited. + if (this._getLocalShortcut(index)) { + return; + } + + let tree = document.getElementById("engineList"); + let engine = this._engineStore.engines[index]; + tree.startEditing(index, tree.columns.getLastColumn()); + tree.inputField.value = engine.alias || ""; + tree.inputField.select(); + } // nsITreeView get rowCount() { return ( this._engineStore.engines.length + UrlbarUtils.LOCAL_SEARCH_MODES.length ); - }, + } getImageSrc(index, column) { if (column.id == "engineName") { @@ -1157,18 +1161,11 @@ EngineView.prototype = { return shortcut.icon; } - if (this._engineStore.engines[index].iconURL) { - return this._engineStore.engines[index].iconURL; - } - - if (window.devicePixelRatio > 1) { - return "chrome://browser/skin/search-engine-placeholder@2x.png"; - } - return "chrome://browser/skin/search-engine-placeholder.png"; + return this._engineStore.engines[index].iconURL; } return ""; - }, + } getCellText(index, column) { if (column.id == "engineName") { @@ -1185,11 +1182,11 @@ EngineView.prototype = { return this._engineStore.engines[index].originalEngine.aliases.join(", "); } return ""; - }, + } setTree(tree) { this.tree = tree; - }, + } canDrop(targetIndex, orientation, dataTransfer) { var sourceIndex = this.getSourceIndexFromDrag(dataTransfer); @@ -1200,7 +1197,7 @@ EngineView.prototype = { // Local shortcut rows can't be dragged or dropped on. targetIndex < this._engineStore.engines.length ); - }, + } async drop(dropIndex, orientation, dataTransfer) { // Local shortcut rows can't be dragged or dropped on. This can sometimes @@ -1223,17 +1220,16 @@ EngineView.prototype = { await this._engineStore.moveEngine(sourceEngine, dropIndex); gSearchPane.showRestoreDefaults(true); - gSearchPane.buildDefaultEngineDropDowns(); // Redraw, and adjust selection this.invalidate(); this.selection.select(dropIndex); - }, + } - selection: null, - getRowProperties(index) { + selection = null; + getRowProperties() { return ""; - }, + } getCellProperties(index, column) { if (column.id == "engineName") { // For local shortcut rows, return the result source name so we can style @@ -1244,34 +1240,34 @@ EngineView.prototype = { } } return ""; - }, - getColumnProperties(column) { + } + getColumnProperties() { return ""; - }, - isContainer(index) { + } + isContainer() { return false; - }, - isContainerOpen(index) { + } + isContainerOpen() { return false; - }, - isContainerEmpty(index) { + } + isContainerEmpty() { return false; - }, - isSeparator(index) { + } + isSeparator() { return false; - }, - isSorted(index) { + } + isSorted() { return false; - }, - getParentIndex(index) { + } + getParentIndex() { return -1; - }, - hasNextSibling(parentIndex, index) { + } + hasNextSibling() { return false; - }, - getLevel(index) { + } + getLevel() { return 0; - }, + } getCellValue(index, column) { if (column.id == "engineShown") { let shortcut = this._getLocalShortcut(index); @@ -1281,17 +1277,17 @@ EngineView.prototype = { return !this._engineStore.engines[index].originalEngine.hideOneOffButton; } return undefined; - }, - toggleOpenState(index) {}, - cycleHeader(column) {}, - selectionChanged() {}, - cycleCell(row, column) {}, + } + toggleOpenState() {} + cycleHeader() {} + selectionChanged() {} + cycleCell() {} isEditable(index, column) { return ( column.id != "engineName" && (column.id == "engineShown" || !this._getLocalShortcut(index)) ); - }, + } setCellValue(index, column, value) { if (column.id == "engineShown") { let shortcut = this._getLocalShortcut(index); @@ -1302,18 +1298,153 @@ EngineView.prototype = { } this._engineStore.engines[index].originalEngine.hideOneOffButton = value != "true"; - gEngineView.invalidate(); + this.invalidate(); } - }, + } setCellText(index, column, value) { if (column.id == "engineKeyword") { - gSearchPane - .editKeyword(this._engineStore.engines[index], value) - .then(valid => { + this.#changeKeyword(this._engineStore.engines[index], value).then( + valid => { if (!valid) { - gSearchPane.startEditingAlias(index); + this.#startEditingAlias(index); } - }); + } + ); } - }, -}; + } + + /** + * Handles changing the keyword for an engine. This will check for potentially + * duplicate keywords and prompt the user if necessary. + * + * @param {object} aEngine + * The engine to change. + * @param {string} aNewKeyword + * The new keyword. + * @returns {Promise<boolean>} + * Resolves to true if the keyword was changed. + */ + async #changeKeyword(aEngine, aNewKeyword) { + let keyword = aNewKeyword.trim(); + if (keyword) { + let eduplicate = false; + let dupName = ""; + + // Check for duplicates in Places keywords. + let bduplicate = !!(await PlacesUtils.keywords.fetch(keyword)); + + // Check for duplicates in changes we haven't committed yet + let engines = this._engineStore.engines; + let lc_keyword = keyword.toLocaleLowerCase(); + for (let engine of engines) { + if ( + engine.alias && + engine.alias.toLocaleLowerCase() == lc_keyword && + engine.name != aEngine.name + ) { + eduplicate = true; + dupName = engine.name; + break; + } + } + + // Notify the user if they have chosen an existing engine/bookmark keyword + if (eduplicate || bduplicate) { + let msgids = [{ id: "search-keyword-warning-title" }]; + if (eduplicate) { + msgids.push({ + id: "search-keyword-warning-engine", + args: { name: dupName }, + }); + } else { + msgids.push({ id: "search-keyword-warning-bookmark" }); + } + + let [dtitle, msg] = await document.l10n.formatValues(msgids); + + Services.prompt.alert(window, dtitle, msg); + return false; + } + } + + this._engineStore.changeEngine(aEngine, "alias", keyword); + this.invalidate(); + return true; + } +} + +/** + * Manages the default engine dropdown buttons in the search pane of preferences. + */ +class DefaultEngineDropDown { + #element = null; + #type = null; + + constructor(type, engineStore) { + this.#type = type; + this.#element = document.getElementById( + type == "private" ? "defaultPrivateEngine" : "defaultEngine" + ); + + engineStore.addListener(this); + } + + rowCountChanged(index, count, enginesList) { + // Simply rebuild the menulist, rather than trying to update the changed row. + this.rebuild(enginesList); + } + + defaultEngineChanged(type, engine, enginesList) { + if (type != this.#type) { + return; + } + // If the user is going through the drop down using up/down keys, the + // dropdown may still be open (eg. on Windows) when engine-default is + // fired, so rebuilding the list unconditionally would get in the way. + let selectedEngineName = this.#element.selectedItem?.engine?.name; + if (selectedEngineName != engine.name) { + this.rebuild(enginesList); + } + } + + engineIconUpdated(index, enginesList) { + let item = this.#element.getItemAtIndex(index); + // Check this is the right item. + if (item?.label == enginesList[index].name) { + item.setAttribute("image", enginesList[index].iconURL); + } + } + + async rebuild(enginesList) { + if ( + this.#type == "private" && + !gSearchPane._separatePrivateDefaultPref.value + ) { + return; + } + let defaultEngine = await Services.search[ + this.#type == "normal" ? "getDefault" : "getDefaultPrivate" + ](); + + this.#element.removeAllItems(); + for (let engine of enginesList) { + let item = this.#element.appendItem(engine.name); + item.setAttribute( + "class", + "menuitem-iconic searchengine-menuitem menuitem-with-favicon" + ); + if (engine.iconURL) { + item.setAttribute("image", engine.iconURL); + } + item.engine = engine; + if (engine.name == defaultEngine.name) { + this.#element.selectedItem = item; + } + } + // This should never happen, but try and make sure we have at least one + // selected item. + if (!this.#element.selectedItem) { + this.#element.selectedIndex = 0; + } + } +} diff --git a/browser/components/preferences/sync.js b/browser/components/preferences/sync.js index 11a6a42e2e..fa3f333e8b 100644 --- a/browser/components/preferences/sync.js +++ b/browser/components/preferences/sync.js @@ -69,7 +69,7 @@ var gSyncPane = { xps.ensureLoaded(); }, - _showLoadPage(xps) { + _showLoadPage() { let maybeAcct = false; let username = Services.prefs.getCharPref("services.sync.username", ""); if (username) { diff --git a/browser/components/preferences/tests/browser.toml b/browser/components/preferences/tests/browser.toml index fe523bde94..9e619ce4be 100644 --- a/browser/components/preferences/tests/browser.toml +++ b/browser/components/preferences/tests/browser.toml @@ -11,6 +11,8 @@ support-files = [ "addons/set_newtab.xpi", ] +["browser_about_settings.js"] + ["browser_advanced_update.js"] skip-if = ["!updater"] @@ -82,6 +84,8 @@ skip-if = ["socketprocess_networking"] ["browser_defaultbrowser_alwayscheck.js"] +["browser_dns_over_https_exceptions_subdialog.js"] + ["browser_engines.js"] fail-if = ["a11y_checks"] # Bug 1854636 clicked treechildren#engineChildren may not be focusable diff --git a/browser/components/preferences/tests/browser_about_settings.js b/browser/components/preferences/tests/browser_about_settings.js new file mode 100644 index 0000000000..3ffd75eeef --- /dev/null +++ b/browser/components/preferences/tests/browser_about_settings.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_openPreferences_aboutSettings() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:settings", + }, + async () => { + is( + gBrowser.currentURI.spec, + "about:settings", + "about:settings should open normally" + ); + + // using `openPreferencesViaOpenPreferencesAPI` would introduce an extra about:blank tab we need to take care of + await openPreferences("paneGeneral"); + + is( + gBrowser.currentURI.spec, + "about:settings#general", + "openPreferences should keep about:settings" + ); + } + ); +}); diff --git a/browser/components/preferences/tests/browser_basic_rebuild_fonts_test.js b/browser/components/preferences/tests/browser_basic_rebuild_fonts_test.js index 51998db5a9..4491373c75 100644 --- a/browser/components/preferences/tests/browser_basic_rebuild_fonts_test.js +++ b/browser/components/preferences/tests/browser_basic_rebuild_fonts_test.js @@ -109,7 +109,7 @@ add_task(async function () { win.FontBuilder._enumerator = { _list: ["MockedFont1", "MockedFont2", "MockedFont3"], _defaultFont: null, - EnumerateFontsAsync(lang, type) { + EnumerateFontsAsync() { return Promise.resolve(this._list); }, EnumerateAllFontsAsync() { diff --git a/browser/components/preferences/tests/browser_browser_languages_subdialog.js b/browser/components/preferences/tests/browser_browser_languages_subdialog.js index 1941fb3502..9faa6d2969 100644 --- a/browser/components/preferences/tests/browser_browser_languages_subdialog.js +++ b/browser/components/preferences/tests/browser_browser_languages_subdialog.js @@ -194,7 +194,7 @@ function assertTelemetryRecorded(events) { // Only look at the related events after stripping the timestamp and category. let relatedEvents = snapshot.parent - .filter(([timestamp, category]) => category == TELEMETRY_CATEGORY) + .filter(([, category]) => category == TELEMETRY_CATEGORY) .map(relatedEvent => relatedEvent.slice(2, 6)); // Events are now an array of: method, object[, value[, extra]] as expected. @@ -297,7 +297,7 @@ add_task(async function testDisabledBrowserLanguages() { // Search for more languages. available.menupopup.lastElementChild.doCommand(); available.menupopup.hidePopup(); - await waitForMutation(available.menupopup, { childList: true }, target => + await waitForMutation(available.menupopup, { childList: true }, () => Array.from(available.menupopup.children).some( locale => locale.value == "pl" ) @@ -352,7 +352,7 @@ add_task(async function testReorderingBrowserLanguages() { // Install all the available langpacks. let langpacks = await createTestLangpacks(); let addons = await Promise.all( - langpacks.map(async ([locale, file]) => { + langpacks.map(async ([, file]) => { let install = await AddonTestUtils.promiseInstallFile(file); return install.addon; }) @@ -458,7 +458,7 @@ add_task(async function testAddAndRemoveSelectedLanguages() { let langpacks = await createTestLangpacks(); let addons = await Promise.all( - langpacks.map(async ([locale, file]) => { + langpacks.map(async ([, file]) => { let install = await AddonTestUtils.promiseInstallFile(file); return install.addon; }) @@ -603,7 +603,7 @@ add_task(async function testInstallFromAMO() { await waitForMutation( available.menupopup, { childList: true }, - target => available.itemCount > 1 + () => available.itemCount > 1 ); } @@ -685,7 +685,7 @@ add_task(async function testInstallFromAMO() { await waitForMutation( available.menupopup, { childList: true }, - target => available.itemCount > 1 + () => available.itemCount > 1 ); } assertLocaleOrder(selected, "en-US"); @@ -793,7 +793,7 @@ add_task(async function testReorderMainPane() { let langpacks = await createTestLangpacks(); let addons = await Promise.all( - langpacks.map(async ([locale, file]) => { + langpacks.map(async ([, file]) => { let install = await AddonTestUtils.promiseInstallFile(file); return install.addon; }) @@ -860,7 +860,7 @@ add_task(async function testLiveLanguageReloading() { let langpacks = await createTestLangpacks(); let addons = await Promise.all( - langpacks.map(async ([locale, file]) => { + langpacks.map(async ([, file]) => { let install = await AddonTestUtils.promiseInstallFile(file); return install.addon; }) @@ -928,7 +928,7 @@ add_task(async function testLiveLanguageReloadingBidiOff() { let langpacks = await createTestLangpacks(); let addons = await Promise.all( - langpacks.map(async ([locale, file]) => { + langpacks.map(async ([, file]) => { let install = await AddonTestUtils.promiseInstallFile(file); return install.addon; }) @@ -1006,7 +1006,7 @@ add_task(async function testLiveLanguageReloadingBidiOn() { let langpacks = await createTestLangpacks(); let addons = await Promise.all( - langpacks.map(async ([locale, file]) => { + langpacks.map(async ([, file]) => { let install = await AddonTestUtils.promiseInstallFile(file); return install.addon; }) diff --git a/browser/components/preferences/tests/browser_cert_export.js b/browser/components/preferences/tests/browser_cert_export.js index 48769f84e6..f37cdec767 100644 --- a/browser/components/preferences/tests/browser_cert_export.js +++ b/browser/components/preferences/tests/browser_cert_export.js @@ -78,7 +78,7 @@ async function checkCertExportWorks( ) { MockFilePicker.displayDirectory = destDir; var destFile = destDir.clone(); - MockFilePicker.init(window); + MockFilePicker.init(window.browsingContext); MockFilePicker.filterIndex = exportType; MockFilePicker.showCallback = function (fp) { info("showCallback"); diff --git a/browser/components/preferences/tests/browser_connection.js b/browser/components/preferences/tests/browser_connection.js index 01cdf571f2..896361b16b 100644 --- a/browser/components/preferences/tests/browser_connection.js +++ b/browser/components/preferences/tests/browser_connection.js @@ -26,7 +26,7 @@ function test() { so it has to be opened as a sub dialog of the main pref tab. Open the main tab here. */ - open_preferences(async function tabOpened(aContentWindow) { + open_preferences(async function tabOpened() { is( gBrowser.currentURI.spec, "about:preferences", diff --git a/browser/components/preferences/tests/browser_connection_bug388287.js b/browser/components/preferences/tests/browser_connection_bug388287.js index d6f0c3c9d0..87d41a22c1 100644 --- a/browser/components/preferences/tests/browser_connection_bug388287.js +++ b/browser/components/preferences/tests/browser_connection_bug388287.js @@ -37,7 +37,7 @@ function test() { so it has to be opened as a sub dialog of the main pref tab. Open the main tab here. */ - open_preferences(async function tabOpened(aContentWindow) { + open_preferences(async function tabOpened() { let dialog, dialogClosingPromise, dialogElement; let proxyTypePref, sharePref, httpPref, httpPortPref; diff --git a/browser/components/preferences/tests/browser_cookies_exceptions.js b/browser/components/preferences/tests/browser_cookies_exceptions.js index d2d538a48a..03e29d0b4c 100644 --- a/browser/components/preferences/tests/browser_cookies_exceptions.js +++ b/browser/components/preferences/tests/browser_cookies_exceptions.js @@ -19,7 +19,7 @@ add_task(async function testAllow() { apply(); await observeAllPromise; }, - params => { + () => { return [ { type: "cookie", @@ -52,7 +52,7 @@ add_task(async function testBlock() { apply(); await observeAllPromise; }, - params => { + () => { return [ { type: "cookie", @@ -85,7 +85,7 @@ add_task(async function testAllowAgain() { apply(); await observeAllPromise; }, - params => { + () => { return [ { type: "cookie", @@ -152,7 +152,7 @@ add_task(async function testAdd() { PermissionTestUtils.remove(uri, "popup"); }, - params => { + () => { return [ { type: "popup", @@ -178,7 +178,7 @@ add_task(async function testAllowHTTPSWithPort() { apply(); await observeAllPromise; }, - params => { + () => { return [ { type: "cookie", @@ -204,7 +204,7 @@ add_task(async function testBlockHTTPSWithPort() { apply(); await observeAllPromise; }, - params => { + () => { return [ { type: "cookie", @@ -230,7 +230,7 @@ add_task(async function testAllowAgainHTTPSWithPort() { apply(); await observeAllPromise; }, - params => { + () => { return [ { type: "cookie", @@ -288,7 +288,7 @@ add_task(async function testAllowPort() { apply(); await observeAllPromise; }, - params => { + () => { return [ { type: "cookie", @@ -321,7 +321,7 @@ add_task(async function testBlockPort() { apply(); await observeAllPromise; }, - params => { + () => { return [ { type: "cookie", @@ -354,7 +354,7 @@ add_task(async function testAllowAgainPort() { apply(); await observeAllPromise; }, - params => { + () => { return [ { type: "cookie", @@ -450,7 +450,7 @@ add_task(async function testSort() { PermissionTestUtils.remove(uri, "cookie"); } }, - params => { + () => { return [ { type: "cookie", @@ -477,7 +477,7 @@ add_task(async function testSort() { add_task(async function testPrivateBrowsingSessionPermissionsAreHidden() { await runTest( - async (params, observeAllPromise, apply) => { + async params => { assertListContents(params, []); let uri = Services.io.newURI("http://test.com"); @@ -498,7 +498,7 @@ add_task(async function testPrivateBrowsingSessionPermissionsAreHidden() { PermissionTestUtils.remove(uri, "cookie"); }, - params => { + () => { return []; } ); diff --git a/browser/components/preferences/tests/browser_dns_over_https_exceptions_subdialog.js b/browser/components/preferences/tests/browser_dns_over_https_exceptions_subdialog.js new file mode 100644 index 0000000000..c24c13e9e8 --- /dev/null +++ b/browser/components/preferences/tests/browser_dns_over_https_exceptions_subdialog.js @@ -0,0 +1,185 @@ +async function dohExceptionsSubdialogOpened(dialogOverlay) { + const promiseSubDialogLoaded = promiseLoadSubDialog( + "chrome://browser/content/preferences/dialogs/dohExceptions.xhtml" + ); + const contentDocument = gBrowser.contentDocument; + contentDocument.getElementById("dohExceptionsButton").click(); + const win = await promiseSubDialogLoaded; + dialogOverlay = content.gSubDialog._topDialog._overlay; + ok(!BrowserTestUtils.isHidden(dialogOverlay), "The dialog is visible."); + return win; +} + +function acceptDoHExceptionsSubdialog(win) { + const button = win.document.querySelector("dialog").getButton("accept"); + button.doCommand(); +} + +function cancelDoHExceptionsSubdialog(win) { + const button = win.document.querySelector("dialog").getButton("cancel"); + button.doCommand(); +} + +function addNewException(domain, dialog) { + let url = dialog.document.getElementById("url"); + let addButton = dialog.document.getElementById("btnAddException"); + + ok( + addButton.disabled, + "The Add button is disabled when domain's input box is empty" + ); + + url.focus(); + EventUtils.sendString(domain); + + ok( + !addButton.disabled, + "The Add button is enabled when some text is on domain's input box" + ); + + addButton.click(); + + is( + url.value, + "", + "Domain input box is empty after adding a new domain to the list" + ); + ok( + addButton.disabled, + "The Add button is disabled after exception has been added to the list" + ); +} + +add_task(async function () { + Services.prefs.lockPref("network.trr.excluded-domains"); + + await openPreferencesViaOpenPreferencesAPI("panePrivacy", { + leaveOpen: true, + }); + let dialogOverlay = content.gSubDialog._preloadDialog._overlay; + let win = await dohExceptionsSubdialogOpened(dialogOverlay); + + ok( + win.document.getElementById("btnAddException").disabled, + "The Add button is disabled when preference is locked" + ); + ok( + win.document.getElementById("url").disabled, + "The url input box is disabled when preference is locked" + ); + + cancelDoHExceptionsSubdialog(win); + Services.prefs.unlockPref("network.trr.excluded-domains"); + win = await dohExceptionsSubdialogOpened(dialogOverlay); + + ok( + win.document.getElementById("btnAddException").disabled, + "The Add button is disabled when preference is not locked" + ); + ok( + !win.document.getElementById("url").disabled, + "The url input box is enabled when preference is not locked" + ); + + cancelDoHExceptionsSubdialog(win); + gBrowser.removeCurrentTab(); +}); + +add_task(async function () { + await openPreferencesViaOpenPreferencesAPI("panePrivacy", { + leaveOpen: true, + }); + let dialogOverlay = content.gSubDialog._preloadDialog._overlay; + + ok(BrowserTestUtils.isHidden(dialogOverlay), "The dialog is invisible."); + let win = await dohExceptionsSubdialogOpened(dialogOverlay); + acceptDoHExceptionsSubdialog(win); + ok(BrowserTestUtils.isHidden(dialogOverlay), "The dialog is invisible."); + + win = await dohExceptionsSubdialogOpened(dialogOverlay); + Assert.equal( + win.document.getElementById("permissionsBox").itemCount, + 0, + "There are no exceptions set." + ); + ok( + win.document.getElementById("removeException").disabled, + "The Remove button is disabled when there are no exceptions on the list" + ); + ok( + win.document.getElementById("removeAllExceptions").disabled, + "The Remove All button is disabled when there are no exceptions on the list" + ); + ok( + win.document.getElementById("btnAddException").disabled, + "The Add button is disabled when dialog box has just been opened" + ); + + addNewException("test1.com", win); + Assert.equal( + win.document.getElementById("permissionsBox").itemCount, + 1, + "List shows 1 new item" + ); + let activeExceptions = win.document.getElementById("permissionsBox").children; + is( + activeExceptions[0].getAttribute("domain"), + "test1.com", + "test1.com added to the list" + ); + ok( + !win.document.getElementById("removeAllExceptions").disabled, + "The Remove All button is enabled when there is one exception on the list" + ); + addNewException("test2.com", win); + addNewException("test3.com", win); + Assert.equal( + win.document.getElementById("permissionsBox").itemCount, + 3, + "List shows 3 domain items" + ); + ok( + win.document.getElementById("removeException").disabled, + "The Remove button is disabled when no exception has been selected" + ); + win.document.getElementById("permissionsBox").selectedIndex = 1; + ok( + !win.document.getElementById("removeException").disabled, + "The Remove button is enabled when an exception has been selected" + ); + win.document.getElementById("removeException").doCommand(); + Assert.equal( + win.document.getElementById("permissionsBox").itemCount, + 2, + "List shows 2 domain items after removing one of the three" + ); + activeExceptions = win.document.getElementById("permissionsBox").children; + ok( + win.document.getElementById("permissionsBox").itemCount == 2 && + activeExceptions[0].getAttribute("domain") == "test1.com" && + activeExceptions[1].getAttribute("domain") == "test3.com", + "test1.com and test3.com are the only items left on the list" + ); + is( + win.document.getElementById("permissionsBox").selectedIndex, + -1, + "There is no selected item after removal" + ); + addNewException("test2.com", win); + activeExceptions = win.document.getElementById("permissionsBox").children; + ok( + win.document.getElementById("permissionsBox").itemCount == 3 && + activeExceptions[1].getAttribute("domain") == "test2.com", + "test2.com has been added as the second item" + ); + win.document.getElementById("removeAllExceptions").doCommand(); + is( + win.document.getElementById("permissionsBox").itemCount, + 0, + "There are no elements on the list after clicking Remove All" + ); + + acceptDoHExceptionsSubdialog(win); + + gBrowser.removeCurrentTab(); +}); diff --git a/browser/components/preferences/tests/browser_fluent.js b/browser/components/preferences/tests/browser_fluent.js index db2daecc4f..ae83abecf0 100644 --- a/browser/components/preferences/tests/browser_fluent.js +++ b/browser/components/preferences/tests/browser_fluent.js @@ -1,7 +1,7 @@ function whenMainPaneLoadedFinished() { - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { const topic = "main-pane-loaded"; - Services.obs.addObserver(function observer(aSubject) { + Services.obs.addObserver(function observer() { Services.obs.removeObserver(observer, topic); resolve(); }, topic); diff --git a/browser/components/preferences/tests/browser_localSearchShortcuts.js b/browser/components/preferences/tests/browser_localSearchShortcuts.js index 0b8e170cc1..7f0f4869b7 100644 --- a/browser/components/preferences/tests/browser_localSearchShortcuts.js +++ b/browser/components/preferences/tests/browser_localSearchShortcuts.js @@ -36,7 +36,7 @@ add_setup(async function () { // The rows should be visible and checked by default. add_task(async function visible() { await checkRowVisibility(true); - await forEachLocalShortcutRow(async (row, shortcut) => { + await forEachLocalShortcutRow(async row => { Assert.equal( gTree.view.getCellValue(row, gTree.columns.getNamedColumn("engineShown")), "true", @@ -136,7 +136,7 @@ add_task(async function syncToPrefs_click() { // The keyword column should not be editable according to isEditable(). add_task(async function keywordNotEditable_isEditable() { - await forEachLocalShortcutRow(async (row, shortcut) => { + await forEachLocalShortcutRow(async row => { Assert.ok( !gTree.view.isEditable( row, diff --git a/browser/components/preferences/tests/browser_permissions_urlFieldHidden.js b/browser/components/preferences/tests/browser_permissions_urlFieldHidden.js index 537ee3db72..dea9762861 100644 --- a/browser/components/preferences/tests/browser_permissions_urlFieldHidden.js +++ b/browser/components/preferences/tests/browser_permissions_urlFieldHidden.js @@ -3,7 +3,7 @@ const PERMISSIONS_URL = "chrome://browser/content/preferences/dialogs/permissions.xhtml"; -add_task(async function urlFieldVisibleForPopupPermissions(finish) { +add_task(async function urlFieldVisibleForPopupPermissions() { await openPreferencesViaOpenPreferencesAPI("panePrivacy", { leaveOpen: true, }); diff --git a/browser/components/preferences/tests/browser_proxy_backup.js b/browser/components/preferences/tests/browser_proxy_backup.js index fc05e19ada..e4ce5a99bd 100644 --- a/browser/components/preferences/tests/browser_proxy_backup.js +++ b/browser/components/preferences/tests/browser_proxy_backup.js @@ -47,7 +47,7 @@ function test() { so it has to be opened as a sub dialog of the main pref tab. Open the main tab here. */ - open_preferences(async function tabOpened(aContentWindow) { + open_preferences(async function tabOpened() { is( gBrowser.currentURI.spec, "about:preferences", diff --git a/browser/components/preferences/tests/browser_searchChangedEngine.js b/browser/components/preferences/tests/browser_searchChangedEngine.js index 0882c9775e..22baba8dd6 100644 --- a/browser/components/preferences/tests/browser_searchChangedEngine.js +++ b/browser/components/preferences/tests/browser_searchChangedEngine.js @@ -46,6 +46,10 @@ add_task(async function test_change_engine() { let row = findRow(tree, "Example"); Assert.notEqual(row, -1, "Should have found the entry"); + await TestUtils.waitForCondition( + () => tree.view.getImageSrc(row, tree.columns.getNamedColumn("engineName")), + "Should have go an image URL" + ); Assert.ok( tree.view .getImageSrc(row, tree.columns.getNamedColumn("engineName")) @@ -74,6 +78,13 @@ add_task(async function test_change_engine() { row = findRow(tree, "Example 2"); Assert.notEqual(row, -1, "Should have found the updated entry"); + await TestUtils.waitForCondition( + () => + tree.view + .getImageSrc(row, tree.columns.getNamedColumn("engineName")) + ?.includes("img456.png"), + "Should have updated the image URL" + ); Assert.ok( tree.view .getImageSrc(row, tree.columns.getNamedColumn("engineName")) diff --git a/browser/components/preferences/tests/browser_search_within_preferences_2.js b/browser/components/preferences/tests/browser_search_within_preferences_2.js index 6de068fbe4..5fcb445adc 100644 --- a/browser/components/preferences/tests/browser_search_within_preferences_2.js +++ b/browser/components/preferences/tests/browser_search_within_preferences_2.js @@ -178,3 +178,87 @@ add_task(async function () { await BrowserTestUtils.removeTab(gBrowser.selectedTab); }); + +/** + * Test that search works as expected for custom elements that utilize both + * slots and shadow DOM. We should be able to find text the shadow DOM. + */ +add_task(async function testSearchShadowDOM() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", { + leaveOpen: true, + }); + + // Create the toggle. + let { toggle, SHADOW_DOM_TEXT } = createToggle(gBrowser); + + ok( + !BrowserTestUtils.isVisible(toggle), + "Toggle is not visible prior to search." + ); + + // Perform search with text found in moz-toggle's shadow DOM. + let query = SHADOW_DOM_TEXT; + let searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, + "PreferencesSearchCompleted", + evt => evt.detail == query + ); + EventUtils.sendString(query); + await searchCompletedPromise; + ok( + BrowserTestUtils.isVisible(toggle), + "Toggle is visible after searching for string in the shadow DOM." + ); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +/** + * Test that search works as expected for custom elements that utilize both + * slots and shadow DOM. We should be able to find text the light DOM. + */ +add_task(async function testSearchLightDOM() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", { + leaveOpen: true, + }); + + // Create the toggle. + let { toggle, LIGHT_DOM_TEXT } = createToggle(gBrowser); + + // Perform search with text found in moz-toggle's slotted content. + let query = LIGHT_DOM_TEXT; + let searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, + "PreferencesSearchCompleted", + evt => evt.detail == query + ); + EventUtils.sendString(query); + await searchCompletedPromise; + ok( + BrowserTestUtils.isVisible(toggle), + "Toggle is visible again after searching for text found in slotted content." + ); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Create a toggle with a slotted link element. +function createToggle(gBrowser) { + const SHADOW_DOM_TEXT = "This text lives in the shadow DOM"; + const LIGHT_DOM_TEXT = "This text lives in the light DOM"; + + let doc = gBrowser.contentDocument; + let toggle = doc.createElement("moz-toggle"); + toggle.label = SHADOW_DOM_TEXT; + + let link = doc.createElement("a"); + link.href = "https://mozilla.org/"; + link.textContent = LIGHT_DOM_TEXT; + toggle.append(link); + link.slot = "support-link"; + + let protectionsGroup = doc.getElementById("trackingGroup"); + protectionsGroup.append(toggle); + + return { SHADOW_DOM_TEXT, LIGHT_DOM_TEXT, toggle }; +} diff --git a/browser/components/preferences/tests/browser_sync_chooseWhatToSync.js b/browser/components/preferences/tests/browser_sync_chooseWhatToSync.js index b36d9ecea3..3ff65a0d93 100644 --- a/browser/components/preferences/tests/browser_sync_chooseWhatToSync.js +++ b/browser/components/preferences/tests/browser_sync_chooseWhatToSync.js @@ -167,7 +167,7 @@ add_task(async function testDialogLaunchFromURI() { ); await BrowserTestUtils.withNewTab( "about:preferences?action=choose-what-to-sync#sync", - async browser => { + async () => { let dialogEvent = await dialogEventPromise; Assert.equal( dialogEvent.detail.dialog._frame.contentWindow.location, diff --git a/browser/components/preferences/tests/browser_sync_pairing.js b/browser/components/preferences/tests/browser_sync_pairing.js index 6491007a38..39f7b547d8 100644 --- a/browser/components/preferences/tests/browser_sync_pairing.js +++ b/browser/components/preferences/tests/browser_sync_pairing.js @@ -29,7 +29,7 @@ add_setup(async function () { }; const origStart = FxAccountsPairingFlow.start; - FxAccountsPairingFlow.start = ({ emitter: e }) => { + FxAccountsPairingFlow.start = () => { return `https://foo.bar/${flowCounter++}`; }; diff --git a/browser/components/preferences/tests/browser_trendingsuggestions.js b/browser/components/preferences/tests/browser_trendingsuggestions.js index 1cfae387cf..81285b7f6f 100644 --- a/browser/components/preferences/tests/browser_trendingsuggestions.js +++ b/browser/components/preferences/tests/browser_trendingsuggestions.js @@ -78,3 +78,20 @@ add_task(async function testNonTrendingEngine() { ); gBrowser.removeCurrentTab(); }); + +add_task(async function testEnabledTrendingEngine() { + const engine1 = Services.search.getEngineByName("Google"); + Services.search.setDefault( + engine1, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true }); + let doc = gBrowser.selectedBrowser.contentDocument; + let trendingCheckbox = doc.getElementById(TRENDING_CHECKBOX_ID); + + Assert.ok( + !trendingCheckbox.disabled, + "Checkbox should not be disabled when an engine that supports trending suggestions is default" + ); + gBrowser.removeCurrentTab(); +}); diff --git a/browser/components/preferences/tests/head.js b/browser/components/preferences/tests/head.js index 1861c040e5..f9e5b10c09 100644 --- a/browser/components/preferences/tests/head.js +++ b/browser/components/preferences/tests/head.js @@ -45,7 +45,7 @@ function openAndLoadSubDialog( } function promiseLoadSubDialog(aURL) { - return new Promise((resolve, reject) => { + return new Promise(resolve => { content.gSubDialog._dialogStack.addEventListener( "dialogopen", function dialogopen(aEvent) { diff --git a/browser/components/preferences/tests/siteData/browser_clearSiteData.js b/browser/components/preferences/tests/siteData/browser_clearSiteData.js index ad1d27bfe2..7ae1fda453 100644 --- a/browser/components/preferences/tests/siteData/browser_clearSiteData.js +++ b/browser/components/preferences/tests/siteData/browser_clearSiteData.js @@ -137,7 +137,7 @@ async function testClearData(clearSiteData, clearCache) { let clearButton = dialogWin.document .querySelector("dialog") .getButton("accept"); - if (!clearSiteData && !clearCache) { + if (!clearSiteData && !clearCache && useOldClearHistoryDialog) { // Simulate user input on one of the checkboxes to trigger the event listener for // disabling the clearButton. clearCacheCheckbox.doCommand(); diff --git a/browser/components/preferences/tests/siteData/head.js b/browser/components/preferences/tests/siteData/head.js index 2b8a9984a6..6385013a52 100644 --- a/browser/components/preferences/tests/siteData/head.js +++ b/browser/components/preferences/tests/siteData/head.js @@ -52,7 +52,7 @@ function is_element_hidden(aElement, aMsg) { } function promiseLoadSubDialog(aURL) { - return new Promise((resolve, reject) => { + return new Promise(resolve => { content.gSubDialog._dialogStack.addEventListener( "dialogopen", function dialogopen(aEvent) { diff --git a/browser/components/preferences/tests/siteData/service_worker_test.html b/browser/components/preferences/tests/siteData/service_worker_test.html index 56f5173481..710b61090f 100644 --- a/browser/components/preferences/tests/siteData/service_worker_test.html +++ b/browser/components/preferences/tests/siteData/service_worker_test.html @@ -13,7 +13,7 @@ <h1>Service Worker Test</h1> <script type="text/javascript"> navigator.serviceWorker.register("service_worker_test.js") - .then(regis => document.body.setAttribute("data-test-service-worker-registered", "true")); + .then(() => document.body.setAttribute("data-test-service-worker-registered", "true")); </script> </body> </html> diff --git a/browser/components/preferences/translations.inc.xhtml b/browser/components/preferences/translations.inc.xhtml index 5fed03da9b..9463fde707 100644 --- a/browser/components/preferences/translations.inc.xhtml +++ b/browser/components/preferences/translations.inc.xhtml @@ -4,9 +4,12 @@ <script src="chrome://browser/content/preferences/translations.js"/> -<div xmlns="http://www.w3.org/1999/xhtml" +<div id="translations-settings-page" + xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - data-category="paneTranslations"> + data-category="paneTranslations" + data-hidden-from-search="true" + hidden="true"> <button id="translations-settings-back-button" class="back-button" data-l10n-id="translations-settings-back-button"/> |