summaryrefslogtreecommitdiffstats
path: root/browser/components/preferences/tests
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/components/preferences/tests/addons/pl-dictionary.xpibin0 -> 793 bytes
-rw-r--r--browser/components/preferences/tests/addons/set_homepage.xpibin0 -> 5156 bytes
-rw-r--r--browser/components/preferences/tests/addons/set_newtab.xpibin0 -> 5210 bytes
-rw-r--r--browser/components/preferences/tests/browser.ini152
-rw-r--r--browser/components/preferences/tests/browser_advanced_update.js185
-rw-r--r--browser/components/preferences/tests/browser_application_xml_handle_internally.js49
-rw-r--r--browser/components/preferences/tests/browser_applications_selection.js403
-rw-r--r--browser/components/preferences/tests/browser_basic_rebuild_fonts_test.js235
-rw-r--r--browser/components/preferences/tests/browser_browser_languages_subdialog.js1058
-rw-r--r--browser/components/preferences/tests/browser_bug1018066_resetScrollPosition.js30
-rw-r--r--browser/components/preferences/tests/browser_bug1020245_openPreferences_to_paneContent.js163
-rw-r--r--browser/components/preferences/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js116
-rw-r--r--browser/components/preferences/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xhtml26
-rw-r--r--browser/components/preferences/tests/browser_bug1547020_lockedDownloadDir.js24
-rw-r--r--browser/components/preferences/tests/browser_bug1579418.js55
-rw-r--r--browser/components/preferences/tests/browser_bug410900.js47
-rw-r--r--browser/components/preferences/tests/browser_bug731866.js95
-rw-r--r--browser/components/preferences/tests/browser_bug795764_cachedisabled.js62
-rw-r--r--browser/components/preferences/tests/browser_cert_export.js161
-rw-r--r--browser/components/preferences/tests/browser_change_app_handler.js155
-rw-r--r--browser/components/preferences/tests/browser_checkspelling.js34
-rw-r--r--browser/components/preferences/tests/browser_connection.js145
-rw-r--r--browser/components/preferences/tests/browser_connection_bug1445991.js31
-rw-r--r--browser/components/preferences/tests/browser_connection_bug1505330.js31
-rw-r--r--browser/components/preferences/tests/browser_connection_bug388287.js124
-rw-r--r--browser/components/preferences/tests/browser_containers_name_input.js72
-rw-r--r--browser/components/preferences/tests/browser_contentblocking.js1382
-rw-r--r--browser/components/preferences/tests/browser_contentblocking_categories.js487
-rw-r--r--browser/components/preferences/tests/browser_contentblocking_standard_tcp_section.js148
-rw-r--r--browser/components/preferences/tests/browser_cookie_exceptions_addRemove.js299
-rw-r--r--browser/components/preferences/tests/browser_cookies_exceptions.js568
-rw-r--r--browser/components/preferences/tests/browser_defaultbrowser_alwayscheck.js185
-rw-r--r--browser/components/preferences/tests/browser_engines.js141
-rw-r--r--browser/components/preferences/tests/browser_etp_exceptions_dialog.js96
-rw-r--r--browser/components/preferences/tests/browser_experimental_features.js74
-rw-r--r--browser/components/preferences/tests/browser_experimental_features_filter.js183
-rw-r--r--browser/components/preferences/tests/browser_experimental_features_hidden_when_not_public.js86
-rw-r--r--browser/components/preferences/tests/browser_experimental_features_resetall.js112
-rw-r--r--browser/components/preferences/tests/browser_extension_controlled.js1447
-rw-r--r--browser/components/preferences/tests/browser_filetype_dialog.js189
-rw-r--r--browser/components/preferences/tests/browser_fluent.js40
-rw-r--r--browser/components/preferences/tests/browser_homepage_default.js31
-rw-r--r--browser/components/preferences/tests/browser_homepages_filter_aboutpreferences.js33
-rw-r--r--browser/components/preferences/tests/browser_homepages_use_bookmark.js94
-rw-r--r--browser/components/preferences/tests/browser_hometab_restore_defaults.js220
-rw-r--r--browser/components/preferences/tests/browser_https_only_exceptions.js279
-rw-r--r--browser/components/preferences/tests/browser_https_only_section.js74
-rw-r--r--browser/components/preferences/tests/browser_ignore_invalid_capability.js40
-rw-r--r--browser/components/preferences/tests/browser_languages_subdialog.js139
-rw-r--r--browser/components/preferences/tests/browser_layersacceleration.js36
-rw-r--r--browser/components/preferences/tests/browser_localSearchShortcuts.js309
-rw-r--r--browser/components/preferences/tests/browser_moreFromMozilla.js380
-rw-r--r--browser/components/preferences/tests/browser_moreFromMozilla_locales.js331
-rw-r--r--browser/components/preferences/tests/browser_newtab_menu.js38
-rw-r--r--browser/components/preferences/tests/browser_notifications_do_not_disturb.js57
-rw-r--r--browser/components/preferences/tests/browser_open_download_preferences.js288
-rw-r--r--browser/components/preferences/tests/browser_open_migration_wizard.js54
-rw-r--r--browser/components/preferences/tests/browser_password_management.js43
-rw-r--r--browser/components/preferences/tests/browser_pdf_disabled.js49
-rw-r--r--browser/components/preferences/tests/browser_performance.js300
-rw-r--r--browser/components/preferences/tests/browser_performance_content_process_limit.js52
-rw-r--r--browser/components/preferences/tests/browser_performance_e10srollout.js164
-rw-r--r--browser/components/preferences/tests/browser_performance_non_e10s.js210
-rw-r--r--browser/components/preferences/tests/browser_permissions_checkPermissionsWereAdded.js127
-rw-r--r--browser/components/preferences/tests/browser_permissions_dialog.js642
-rw-r--r--browser/components/preferences/tests/browser_permissions_dialog_default_perm.js145
-rw-r--r--browser/components/preferences/tests/browser_permissions_urlFieldHidden.js38
-rw-r--r--browser/components/preferences/tests/browser_primaryPassword.js130
-rw-r--r--browser/components/preferences/tests/browser_privacy_cookieBannerHandling.js210
-rw-r--r--browser/components/preferences/tests/browser_privacy_dnsoverhttps.js844
-rw-r--r--browser/components/preferences/tests/browser_privacy_firefoxSuggest.js855
-rw-r--r--browser/components/preferences/tests/browser_privacy_passwordGenerationAndAutofill.js199
-rw-r--r--browser/components/preferences/tests/browser_privacy_quickactions.js110
-rw-r--r--browser/components/preferences/tests/browser_privacy_relayIntegration.js251
-rw-r--r--browser/components/preferences/tests/browser_privacy_segmentation_pref.js131
-rw-r--r--browser/components/preferences/tests/browser_privacy_syncDataClearing.js287
-rw-r--r--browser/components/preferences/tests/browser_privacypane_2.js19
-rw-r--r--browser/components/preferences/tests/browser_privacypane_3.js21
-rw-r--r--browser/components/preferences/tests/browser_proxy_backup.js84
-rw-r--r--browser/components/preferences/tests/browser_sanitizeOnShutdown_prefLocked.js47
-rw-r--r--browser/components/preferences/tests/browser_searchChangedEngine.js90
-rw-r--r--browser/components/preferences/tests/browser_searchDefaultEngine.js372
-rw-r--r--browser/components/preferences/tests/browser_searchFindMoreLink.js36
-rw-r--r--browser/components/preferences/tests/browser_searchRestoreDefaults.js259
-rw-r--r--browser/components/preferences/tests/browser_searchScroll.js66
-rw-r--r--browser/components/preferences/tests/browser_searchShowSuggestionsFirst.js240
-rw-r--r--browser/components/preferences/tests/browser_search_no_results_change_category.js44
-rw-r--r--browser/components/preferences/tests/browser_search_searchTerms.js201
-rw-r--r--browser/components/preferences/tests/browser_search_subdialog_tooltip_saved_addresses.js39
-rw-r--r--browser/components/preferences/tests/browser_search_subdialogs_within_preferences_1.js48
-rw-r--r--browser/components/preferences/tests/browser_search_subdialogs_within_preferences_2.js36
-rw-r--r--browser/components/preferences/tests/browser_search_subdialogs_within_preferences_3.js35
-rw-r--r--browser/components/preferences/tests/browser_search_subdialogs_within_preferences_4.js39
-rw-r--r--browser/components/preferences/tests/browser_search_subdialogs_within_preferences_5.js46
-rw-r--r--browser/components/preferences/tests/browser_search_subdialogs_within_preferences_6.js35
-rw-r--r--browser/components/preferences/tests/browser_search_subdialogs_within_preferences_7.js34
-rw-r--r--browser/components/preferences/tests/browser_search_subdialogs_within_preferences_8.js45
-rw-r--r--browser/components/preferences/tests/browser_search_subdialogs_within_preferences_site_data.js45
-rw-r--r--browser/components/preferences/tests/browser_search_within_preferences_1.js344
-rw-r--r--browser/components/preferences/tests/browser_search_within_preferences_2.js180
-rw-r--r--browser/components/preferences/tests/browser_search_within_preferences_command.js45
-rw-r--r--browser/components/preferences/tests/browser_searchsuggestions.js128
-rw-r--r--browser/components/preferences/tests/browser_security-1.js106
-rw-r--r--browser/components/preferences/tests/browser_security-2.js177
-rw-r--r--browser/components/preferences/tests/browser_security-3.js130
-rw-r--r--browser/components/preferences/tests/browser_site_login_exceptions.js101
-rw-r--r--browser/components/preferences/tests/browser_site_login_exceptions_policy.js65
-rw-r--r--browser/components/preferences/tests/browser_spotlight.js72
-rw-r--r--browser/components/preferences/tests/browser_statePartitioning_PBM_strings.js124
-rw-r--r--browser/components/preferences/tests/browser_statePartitioning_strings.js79
-rw-r--r--browser/components/preferences/tests/browser_subdialogs.js639
-rw-r--r--browser/components/preferences/tests/browser_sync_chooseWhatToSync.js178
-rw-r--r--browser/components/preferences/tests/browser_sync_disabled.js26
-rw-r--r--browser/components/preferences/tests/browser_sync_pairing.js149
-rw-r--r--browser/components/preferences/tests/browser_warning_permanent_private_browsing.js57
-rw-r--r--browser/components/preferences/tests/empty_pdf_file.pdf0
-rw-r--r--browser/components/preferences/tests/engine1/manifest.json27
-rw-r--r--browser/components/preferences/tests/engine2/manifest.json27
-rw-r--r--browser/components/preferences/tests/head.js334
-rw-r--r--browser/components/preferences/tests/privacypane_tests_perwindow.js388
-rw-r--r--browser/components/preferences/tests/siteData/browser.ini22
-rw-r--r--browser/components/preferences/tests/siteData/browser_clearSiteData.js219
-rw-r--r--browser/components/preferences/tests/siteData/browser_siteData.js400
-rw-r--r--browser/components/preferences/tests/siteData/browser_siteData2.js475
-rw-r--r--browser/components/preferences/tests/siteData/browser_siteData3.js327
-rw-r--r--browser/components/preferences/tests/siteData/browser_siteData_multi_select.js119
-rw-r--r--browser/components/preferences/tests/siteData/head.js280
-rw-r--r--browser/components/preferences/tests/siteData/offline/manifest.appcache3
-rw-r--r--browser/components/preferences/tests/siteData/offline/offline.html13
-rw-r--r--browser/components/preferences/tests/siteData/service_worker_test.html19
-rw-r--r--browser/components/preferences/tests/siteData/service_worker_test.js1
-rw-r--r--browser/components/preferences/tests/siteData/site_data_test.html29
-rw-r--r--browser/components/preferences/tests/subdialog.xhtml29
-rw-r--r--browser/components/preferences/tests/subdialog2.xhtml29
134 files changed, 23302 insertions, 0 deletions
diff --git a/browser/components/preferences/tests/addons/pl-dictionary.xpi b/browser/components/preferences/tests/addons/pl-dictionary.xpi
new file mode 100644
index 0000000000..cc4da1fa83
--- /dev/null
+++ b/browser/components/preferences/tests/addons/pl-dictionary.xpi
Binary files differ
diff --git a/browser/components/preferences/tests/addons/set_homepage.xpi b/browser/components/preferences/tests/addons/set_homepage.xpi
new file mode 100644
index 0000000000..9aff671021
--- /dev/null
+++ b/browser/components/preferences/tests/addons/set_homepage.xpi
Binary files differ
diff --git a/browser/components/preferences/tests/addons/set_newtab.xpi b/browser/components/preferences/tests/addons/set_newtab.xpi
new file mode 100644
index 0000000000..f11db0b6a8
--- /dev/null
+++ b/browser/components/preferences/tests/addons/set_newtab.xpi
Binary files differ
diff --git a/browser/components/preferences/tests/browser.ini b/browser/components/preferences/tests/browser.ini
new file mode 100644
index 0000000000..8203bceb90
--- /dev/null
+++ b/browser/components/preferences/tests/browser.ini
@@ -0,0 +1,152 @@
+[DEFAULT]
+prefs =
+ extensions.formautofill.addresses.available='on'
+ extensions.formautofill.creditCards.available='on'
+ signon.management.page.os-auth.enabled=true
+support-files =
+ head.js
+ privacypane_tests_perwindow.js
+ addons/pl-dictionary.xpi
+ addons/set_homepage.xpi
+ addons/set_newtab.xpi
+
+[browser_advanced_update.js]
+skip-if = !updater
+[browser_application_xml_handle_internally.js]
+[browser_applications_selection.js]
+[browser_basic_rebuild_fonts_test.js]
+[browser_browser_languages_subdialog.js]
+skip-if =
+ tsan
+ (!debug && os == 'win') # Bug 1518370
+[browser_bug1018066_resetScrollPosition.js]
+[browser_bug1020245_openPreferences_to_paneContent.js]
+[browser_bug1184989_prevent_scrolling_when_preferences_flipped.js]
+skip-if = os == "mac" # 1664576
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+support-files =
+ browser_bug1184989_prevent_scrolling_when_preferences_flipped.xhtml
+[browser_bug1547020_lockedDownloadDir.js]
+[browser_bug1579418.js]
+[browser_bug410900.js]
+[browser_bug731866.js]
+[browser_bug795764_cachedisabled.js]
+[browser_cert_export.js]
+[browser_change_app_handler.js]
+skip-if = os != "win" # Windows-specific handler application selection dialog
+[browser_checkspelling.js]
+[browser_connection.js]
+[browser_connection_bug1445991.js]
+[browser_connection_bug1505330.js]
+skip-if = (verify && debug && (os == 'linux' || os == 'mac'))
+[browser_connection_bug388287.js]
+[browser_containers_name_input.js]
+[browser_contentblocking.js]
+skip-if = socketprocess_networking
+[browser_contentblocking_categories.js]
+[browser_contentblocking_standard_tcp_section.js]
+[browser_cookie_exceptions_addRemove.js]
+[browser_cookies_exceptions.js]
+[browser_defaultbrowser_alwayscheck.js]
+[browser_engines.js]
+[browser_etp_exceptions_dialog.js]
+[browser_experimental_features.js]
+[browser_experimental_features_filter.js]
+[browser_experimental_features_hidden_when_not_public.js]
+skip-if =
+ os == "mac" && debug # Bug 1723854
+ os == "linux" && os_version == "18.04" && debug # Bug 1723854
+[browser_experimental_features_resetall.js]
+[browser_extension_controlled.js]
+skip-if =
+ tsan
+ ccov && (os == 'linux' || os == 'win') # Linux: bug 1613530, Windows: bug 1437051
+[browser_filetype_dialog.js]
+[browser_fluent.js]
+[browser_homepage_default.js]
+[browser_homepages_filter_aboutpreferences.js]
+[browser_homepages_use_bookmark.js]
+[browser_hometab_restore_defaults.js]
+https_first_disabled = true
+[browser_https_only_exceptions.js]
+[browser_https_only_section.js]
+[browser_ignore_invalid_capability.js]
+[browser_languages_subdialog.js]
+[browser_layersacceleration.js]
+[browser_localSearchShortcuts.js]
+[browser_moreFromMozilla.js]
+[browser_moreFromMozilla_locales.js]
+[browser_newtab_menu.js]
+[browser_notifications_do_not_disturb.js]
+[browser_open_download_preferences.js]
+support-files = empty_pdf_file.pdf
+[browser_open_migration_wizard.js]
+[browser_password_management.js]
+[browser_pdf_disabled.js]
+[browser_performance.js]
+[browser_performance_content_process_limit.js]
+[browser_performance_e10srollout.js]
+[browser_performance_non_e10s.js]
+skip-if = true
+[browser_permissions_checkPermissionsWereAdded.js]
+[browser_permissions_dialog.js]
+[browser_permissions_dialog_default_perm.js]
+[browser_permissions_urlFieldHidden.js]
+[browser_primaryPassword.js]
+[browser_privacy_cookieBannerHandling.js]
+[browser_privacy_dnsoverhttps.js]
+[browser_privacy_firefoxSuggest.js]
+[browser_privacy_passwordGenerationAndAutofill.js]
+[browser_privacy_quickactions.js]
+[browser_privacy_relayIntegration.js]
+[browser_privacy_segmentation_pref.js]
+[browser_privacy_syncDataClearing.js]
+[browser_privacypane_2.js]
+[browser_privacypane_3.js]
+[browser_proxy_backup.js]
+[browser_sanitizeOnShutdown_prefLocked.js]
+[browser_searchChangedEngine.js]
+[browser_searchDefaultEngine.js]
+support-files =
+ engine1/manifest.json
+ engine2/manifest.json
+[browser_searchFindMoreLink.js]
+[browser_searchRestoreDefaults.js]
+[browser_searchScroll.js]
+support-files =
+ !/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
+[browser_searchShowSuggestionsFirst.js]
+[browser_search_no_results_change_category.js]
+[browser_search_searchTerms.js]
+[browser_search_subdialog_tooltip_saved_addresses.js]
+[browser_search_subdialogs_within_preferences_1.js]
+skip-if = tsan # Bug 1678829
+[browser_search_subdialogs_within_preferences_2.js]
+[browser_search_subdialogs_within_preferences_3.js]
+[browser_search_subdialogs_within_preferences_4.js]
+[browser_search_subdialogs_within_preferences_5.js]
+[browser_search_subdialogs_within_preferences_6.js]
+[browser_search_subdialogs_within_preferences_7.js]
+[browser_search_subdialogs_within_preferences_8.js]
+[browser_search_subdialogs_within_preferences_site_data.js]
+[browser_search_within_preferences_1.js]
+skip-if = (os == 'win' && processor == "aarch64") # Bug 1536560
+[browser_search_within_preferences_2.js]
+[browser_search_within_preferences_command.js]
+[browser_searchsuggestions.js]
+[browser_security-1.js]
+[browser_security-2.js]
+[browser_security-3.js]
+[browser_site_login_exceptions.js]
+[browser_site_login_exceptions_policy.js]
+[browser_spotlight.js]
+[browser_statePartitioning_PBM_strings.js]
+[browser_statePartitioning_strings.js]
+[browser_subdialogs.js]
+support-files =
+ subdialog.xhtml
+ subdialog2.xhtml
+[browser_sync_chooseWhatToSync.js]
+[browser_sync_disabled.js]
+[browser_sync_pairing.js]
+[browser_warning_permanent_private_browsing.js]
diff --git a/browser/components/preferences/tests/browser_advanced_update.js b/browser/components/preferences/tests/browser_advanced_update.js
new file mode 100644
index 0000000000..95da7a1c7a
--- /dev/null
+++ b/browser/components/preferences/tests/browser_advanced_update.js
@@ -0,0 +1,185 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const Cm = Components.manager;
+
+const uuidGenerator = Services.uuid;
+
+const mockUpdateManager = {
+ contractId: "@mozilla.org/updates/update-manager;1",
+
+ _mockClassId: uuidGenerator.generateUUID(),
+
+ _originalClassId: "",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIUpdateManager"]),
+
+ createInstance(iiD) {
+ return this.QueryInterface(iiD);
+ },
+
+ register() {
+ let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ if (!registrar.isCIDRegistered(this._mockClassId)) {
+ this._originalClassId = registrar.contractIDToCID(this.contractId);
+ registrar.registerFactory(
+ this._mockClassId,
+ "Unregister after testing",
+ this.contractId,
+ this
+ );
+ }
+ },
+
+ unregister() {
+ let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.unregisterFactory(this._mockClassId, this);
+ registrar.registerFactory(this._originalClassId, "", this.contractId, null);
+ },
+
+ getUpdateCount() {
+ return this._updates.length;
+ },
+
+ getUpdateAt(index) {
+ return this._updates[index];
+ },
+
+ _updates: [
+ {
+ name: "Firefox Developer Edition 49.0a2",
+ statusText: "The Update was successfully installed",
+ buildID: "20160728004010",
+ installDate: 1469763105156,
+ detailsURL: "https://www.mozilla.org/firefox/aurora/",
+ },
+ {
+ name: "Firefox Developer Edition 43.0a2",
+ statusText: "The Update was successfully installed",
+ buildID: "20150929004011",
+ installDate: 1443585886224,
+ detailsURL: "https://www.mozilla.org/firefox/aurora/",
+ },
+ {
+ name: "Firefox Developer Edition 42.0a2",
+ statusText: "The Update was successfully installed",
+ buildID: "20150920004018",
+ installDate: 1442818147544,
+ detailsURL: "https://www.mozilla.org/firefox/aurora/",
+ },
+ ],
+};
+
+function formatInstallDate(sec) {
+ var date = new Date(sec);
+ const dtOptions = {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ hour: "numeric",
+ minute: "numeric",
+ second: "numeric",
+ };
+ return date.toLocaleString(undefined, dtOptions);
+}
+
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ let showBtn = doc.getElementById("showUpdateHistory");
+ let dialogOverlay = content.gSubDialog._preloadDialog._overlay;
+
+ // XXX: For unknown reasons, this mock cannot be loaded by
+ // XPCOMUtils.defineLazyServiceGetter() called in aboutDialog-appUpdater.js.
+ // It is registered here so that we could assert update history subdialog
+ // without stopping the preferences advanced pane from loading.
+ // See bug 1361929.
+ mockUpdateManager.register();
+
+ // Test the dialog window opens
+ ok(
+ BrowserTestUtils.is_hidden(dialogOverlay),
+ "The dialog should be invisible"
+ );
+ let promiseSubDialogLoaded = promiseLoadSubDialog(
+ "chrome://mozapps/content/update/history.xhtml"
+ );
+ showBtn.doCommand();
+ await promiseSubDialogLoaded;
+ ok(
+ !BrowserTestUtils.is_hidden(dialogOverlay),
+ "The dialog should be visible"
+ );
+
+ let dialogFrame = dialogOverlay.querySelector(".dialogFrame");
+ let frameDoc = dialogFrame.contentDocument;
+ let updates = frameDoc.querySelectorAll("richlistitem.update");
+
+ // Test the update history numbers are correct
+ is(
+ updates.length,
+ mockUpdateManager.getUpdateCount(),
+ "The update count is incorrect."
+ );
+
+ // Test the updates are displayed correctly
+ let update = null;
+ let updateData = null;
+ for (let i = 0; i < updates.length; ++i) {
+ update = updates[i];
+ updateData = mockUpdateManager.getUpdateAt(i);
+
+ let testcases = [
+ {
+ selector: ".update-name",
+ id: "update-full-build-name",
+ args: { name: updateData.name, buildID: updateData.buildID },
+ },
+ {
+ selector: ".update-installedOn-label",
+ id: "update-installed-on",
+ args: { date: formatInstallDate(updateData.installDate) },
+ },
+ {
+ selector: ".update-status-label",
+ id: "update-status",
+ args: { status: updateData.statusText },
+ },
+ ];
+
+ for (let { selector, id, args } of testcases) {
+ const element = update.querySelector(selector);
+ const l10nAttrs = frameDoc.l10n.getAttributes(element);
+ Assert.deepEqual(
+ l10nAttrs,
+ {
+ id,
+ args,
+ },
+ "Wrong " + id
+ );
+ }
+
+ if (update.detailsURL) {
+ is(
+ update.detailsURL,
+ update.querySelector(".text-link").href,
+ "Wrong detailsURL"
+ );
+ }
+ }
+
+ // Test the dialog window closes
+ let closeBtn = dialogOverlay.querySelector(".dialogClose");
+ closeBtn.doCommand();
+ ok(
+ BrowserTestUtils.is_hidden(dialogOverlay),
+ "The dialog should be invisible"
+ );
+
+ mockUpdateManager.unregister();
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_application_xml_handle_internally.js b/browser/components/preferences/tests/browser_application_xml_handle_internally.js
new file mode 100644
index 0000000000..edb4a4c0ec
--- /dev/null
+++ b/browser/components/preferences/tests/browser_application_xml_handle_internally.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const HandlerService = Cc[
+ "@mozilla.org/uriloader/handler-service;1"
+].getService(Ci.nsIHandlerService);
+
+const MIMEService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+
+// This test checks that application/xml has the handle internally option.
+add_task(async function applicationXmlHandleInternally() {
+ const mimeInfo = MIMEService.getFromTypeAndExtension(
+ "application/xml",
+ "xml"
+ );
+ HandlerService.store(mimeInfo);
+ registerCleanupFunction(() => {
+ HandlerService.remove(mimeInfo);
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+
+ let win = gBrowser.selectedBrowser.contentWindow;
+
+ let container = win.document.getElementById("handlersView");
+
+ // First, find the application/xml item.
+ let xmlItem = container.querySelector("richlistitem[type='application/xml']");
+ Assert.ok(xmlItem, "application/xml is present in handlersView");
+ if (xmlItem) {
+ xmlItem.scrollIntoView({ block: "center" });
+ xmlItem.closest("richlistbox").selectItem(xmlItem);
+
+ // Open its menu
+ let list = xmlItem.querySelector(".actionsMenu");
+ let popup = list.menupopup;
+ let popupShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(list, {}, win);
+ await popupShown;
+
+ let handleInternallyItem = list.querySelector(
+ `menuitem[action='${Ci.nsIHandlerInfo.handleInternally}']`
+ );
+
+ ok(!!handleInternallyItem, "handle internally is present");
+ }
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_applications_selection.js b/browser/components/preferences/tests/browser_applications_selection.js
new file mode 100644
index 0000000000..683ce76a89
--- /dev/null
+++ b/browser/components/preferences/tests/browser_applications_selection.js
@@ -0,0 +1,403 @@
+SimpleTest.requestCompleteLog();
+const { HandlerServiceTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/HandlerServiceTestUtils.sys.mjs"
+);
+
+let gHandlerService = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
+ Ci.nsIHandlerService
+);
+
+let gOldMailHandlers = [];
+let gDummyHandlers = [];
+let gOriginalPreferredMailHandler;
+let gOriginalPreferredPDFHandler;
+
+registerCleanupFunction(function () {
+ function removeDummyHandlers(handlers) {
+ // Remove any of the dummy handlers we created.
+ for (let i = handlers.Count() - 1; i >= 0; i--) {
+ try {
+ if (
+ gDummyHandlers.some(
+ h =>
+ h.uriTemplate ==
+ handlers.queryElementAt(i, Ci.nsIWebHandlerApp).uriTemplate
+ )
+ ) {
+ handlers.removeElementAt(i);
+ }
+ } catch (ex) {
+ /* ignore non-web-app handlers */
+ }
+ }
+ }
+ // Re-add the original protocol handlers:
+ let mailHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("mailto");
+ let mailHandlers = mailHandlerInfo.possibleApplicationHandlers;
+ for (let h of gOldMailHandlers) {
+ mailHandlers.appendElement(h);
+ }
+ removeDummyHandlers(mailHandlers);
+ mailHandlerInfo.preferredApplicationHandler = gOriginalPreferredMailHandler;
+ gHandlerService.store(mailHandlerInfo);
+
+ let pdfHandlerInfo =
+ HandlerServiceTestUtils.getHandlerInfo("application/pdf");
+ removeDummyHandlers(pdfHandlerInfo.possibleApplicationHandlers);
+ pdfHandlerInfo.preferredApplicationHandler = gOriginalPreferredPDFHandler;
+ gHandlerService.store(pdfHandlerInfo);
+
+ gBrowser.removeCurrentTab();
+});
+
+function scrubMailtoHandlers(handlerInfo) {
+ // Remove extant web handlers because they have icons that
+ // we fetch from the web, which isn't allowed in tests.
+ let handlers = handlerInfo.possibleApplicationHandlers;
+ for (let i = handlers.Count() - 1; i >= 0; i--) {
+ try {
+ let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
+ gOldMailHandlers.push(handler);
+ // If we get here, this is a web handler app. Remove it:
+ handlers.removeElementAt(i);
+ } catch (ex) {}
+ }
+}
+
+add_setup(async function () {
+ // Create our dummy handlers
+ let handler1 = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(
+ Ci.nsIWebHandlerApp
+ );
+ handler1.name = "Handler 1";
+ handler1.uriTemplate = "https://example.com/first/%s";
+
+ let handler2 = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(
+ Ci.nsIWebHandlerApp
+ );
+ handler2.name = "Handler 2";
+ handler2.uriTemplate = "http://example.org/second/%s";
+ gDummyHandlers.push(handler1, handler2);
+
+ function substituteWebHandlers(handlerInfo) {
+ // Append the dummy handlers to replace them:
+ let handlers = handlerInfo.possibleApplicationHandlers;
+ handlers.appendElement(handler1);
+ handlers.appendElement(handler2);
+ gHandlerService.store(handlerInfo);
+ }
+ // Set up our mailto handler test infrastructure.
+ let mailtoHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("mailto");
+ scrubMailtoHandlers(mailtoHandlerInfo);
+ gOriginalPreferredMailHandler = mailtoHandlerInfo.preferredApplicationHandler;
+ substituteWebHandlers(mailtoHandlerInfo);
+
+ // Now do the same for pdf handler:
+ let pdfHandlerInfo =
+ HandlerServiceTestUtils.getHandlerInfo("application/pdf");
+ // PDF doesn't have built-in web handlers, so no need to scrub.
+ gOriginalPreferredPDFHandler = pdfHandlerInfo.preferredApplicationHandler;
+ substituteWebHandlers(pdfHandlerInfo);
+
+ await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+ info("Preferences page opened on the general pane.");
+
+ await gBrowser.selectedBrowser.contentWindow.promiseLoadHandlersList;
+ info("Apps list loaded.");
+});
+
+async function selectStandardOptions(itemToUse) {
+ async function selectItemInPopup(item) {
+ let popupShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ // Synthesizing the mouse on the .actionsMenu menulist somehow just selects
+ // the top row. Probably something to do with the multiple layers of anon
+ // content - workaround by using the `.open` setter instead.
+ list.open = true;
+ await popupShown;
+ let popupHidden = BrowserTestUtils.waitForEvent(popup, "popuphidden");
+ if (typeof item == "function") {
+ item = item();
+ }
+ popup.activateItem(item);
+ await popupHidden;
+ return item;
+ }
+
+ let itemType = itemToUse.getAttribute("type");
+ // Center the item. Center rather than top so it doesn't get blocked by
+ // the search header.
+ itemToUse.scrollIntoView({ block: "center" });
+ itemToUse.closest("richlistbox").selectItem(itemToUse);
+ Assert.ok(itemToUse.selected, "Should be able to select our item.");
+ // Force reflow to make sure it's visible and the container dropdown isn't
+ // hidden.
+ itemToUse.getBoundingClientRect().top;
+ let list = itemToUse.querySelector(".actionsMenu");
+ let popup = list.menupopup;
+
+ // select one of our test cases:
+ let handlerItem = list.querySelector("menuitem[data-l10n-args*='Handler 1']");
+ await selectItemInPopup(handlerItem);
+ let { preferredAction, alwaysAskBeforeHandling } =
+ HandlerServiceTestUtils.getHandlerInfo(itemType);
+ Assert.notEqual(
+ preferredAction,
+ Ci.nsIHandlerInfo.alwaysAsk,
+ "Should have selected something other than 'always ask' (" + itemType + ")"
+ );
+ Assert.ok(
+ !alwaysAskBeforeHandling,
+ "Should have turned off asking before handling (" + itemType + ")"
+ );
+
+ // Test the alwaysAsk option
+ let alwaysAskItem = list.getElementsByAttribute(
+ "action",
+ Ci.nsIHandlerInfo.alwaysAsk
+ )[0];
+ await selectItemInPopup(alwaysAskItem);
+ Assert.equal(
+ list.selectedItem,
+ alwaysAskItem,
+ "Should have selected always ask item (" + itemType + ")"
+ );
+ alwaysAskBeforeHandling =
+ HandlerServiceTestUtils.getHandlerInfo(itemType).alwaysAskBeforeHandling;
+ Assert.ok(
+ alwaysAskBeforeHandling,
+ "Should have turned on asking before handling (" + itemType + ")"
+ );
+
+ let useDefaultItem = list.getElementsByAttribute(
+ "action",
+ Ci.nsIHandlerInfo.useSystemDefault
+ );
+ useDefaultItem = useDefaultItem && useDefaultItem[0];
+ if (useDefaultItem) {
+ await selectItemInPopup(useDefaultItem);
+ Assert.equal(
+ list.selectedItem,
+ useDefaultItem,
+ "Should have selected 'use default' item (" + itemType + ")"
+ );
+ preferredAction =
+ HandlerServiceTestUtils.getHandlerInfo(itemType).preferredAction;
+ Assert.equal(
+ preferredAction,
+ Ci.nsIHandlerInfo.useSystemDefault,
+ "Should have selected 'use default' (" + itemType + ")"
+ );
+ } else {
+ // Whether there's a "use default" item depends on the OS, so it's not
+ // possible to rely on it being the case or not.
+ info("No 'Use default' item, so not testing (" + itemType + ")");
+ }
+
+ // Select a web app item.
+ let webAppItems = Array.from(
+ popup.getElementsByAttribute("action", Ci.nsIHandlerInfo.useHelperApp)
+ );
+ webAppItems = webAppItems.filter(
+ item => item.handlerApp instanceof Ci.nsIWebHandlerApp
+ );
+ Assert.equal(
+ webAppItems.length,
+ 2,
+ "Should have 2 web application handler. (" + itemType + ")"
+ );
+ Assert.notEqual(
+ webAppItems[0].label,
+ webAppItems[1].label,
+ "Should have 2 different web app handlers"
+ );
+ let selectedItem = await selectItemInPopup(webAppItems[0]);
+
+ // Test that the selected item label is the same as the label
+ // of the menu item.
+ let win = gBrowser.selectedBrowser.contentWindow;
+ await win.document.l10n.translateFragment(selectedItem);
+ await win.document.l10n.translateFragment(itemToUse);
+ Assert.equal(
+ selectedItem.label,
+ itemToUse.querySelector(".actionContainer label").value,
+ "Should have selected correct item (" + itemType + ")"
+ );
+ let { preferredApplicationHandler } =
+ HandlerServiceTestUtils.getHandlerInfo(itemType);
+ preferredApplicationHandler.QueryInterface(Ci.nsIWebHandlerApp);
+ Assert.equal(
+ selectedItem.handlerApp.uriTemplate,
+ preferredApplicationHandler.uriTemplate,
+ "App should actually be selected in the backend. (" + itemType + ")"
+ );
+
+ // select the other web app item
+ selectedItem = await selectItemInPopup(webAppItems[1]);
+
+ // Test that the selected item label is the same as the label
+ // of the menu item
+ await win.document.l10n.translateFragment(selectedItem);
+ await win.document.l10n.translateFragment(itemToUse);
+ Assert.equal(
+ selectedItem.label,
+ itemToUse.querySelector(".actionContainer label").value,
+ "Should have selected correct item (" + itemType + ")"
+ );
+ preferredApplicationHandler =
+ HandlerServiceTestUtils.getHandlerInfo(
+ itemType
+ ).preferredApplicationHandler;
+ preferredApplicationHandler.QueryInterface(Ci.nsIWebHandlerApp);
+ Assert.equal(
+ selectedItem.handlerApp.uriTemplate,
+ preferredApplicationHandler.uriTemplate,
+ "App should actually be selected in the backend. (" + itemType + ")"
+ );
+}
+
+add_task(async function checkDropdownBehavior() {
+ let win = gBrowser.selectedBrowser.contentWindow;
+
+ let container = win.document.getElementById("handlersView");
+
+ // First check a protocol handler item.
+ let mailItem = container.querySelector("richlistitem[type='mailto']");
+ Assert.ok(mailItem, "mailItem is present in handlersView.");
+ await selectStandardOptions(mailItem);
+
+ // Then check a content menu item.
+ let pdfItem = container.querySelector("richlistitem[type='application/pdf']");
+ Assert.ok(pdfItem, "pdfItem is present in handlersView.");
+ await selectStandardOptions(pdfItem);
+});
+
+add_task(async function sortingCheck() {
+ let win = gBrowser.selectedBrowser.contentWindow;
+ const handlerView = win.document.getElementById("handlersView");
+ const typeColumn = win.document.getElementById("typeColumn");
+ Assert.ok(typeColumn, "typeColumn is present in handlersView.");
+
+ let expectedNumberOfItems =
+ handlerView.querySelectorAll("richlistitem").length;
+
+ // Test default sorting
+ assertSortByType("ascending");
+
+ const oldDir = typeColumn.getAttribute("sortDirection");
+
+ // click on an item and sort again:
+ let itemToUse = handlerView.querySelector("richlistitem[type=mailto]");
+ itemToUse.scrollIntoView({ block: "center" });
+ itemToUse.closest("richlistbox").selectItem(itemToUse);
+
+ // Test sorting on the type column
+ typeColumn.click();
+ assertSortByType("descending");
+ Assert.notEqual(
+ oldDir,
+ typeColumn.getAttribute("sortDirection"),
+ "Sort direction should change"
+ );
+
+ typeColumn.click();
+ assertSortByType("ascending");
+
+ const actionColumn = win.document.getElementById("actionColumn");
+ Assert.ok(actionColumn, "actionColumn is present in handlersView.");
+
+ // Test sorting on the action column
+ const oldActionDir = actionColumn.getAttribute("sortDirection");
+ actionColumn.click();
+ assertSortByAction("ascending");
+ Assert.notEqual(
+ oldActionDir,
+ actionColumn.getAttribute("sortDirection"),
+ "Sort direction should change"
+ );
+
+ actionColumn.click();
+ assertSortByAction("descending");
+
+ // Restore the default sort order
+ typeColumn.click();
+ assertSortByType("ascending");
+
+ function assertSortByAction(order) {
+ Assert.equal(
+ actionColumn.getAttribute("sortDirection"),
+ order,
+ `Sort direction should be ${order}`
+ );
+ let siteItems = handlerView.getElementsByTagName("richlistitem");
+ Assert.equal(
+ siteItems.length,
+ expectedNumberOfItems,
+ "Number of items should not change."
+ );
+ for (let i = 0; i < siteItems.length - 1; ++i) {
+ let aType = siteItems[i].getAttribute("actionDescription").toLowerCase();
+ let bType = siteItems[i + 1]
+ .getAttribute("actionDescription")
+ .toLowerCase();
+ let result = 0;
+ if (aType > bType) {
+ result = 1;
+ } else if (bType > aType) {
+ result = -1;
+ }
+ if (order == "ascending") {
+ Assert.lessOrEqual(
+ result,
+ 0,
+ "Should sort applications in the ascending order by action"
+ );
+ } else {
+ Assert.greaterOrEqual(
+ result,
+ 0,
+ "Should sort applications in the descending order by action"
+ );
+ }
+ }
+ }
+
+ function assertSortByType(order) {
+ Assert.equal(
+ typeColumn.getAttribute("sortDirection"),
+ order,
+ `Sort direction should be ${order}`
+ );
+
+ let siteItems = handlerView.getElementsByTagName("richlistitem");
+ Assert.equal(
+ siteItems.length,
+ expectedNumberOfItems,
+ "Number of items should not change."
+ );
+ for (let i = 0; i < siteItems.length - 1; ++i) {
+ let aType = siteItems[i].getAttribute("typeDescription").toLowerCase();
+ let bType = siteItems[i + 1]
+ .getAttribute("typeDescription")
+ .toLowerCase();
+ let result = 0;
+ if (aType > bType) {
+ result = 1;
+ } else if (bType > aType) {
+ result = -1;
+ }
+ if (order == "ascending") {
+ Assert.lessOrEqual(
+ result,
+ 0,
+ "Should sort applications in the ascending order by type"
+ );
+ } else {
+ Assert.greaterOrEqual(
+ result,
+ 0,
+ "Should sort applications in the descending order by type"
+ );
+ }
+ }
+ }
+});
diff --git a/browser/components/preferences/tests/browser_basic_rebuild_fonts_test.js b/browser/components/preferences/tests/browser_basic_rebuild_fonts_test.js
new file mode 100644
index 0000000000..bacda8a6b4
--- /dev/null
+++ b/browser/components/preferences/tests/browser_basic_rebuild_fonts_test.js
@@ -0,0 +1,235 @@
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+ await gBrowser.contentWindow.gMainPane._selectDefaultLanguageGroupPromise;
+ await TestUtils.waitForCondition(
+ () => !gBrowser.contentWindow.Preferences.updateQueued
+ );
+
+ let doc = gBrowser.contentDocument;
+ let contentWindow = gBrowser.contentWindow;
+ var langGroup = Services.prefs.getComplexValue(
+ "font.language.group",
+ Ci.nsIPrefLocalizedString
+ ).data;
+ is(
+ contentWindow.Preferences.get("font.language.group").value,
+ langGroup,
+ "Language group should be set correctly."
+ );
+
+ let defaultFontType = Services.prefs.getCharPref("font.default." + langGroup);
+ let fontFamilyPref = "font.name." + defaultFontType + "." + langGroup;
+ let fontFamily = Services.prefs.getCharPref(fontFamilyPref);
+ let fontFamilyField = doc.getElementById("defaultFont");
+ is(fontFamilyField.value, fontFamily, "Font family should be set correctly.");
+
+ function dispatchMenuItemCommand(menuItem) {
+ const cmdEvent = doc.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent(
+ "command",
+ true,
+ true,
+ contentWindow,
+ 0,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null,
+ 0
+ );
+ menuItem.dispatchEvent(cmdEvent);
+ }
+
+ /**
+ * Return a promise that resolves when the fontFamilyPref changes.
+ *
+ * Font prefs are the only ones whose form controls set "delayprefsave",
+ * which delays the pref change when a user specifies a new value
+ * for the pref. Thus, in order to confirm that the pref gets changed
+ * when the test selects a new value in a font field, we need to await
+ * the change. Awaiting this function does so for fontFamilyPref.
+ */
+ function fontFamilyPrefChanged() {
+ return new Promise(resolve => {
+ const observer = {
+ observe(aSubject, aTopic, aData) {
+ // Check for an exact match to avoid the ambiguity of nsIPrefBranch's
+ // prefix-matching algorithm for notifying pref observers.
+ if (aData == fontFamilyPref) {
+ Services.prefs.removeObserver(fontFamilyPref, observer);
+ resolve();
+ }
+ },
+ };
+ Services.prefs.addObserver(fontFamilyPref, observer);
+ });
+ }
+
+ const menuItems = fontFamilyField.querySelectorAll("menuitem");
+ ok(menuItems.length > 1, "There are multiple font menuitems.");
+ ok(menuItems[0].selected, "The first (default) font menuitem is selected.");
+
+ dispatchMenuItemCommand(menuItems[1]);
+ ok(menuItems[1].selected, "The second font menuitem is selected.");
+
+ await fontFamilyPrefChanged();
+ fontFamily = Services.prefs.getCharPref(fontFamilyPref);
+ is(fontFamilyField.value, fontFamily, "The font family has been updated.");
+
+ dispatchMenuItemCommand(menuItems[0]);
+ ok(
+ menuItems[0].selected,
+ "The first (default) font menuitem is selected again."
+ );
+
+ await fontFamilyPrefChanged();
+ fontFamily = Services.prefs.getCharPref(fontFamilyPref);
+ is(fontFamilyField.value, fontFamily, "The font family has been updated.");
+
+ let defaultFontSize = Services.prefs.getIntPref(
+ "font.size.variable." + langGroup
+ );
+ let fontSizeField = doc.getElementById("defaultFontSize");
+ is(
+ fontSizeField.value,
+ "" + defaultFontSize,
+ "Font size should be set correctly."
+ );
+
+ let promiseSubDialogLoaded = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/fonts.xhtml"
+ );
+ doc.getElementById("advancedFonts").click();
+ let win = await promiseSubDialogLoaded;
+ doc = win.document;
+
+ // Simulate a dumb font backend.
+ win.FontBuilder._enumerator = {
+ _list: ["MockedFont1", "MockedFont2", "MockedFont3"],
+ _defaultFont: null,
+ EnumerateFontsAsync(lang, type) {
+ return Promise.resolve(this._list);
+ },
+ EnumerateAllFontsAsync() {
+ return Promise.resolve(this._list);
+ },
+ getDefaultFont() {
+ return this._defaultFont;
+ },
+ getStandardFamilyName(name) {
+ return name;
+ },
+ };
+ win.FontBuilder._allFonts = null;
+ win.FontBuilder._langGroupSupported = false;
+
+ let langGroupElement = win.Preferences.get("font.language.group");
+ let selectLangsField = doc.getElementById("selectLangs");
+ let serifField = doc.getElementById("serif");
+ let armenian = "x-armn";
+ let western = "x-western";
+
+ // Await rebuilding of the font lists, which happens asynchronously in
+ // gFontsDialog._selectLanguageGroup. Testing code needs to call this
+ // function and await its resolution after changing langGroupElement's value
+ // (or doing anything else that triggers a call to _selectLanguageGroup).
+ function fontListsRebuilt() {
+ return win.gFontsDialog._selectLanguageGroupPromise;
+ }
+
+ langGroupElement.value = armenian;
+ await fontListsRebuilt();
+ selectLangsField.value = armenian;
+ is(serifField.value, "", "Font family should not be set.");
+
+ let armenianSerifElement = win.Preferences.get("font.name.serif.x-armn");
+
+ langGroupElement.value = western;
+ await fontListsRebuilt();
+ selectLangsField.value = western;
+
+ // Simulate a font backend supporting language-specific enumeration.
+ // NB: FontBuilder has cached the return value from EnumerateAllFonts(),
+ // so _allFonts will always have 3 elements regardless of subsequent
+ // _list changes.
+ win.FontBuilder._enumerator._list = ["MockedFont2"];
+
+ langGroupElement.value = armenian;
+ await fontListsRebuilt();
+ selectLangsField.value = armenian;
+ is(
+ serifField.value,
+ "",
+ "Font family should still be empty for indicating using 'default' font."
+ );
+
+ langGroupElement.value = western;
+ await fontListsRebuilt();
+ selectLangsField.value = western;
+
+ // Simulate a system that has no fonts for the specified language.
+ win.FontBuilder._enumerator._list = [];
+
+ langGroupElement.value = armenian;
+ await fontListsRebuilt();
+ selectLangsField.value = armenian;
+ is(serifField.value, "", "Font family should not be set.");
+
+ // Setting default font to "MockedFont3". Then, when serifField.value is
+ // empty, it should indicate using "MockedFont3" but it shouldn't be saved
+ // to "MockedFont3" in the pref. It should be resolved at runtime.
+ win.FontBuilder._enumerator._list = [
+ "MockedFont1",
+ "MockedFont2",
+ "MockedFont3",
+ ];
+ win.FontBuilder._enumerator._defaultFont = "MockedFont3";
+ langGroupElement.value = armenian;
+ await fontListsRebuilt();
+ selectLangsField.value = armenian;
+ is(
+ serifField.value,
+ "",
+ "Font family should be empty even if there is a default font."
+ );
+
+ armenianSerifElement.value = "MockedFont2";
+ serifField.value = "MockedFont2";
+ is(
+ serifField.value,
+ "MockedFont2",
+ 'Font family should be "MockedFont2" for now.'
+ );
+
+ langGroupElement.value = western;
+ await fontListsRebuilt();
+ selectLangsField.value = western;
+ is(serifField.value, "", "Font family of other language should not be set.");
+
+ langGroupElement.value = armenian;
+ await fontListsRebuilt();
+ selectLangsField.value = armenian;
+ is(
+ serifField.value,
+ "MockedFont2",
+ "Font family should not be changed even after switching the language."
+ );
+
+ // If MochedFont2 is removed from the system, the value should be treated
+ // as empty (i.e., 'default' font) after rebuilding the font list.
+ win.FontBuilder._enumerator._list = ["MockedFont1", "MockedFont3"];
+ win.FontBuilder._enumerator._allFonts = ["MockedFont1", "MockedFont3"];
+ serifField.removeAllItems(); // This will cause rebuilding the font list from available fonts.
+ langGroupElement.value = armenian;
+ await fontListsRebuilt();
+ selectLangsField.value = armenian;
+ is(
+ serifField.value,
+ "",
+ "Font family should become empty due to the font uninstalled."
+ );
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_browser_languages_subdialog.js b/browser/components/preferences/tests/browser_browser_languages_subdialog.js
new file mode 100644
index 0000000000..8b57bf08a8
--- /dev/null
+++ b/browser/components/preferences/tests/browser_browser_languages_subdialog.js
@@ -0,0 +1,1058 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+
+AddonTestUtils.initMochitest(this);
+
+const BROWSER_LANGUAGES_URL =
+ "chrome://browser/content/preferences/dialogs/browserLanguages.xhtml";
+const DICTIONARY_ID_PL = "pl@dictionaries.addons.mozilla.org";
+const TELEMETRY_CATEGORY = "intl.ui.browserLanguage";
+
+function langpackId(locale) {
+ return `langpack-${locale}@firefox.mozilla.org`;
+}
+
+function getManifestData(locale, version = "2.0") {
+ return {
+ langpack_id: locale,
+ name: `${locale} Language Pack`,
+ description: `${locale} Language pack`,
+ languages: {
+ [locale]: {
+ chrome_resources: {
+ branding: `browser/chrome/${locale}/locale/branding/`,
+ },
+ version: "1",
+ },
+ },
+ browser_specific_settings: {
+ gecko: {
+ id: langpackId(locale),
+ strict_min_version: AppConstants.MOZ_APP_VERSION,
+ strict_max_version: AppConstants.MOZ_APP_VERSION,
+ },
+ },
+ version,
+ manifest_version: 2,
+ sources: {
+ browser: {
+ base_path: "browser/",
+ },
+ },
+ author: "Mozilla",
+ };
+}
+
+let testLocales = ["fr", "pl", "he"];
+let testLangpacks;
+
+function createLangpack(locale, version) {
+ return AddonTestUtils.createTempXPIFile({
+ "manifest.json": getManifestData(locale, version),
+ [`browser/${locale}/branding/brand.ftl`]: "-brand-short-name = Firefox",
+ });
+}
+
+function createTestLangpacks() {
+ if (!testLangpacks) {
+ testLangpacks = Promise.all(
+ testLocales.map(async locale => [locale, await createLangpack(locale)])
+ );
+ }
+ return testLangpacks;
+}
+
+function createLocaleResult(target_locale, url) {
+ return {
+ guid: langpackId(target_locale),
+ type: "language",
+ target_locale,
+ current_compatible_version: {
+ files: [
+ {
+ platform: "all",
+ url,
+ },
+ ],
+ },
+ };
+}
+
+async function createLanguageToolsFile() {
+ let langpacks = await createTestLangpacks();
+ let results = langpacks.map(([locale, file]) =>
+ createLocaleResult(locale, Services.io.newFileURI(file).spec)
+ );
+
+ let filename = "language-tools.json";
+ let files = { [filename]: { results } };
+ let tempdir = AddonTestUtils.tempDir.clone();
+ let dir = await AddonTestUtils.promiseWriteFilesToDir(tempdir.path, files);
+ dir.append(filename);
+
+ return dir;
+}
+
+async function createDictionaryBrowseResults() {
+ let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
+ let dictionaryPath = testDir + "/addons/pl-dictionary.xpi";
+ let filename = "dictionaries.json";
+ let response = {
+ page_size: 25,
+ page_count: 1,
+ count: 1,
+ results: [
+ {
+ current_version: {
+ id: 1823648,
+ compatibility: {
+ firefox: { max: "9999", min: "4.0" },
+ },
+ files: [
+ {
+ platform: "all",
+ url: dictionaryPath,
+ },
+ ],
+ version: "1.0.20160228",
+ },
+ default_locale: "pl",
+ description: "Polish spell-check",
+ guid: DICTIONARY_ID_PL,
+ name: "Polish Dictionary",
+ slug: "polish-spellchecker-dictionary",
+ status: "public",
+ summary: "Polish dictionary",
+ type: "dictionary",
+ },
+ ],
+ };
+
+ let files = { [filename]: response };
+ let dir = await AddonTestUtils.promiseWriteFilesToDir(
+ AddonTestUtils.tempDir.path,
+ files
+ );
+ dir.append(filename);
+
+ return dir;
+}
+
+function assertLocaleOrder(list, locales) {
+ is(
+ list.itemCount,
+ locales.split(",").length,
+ "The right number of locales are selected"
+ );
+ is(
+ Array.from(list.children)
+ .map(child => child.value)
+ .join(","),
+ locales,
+ "The selected locales are in order"
+ );
+}
+
+function assertAvailableLocales(list, locales) {
+ let items = Array.from(list.menupopup.children);
+ let listLocales = items.filter(item => item.value && item.value != "search");
+ is(
+ listLocales.length,
+ locales.length,
+ "The right number of locales are available"
+ );
+ is(
+ listLocales
+ .map(item => item.value)
+ .sort()
+ .join(","),
+ locales.sort().join(","),
+ "The available locales match"
+ );
+ is(items[0].getAttribute("class"), "label-item", "The first row is a label");
+}
+
+function getDialogId(dialogDoc) {
+ return dialogDoc.ownerGlobal.arguments[0].telemetryId;
+}
+
+function assertTelemetryRecorded(events) {
+ let snapshot = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ );
+
+ // Make sure we got some data.
+ ok(
+ snapshot.parent && !!snapshot.parent.length,
+ "Got parent telemetry events in the snapshot"
+ );
+
+ // Only look at the related events after stripping the timestamp and category.
+ let relatedEvents = snapshot.parent
+ .filter(([timestamp, category]) => category == TELEMETRY_CATEGORY)
+ .map(relatedEvent => relatedEvent.slice(2, 6));
+
+ // Events are now an array of: method, object[, value[, extra]] as expected.
+ Assert.deepEqual(relatedEvents, events, "The events are recorded correctly");
+}
+
+async function selectLocale(localeCode, available, selected, dialogDoc) {
+ let [locale] = Array.from(available.menupopup.children).filter(
+ item => item.value == localeCode
+ );
+ available.selectedItem = locale;
+
+ // Get ready for the selected list to change.
+ let added = waitForMutation(selected, { childList: true }, target =>
+ Array.from(target.children).some(el => el.value == localeCode)
+ );
+
+ // Add the locale.
+ dialogDoc.getElementById("add").doCommand();
+
+ // Wait for the list to update.
+ await added;
+}
+
+async function openDialog(doc, search = false) {
+ let dialogLoaded = promiseLoadSubDialog(BROWSER_LANGUAGES_URL);
+ if (search) {
+ doc.getElementById("primaryBrowserLocaleSearch").doCommand();
+ doc.getElementById("primaryBrowserLocale").menupopup.hidePopup();
+ } else {
+ doc.getElementById("manageBrowserLanguagesButton").doCommand();
+ }
+ let dialogWin = await dialogLoaded;
+ let dialogDoc = dialogWin.document;
+ return {
+ dialog: dialogDoc.getElementById("BrowserLanguagesDialog"),
+ dialogDoc,
+ available: dialogDoc.getElementById("availableLocales"),
+ selected: dialogDoc.getElementById("selectedLocales"),
+ };
+}
+
+add_task(async function testDisabledBrowserLanguages() {
+ let langpacksFile = await createLanguageToolsFile();
+ let langpacksUrl = Services.io.newFileURI(langpacksFile).spec;
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["intl.multilingual.enabled", true],
+ ["intl.multilingual.downloadEnabled", true],
+ ["intl.multilingual.liveReload", false],
+ ["intl.multilingual.liveReloadBidirectional", false],
+ ["intl.locale.requested", "en-US,pl,he,de"],
+ ["extensions.langpacks.signatures.required", false],
+ ["extensions.getAddons.langpacks.url", langpacksUrl],
+ ],
+ });
+
+ // Install an old pl langpack.
+ let oldLangpack = await createLangpack("pl", "1.0");
+ await AddonTestUtils.promiseInstallFile(oldLangpack);
+
+ // Install all the other available langpacks.
+ let pl;
+ let langpacks = await createTestLangpacks();
+ let addons = await Promise.all(
+ langpacks.map(async ([locale, file]) => {
+ if (locale == "pl") {
+ pl = await AddonManager.getAddonByID(langpackId("pl"));
+ // Disable pl so it's removed from selected.
+ await pl.disable();
+ return pl;
+ }
+ let install = await AddonTestUtils.promiseInstallFile(file);
+ return install.addon;
+ })
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let { dialogDoc, available, selected } = await openDialog(doc);
+
+ // pl is not selected since it's disabled.
+ is(pl.userDisabled, true, "pl is disabled");
+ is(pl.version, "1.0", "pl is the old 1.0 version");
+ assertLocaleOrder(selected, "en-US,he");
+
+ // Wait for the children menu to be populated.
+ await BrowserTestUtils.waitForCondition(
+ () => !!available.children.length,
+ "Children list populated"
+ );
+
+ // Only fr is enabled and not selected, so it's the only locale available.
+ assertAvailableLocales(available, ["fr"]);
+
+ // Search for more languages.
+ available.menupopup.lastElementChild.doCommand();
+ available.menupopup.hidePopup();
+ await waitForMutation(available.menupopup, { childList: true }, target =>
+ Array.from(available.menupopup.children).some(
+ locale => locale.value == "pl"
+ )
+ );
+
+ // pl is now available since it is available remotely.
+ assertAvailableLocales(available, ["fr", "pl"]);
+
+ let installId = null;
+ AddonTestUtils.promiseInstallEvent("onInstallEnded").then(([install]) => {
+ installId = install.installId;
+ });
+
+ // Add pl.
+ await selectLocale("pl", available, selected, dialogDoc);
+ assertLocaleOrder(selected, "pl,en-US,he");
+
+ // Find pl again since it's been upgraded.
+ pl = await AddonManager.getAddonByID(langpackId("pl"));
+ is(pl.userDisabled, false, "pl is now enabled");
+ is(pl.version, "2.0", "pl is upgraded to version 2.0");
+
+ let dialogId = getDialogId(dialogDoc);
+ ok(dialogId, "There's a dialogId");
+ ok(installId, "There's an installId");
+
+ await Promise.all(addons.map(addon => addon.uninstall()));
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ assertTelemetryRecorded([
+ ["manage", "main", dialogId],
+ ["search", "dialog", dialogId],
+ ["add", "dialog", dialogId, { installId }],
+
+ // Cancel is recorded when the tab is closed.
+ ["cancel", "dialog", dialogId],
+ ]);
+});
+
+add_task(async function testReorderingBrowserLanguages() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["intl.multilingual.enabled", true],
+ ["intl.multilingual.downloadEnabled", true],
+ ["intl.multilingual.liveReload", false],
+ ["intl.multilingual.liveReloadBidirectional", false],
+ ["intl.locale.requested", "en-US,pl,he,de"],
+ ["extensions.langpacks.signatures.required", false],
+ ],
+ });
+
+ // Install all the available langpacks.
+ let langpacks = await createTestLangpacks();
+ let addons = await Promise.all(
+ langpacks.map(async ([locale, file]) => {
+ let install = await AddonTestUtils.promiseInstallFile(file);
+ return install.addon;
+ })
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let messageBar = doc.getElementById("confirmBrowserLanguage");
+ is(messageBar.hidden, true, "The message bar is hidden at first");
+
+ // Open the dialog.
+ let { dialog, dialogDoc, selected } = await openDialog(doc);
+ let firstDialogId = getDialogId(dialogDoc);
+
+ // The initial order is set by the pref, filtered by available.
+ assertLocaleOrder(selected, "en-US,pl,he");
+
+ // Moving pl down changes the order.
+ selected.selectedItem = selected.querySelector("[value='pl']");
+ dialogDoc.getElementById("down").doCommand();
+ assertLocaleOrder(selected, "en-US,he,pl");
+
+ // Accepting the change shows the confirm message bar.
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
+ dialog.acceptDialog();
+ await dialogClosed;
+
+ // The message bar uses async `formatValues` and that may resolve
+ // after the dialog is closed.
+ await BrowserTestUtils.waitForMutationCondition(
+ messageBar,
+ { attributes: true },
+ () => !messageBar.hidden
+ );
+ is(
+ messageBar.querySelector("button").getAttribute("locales"),
+ "en-US,he,pl",
+ "The locales are set on the message bar button"
+ );
+
+ // Open the dialog again.
+ let newDialog = await openDialog(doc);
+ dialog = newDialog.dialog;
+ dialogDoc = newDialog.dialogDoc;
+ let secondDialogId = getDialogId(dialogDoc);
+ selected = newDialog.selected;
+
+ // The initial order comes from the previous settings.
+ assertLocaleOrder(selected, "en-US,he,pl");
+
+ // Select pl in the list.
+ selected.selectedItem = selected.querySelector("[value='pl']");
+ // Move pl back up.
+ dialogDoc.getElementById("up").doCommand();
+ assertLocaleOrder(selected, "en-US,pl,he");
+
+ // Accepting the change hides the confirm message bar.
+ dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
+ dialog.acceptDialog();
+ await dialogClosed;
+ is(messageBar.hidden, true, "The message bar is hidden again");
+
+ ok(firstDialogId, "There was an id on the first dialog");
+ ok(secondDialogId, "There was an id on the second dialog");
+ ok(firstDialogId != secondDialogId, "The dialog ids are different");
+ ok(
+ parseInt(firstDialogId) < parseInt(secondDialogId),
+ "The second dialog id is larger than the first"
+ );
+
+ await Promise.all(addons.map(addon => addon.uninstall()));
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ assertTelemetryRecorded([
+ ["manage", "main", firstDialogId],
+ ["reorder", "dialog", firstDialogId],
+ ["accept", "dialog", firstDialogId],
+ ["manage", "main", secondDialogId],
+ ["reorder", "dialog", secondDialogId],
+ ["accept", "dialog", secondDialogId],
+ ]);
+});
+
+add_task(async function testAddAndRemoveSelectedLanguages() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["intl.multilingual.enabled", true],
+ ["intl.multilingual.downloadEnabled", true],
+ ["intl.multilingual.liveReload", false],
+ ["intl.multilingual.liveReloadBidirectional", false],
+ ["intl.locale.requested", "en-US"],
+ ["extensions.langpacks.signatures.required", false],
+ ],
+ });
+
+ let langpacks = await createTestLangpacks();
+ let addons = await Promise.all(
+ langpacks.map(async ([locale, file]) => {
+ let install = await AddonTestUtils.promiseInstallFile(file);
+ return install.addon;
+ })
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let messageBar = doc.getElementById("confirmBrowserLanguage");
+ is(messageBar.hidden, true, "The message bar is hidden at first");
+
+ // Open the dialog.
+ let { dialog, dialogDoc, available, selected } = await openDialog(doc);
+ let dialogId = getDialogId(dialogDoc);
+
+ // loadLocalesFromAMO is async but `initAvailableLocales` doesn't wait
+ // for it to be resolved, so we have to wait for the list to be populated
+ // before we test for its values.
+ await BrowserTestUtils.waitForMutationCondition(
+ available.menupopup,
+ { attributes: true, childList: true },
+ () => {
+ let listLocales = Array.from(available.menupopup.children).filter(
+ item => item.value && item.value != "search"
+ );
+ return listLocales.length == 3;
+ }
+ );
+ // The initial order is set by the pref.
+ assertLocaleOrder(selected, "en-US");
+ assertAvailableLocales(available, ["fr", "pl", "he"]);
+
+ // Add pl and fr to selected.
+ await selectLocale("pl", available, selected, dialogDoc);
+ await selectLocale("fr", available, selected, dialogDoc);
+
+ assertLocaleOrder(selected, "fr,pl,en-US");
+ assertAvailableLocales(available, ["he"]);
+
+ // Remove pl and fr from selected.
+ dialogDoc.getElementById("remove").doCommand();
+ dialogDoc.getElementById("remove").doCommand();
+ assertLocaleOrder(selected, "en-US");
+ assertAvailableLocales(available, ["fr", "pl", "he"]);
+
+ // Add he to selected.
+ await selectLocale("he", available, selected, dialogDoc);
+ assertLocaleOrder(selected, "he,en-US");
+ assertAvailableLocales(available, ["pl", "fr"]);
+
+ // Accepting the change shows the confirm message bar.
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
+ dialog.acceptDialog();
+ await dialogClosed;
+
+ await waitForMutation(
+ messageBar,
+ { attributes: true, attributeFilter: ["hidden"] },
+ target => !target.hidden
+ );
+
+ is(messageBar.hidden, false, "The message bar is now visible");
+ is(
+ messageBar.querySelector("button").getAttribute("locales"),
+ "he,en-US",
+ "The locales are set on the message bar button"
+ );
+
+ await Promise.all(addons.map(addon => addon.uninstall()));
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ assertTelemetryRecorded([
+ ["manage", "main", dialogId],
+
+ // Install id is not recorded since it was already installed.
+ ["add", "dialog", dialogId],
+ ["add", "dialog", dialogId],
+
+ ["remove", "dialog", dialogId],
+ ["remove", "dialog", dialogId],
+
+ ["add", "dialog", dialogId],
+ ["accept", "dialog", dialogId],
+ ]);
+});
+
+add_task(async function testInstallFromAMO() {
+ let langpacks = await AddonManager.getAddonsByTypes(["locale"]);
+ is(langpacks.length, 0, "There are no langpacks installed");
+
+ let langpacksFile = await createLanguageToolsFile();
+ let langpacksUrl = Services.io.newFileURI(langpacksFile).spec;
+ let dictionaryBrowseFile = await createDictionaryBrowseResults();
+ let browseApiEndpoint = Services.io.newFileURI(dictionaryBrowseFile).spec;
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["intl.multilingual.enabled", true],
+ ["intl.multilingual.downloadEnabled", true],
+ ["intl.multilingual.liveReload", false],
+ ["intl.multilingual.liveReloadBidirectional", false],
+ ["intl.locale.requested", "en-US"],
+ ["extensions.getAddons.langpacks.url", langpacksUrl],
+ ["extensions.langpacks.signatures.required", false],
+ ["extensions.getAddons.get.url", browseApiEndpoint],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let messageBar = doc.getElementById("confirmBrowserLanguage");
+ is(messageBar.hidden, true, "The message bar is hidden at first");
+
+ // Verify only en-US is listed on the main pane.
+ let getMainPaneLocales = () => {
+ let available = doc.getElementById("primaryBrowserLocale");
+ let availableLocales = Array.from(available.menupopup.children);
+ return availableLocales
+ .map(item => item.value)
+ .sort()
+ .join(",");
+ };
+ is(getMainPaneLocales(), "en-US,search", "Only en-US installed to start");
+
+ // Open the dialog.
+ let { dialog, dialogDoc, available, selected } = await openDialog(doc, true);
+ let firstDialogId = getDialogId(dialogDoc);
+
+ // Make sure the message bar is still hidden.
+ is(
+ messageBar.hidden,
+ true,
+ "The message bar is still hidden after searching"
+ );
+
+ if (available.itemCount == 1) {
+ await waitForMutation(
+ available.menupopup,
+ { childList: true },
+ target => available.itemCount > 1
+ );
+ }
+
+ // The initial order is set by the pref.
+ assertLocaleOrder(selected, "en-US");
+ assertAvailableLocales(available, ["fr", "he", "pl"]);
+ is(
+ Services.locale.availableLocales.join(","),
+ "en-US",
+ "There is only one installed locale"
+ );
+
+ // Verify that there are no extra dictionaries.
+ let dicts = await AddonManager.getAddonsByTypes(["dictionary"]);
+ is(dicts.length, 0, "There are no installed dictionaries");
+
+ let installId = null;
+ AddonTestUtils.promiseInstallEvent("onInstallEnded").then(([install]) => {
+ installId = install.installId;
+ });
+
+ // Add Polish, this will install the langpack.
+ await selectLocale("pl", available, selected, dialogDoc);
+
+ ok(installId, "We got an installId for the langpack installation");
+
+ let langpack = await AddonManager.getAddonByID(langpackId("pl"));
+ Assert.deepEqual(
+ langpack.installTelemetryInfo,
+ { source: "about:preferences" },
+ "The source is set to preferences"
+ );
+
+ // Verify the list is correct.
+ assertLocaleOrder(selected, "pl,en-US");
+ assertAvailableLocales(available, ["fr", "he"]);
+ is(
+ Services.locale.availableLocales.sort().join(","),
+ "en-US,pl",
+ "Polish is now installed"
+ );
+
+ await BrowserTestUtils.waitForCondition(async () => {
+ let newDicts = await AddonManager.getAddonsByTypes(["dictionary"]);
+ let done = !!newDicts.length;
+
+ if (done) {
+ is(
+ newDicts[0].id,
+ DICTIONARY_ID_PL,
+ "The polish dictionary was installed"
+ );
+ }
+
+ return done;
+ });
+
+ // Move pl down the list, which prevents an error since it isn't valid.
+ dialogDoc.getElementById("down").doCommand();
+ assertLocaleOrder(selected, "en-US,pl");
+
+ // Test that disabling the langpack removes it from the list.
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
+ dialog.acceptDialog();
+ await dialogClosed;
+
+ // Verify pl is now available to select.
+ is(getMainPaneLocales(), "en-US,pl,search", "en-US and pl now available");
+
+ // Disable the Polish langpack.
+ langpack = await AddonManager.getAddonByID("langpack-pl@firefox.mozilla.org");
+ await langpack.disable();
+
+ ({ dialogDoc, available, selected } = await openDialog(doc, true));
+ let secondDialogId = getDialogId(dialogDoc);
+
+ // Wait for the available langpacks to load.
+ if (available.itemCount == 1) {
+ await waitForMutation(
+ available.menupopup,
+ { childList: true },
+ target => available.itemCount > 1
+ );
+ }
+ assertLocaleOrder(selected, "en-US");
+ assertAvailableLocales(available, ["fr", "he", "pl"]);
+
+ // Uninstall the langpack and dictionary.
+ let installs = await AddonManager.getAddonsByTypes(["locale", "dictionary"]);
+ is(installs.length, 2, "There is one langpack and one dictionary installed");
+ await Promise.all(installs.map(item => item.uninstall()));
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ assertTelemetryRecorded([
+ // First dialog installs a locale and accepts.
+ ["search", "main", firstDialogId],
+ // It has an installId since it was downloaded.
+ ["add", "dialog", firstDialogId, { installId }],
+ // It got moved down to avoid errors with finding translations.
+ ["reorder", "dialog", firstDialogId],
+ ["accept", "dialog", firstDialogId],
+
+ // The second dialog just checks the state and is closed with the tab.
+ ["search", "main", secondDialogId],
+ ["cancel", "dialog", secondDialogId],
+ ]);
+});
+
+let hasSearchOption = popup =>
+ Array.from(popup.children).some(el => el.value == "search");
+
+add_task(async function testDownloadEnabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["intl.multilingual.enabled", true],
+ ["intl.multilingual.downloadEnabled", true],
+ ["intl.multilingual.liveReload", false],
+ ["intl.multilingual.liveReloadBidirectional", false],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ let doc = gBrowser.contentDocument;
+
+ let defaultMenulist = doc.getElementById("primaryBrowserLocale");
+ ok(
+ hasSearchOption(defaultMenulist.menupopup),
+ "There's a search option in the General pane"
+ );
+
+ let { available } = await openDialog(doc, false);
+ ok(
+ hasSearchOption(available.menupopup),
+ "There's a search option in the dialog"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function testDownloadDisabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["intl.multilingual.enabled", true],
+ ["intl.multilingual.downloadEnabled", false],
+ ["intl.multilingual.liveReload", false],
+ ["intl.multilingual.liveReloadBidirectional", false],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ let doc = gBrowser.contentDocument;
+
+ let defaultMenulist = doc.getElementById("primaryBrowserLocale");
+ ok(
+ !hasSearchOption(defaultMenulist.menupopup),
+ "There's no search option in the General pane"
+ );
+
+ let { available } = await openDialog(doc, false);
+ ok(
+ !hasSearchOption(available.menupopup),
+ "There's no search option in the dialog"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function testReorderMainPane() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["intl.multilingual.enabled", true],
+ ["intl.multilingual.downloadEnabled", false],
+ ["intl.multilingual.liveReload", false],
+ ["intl.multilingual.liveReloadBidirectional", false],
+ ["intl.locale.requested", "en-US"],
+ ["extensions.langpacks.signatures.required", false],
+ ],
+ });
+
+ // Clear the telemetry from other tests.
+ Services.telemetry.clearEvents();
+
+ let langpacks = await createTestLangpacks();
+ let addons = await Promise.all(
+ langpacks.map(async ([locale, file]) => {
+ let install = await AddonTestUtils.promiseInstallFile(file);
+ return install.addon;
+ })
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ let doc = gBrowser.contentDocument;
+
+ let messageBar = doc.getElementById("confirmBrowserLanguage");
+ is(messageBar.hidden, true, "The message bar is hidden at first");
+
+ let available = doc.getElementById("primaryBrowserLocale");
+ let availableLocales = Array.from(available.menupopup.children);
+ let availableCodes = availableLocales
+ .map(item => item.value)
+ .sort()
+ .join(",");
+ is(
+ availableCodes,
+ "en-US,fr,he,pl",
+ "All of the available locales are listed"
+ );
+
+ is(available.selectedItem.value, "en-US", "English is selected");
+
+ let hebrew = availableLocales.find(item => item.value == "he");
+ hebrew.click();
+ available.menupopup.hidePopup();
+
+ await BrowserTestUtils.waitForCondition(
+ () => !messageBar.hidden,
+ "Wait for message bar to show"
+ );
+
+ is(messageBar.hidden, false, "The message bar is now shown");
+ is(
+ messageBar.querySelector("button").getAttribute("locales"),
+ "he,en-US",
+ "The locales are set on the message bar button"
+ );
+
+ await Promise.all(addons.map(addon => addon.uninstall()));
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ assertTelemetryRecorded([["reorder", "main"]]);
+});
+
+add_task(async function testLiveLanguageReloading() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["intl.multilingual.enabled", true],
+ ["intl.multilingual.downloadEnabled", true],
+ ["intl.multilingual.liveReload", true],
+ ["intl.multilingual.liveReloadBidirectional", false],
+ ["intl.locale.requested", "en-US,fr,he,de"],
+ ["extensions.langpacks.signatures.required", false],
+ ],
+ });
+
+ // Clear the telemetry from other tests.
+ Services.telemetry.clearEvents();
+
+ let langpacks = await createTestLangpacks();
+ let addons = await Promise.all(
+ langpacks.map(async ([locale, file]) => {
+ let install = await AddonTestUtils.promiseInstallFile(file);
+ return install.addon;
+ })
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+
+ let available = doc.getElementById("primaryBrowserLocale");
+ let availableLocales = Array.from(available.menupopup.children);
+
+ is(
+ Services.locale.appLocaleAsBCP47,
+ "en-US",
+ "The app locale starts as English."
+ );
+
+ Assert.deepEqual(
+ Services.locale.requestedLocales,
+ ["en-US", "fr", "he", "de"],
+ "The locale order starts as what was initially requested."
+ );
+
+ // French and English are both LTR languages.
+ let french = availableLocales.find(item => item.value == "fr");
+
+ french.click();
+ available.menupopup.hidePopup();
+
+ is(
+ Services.locale.appLocaleAsBCP47,
+ "fr",
+ "The app locale was changed to French"
+ );
+
+ Assert.deepEqual(
+ Services.locale.requestedLocales,
+ ["fr", "en-US", "he", "de"],
+ "The locale order is switched to french first."
+ );
+
+ await Promise.all(addons.map(addon => addon.uninstall()));
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ assertTelemetryRecorded([["reorder", "main"]]);
+});
+
+add_task(async function testLiveLanguageReloadingBidiOff() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["intl.multilingual.enabled", true],
+ ["intl.multilingual.downloadEnabled", true],
+ ["intl.multilingual.liveReload", true],
+ ["intl.multilingual.liveReloadBidirectional", false],
+ ["intl.locale.requested", "en-US,fr,he,de"],
+ ["extensions.langpacks.signatures.required", false],
+ ],
+ });
+
+ // Clear the telemetry from other tests.
+ Services.telemetry.clearEvents();
+
+ let langpacks = await createTestLangpacks();
+ let addons = await Promise.all(
+ langpacks.map(async ([locale, file]) => {
+ let install = await AddonTestUtils.promiseInstallFile(file);
+ return install.addon;
+ })
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+
+ let available = doc.getElementById("primaryBrowserLocale");
+ let availableLocales = Array.from(available.menupopup.children);
+
+ is(
+ Services.locale.appLocaleAsBCP47,
+ "en-US",
+ "The app locale starts as English."
+ );
+
+ Assert.deepEqual(
+ Services.locale.requestedLocales,
+ ["en-US", "fr", "he", "de"],
+ "The locale order starts as what was initially requested."
+ );
+
+ let messageBar = doc.getElementById("confirmBrowserLanguage");
+ is(messageBar.hidden, true, "The message bar is hidden at first");
+
+ // English is LTR and Hebrew is RTL.
+ let hebrew = availableLocales.find(item => item.value == "he");
+
+ hebrew.click();
+ available.menupopup.hidePopup();
+
+ await BrowserTestUtils.waitForCondition(
+ () => !messageBar.hidden,
+ "Wait for message bar to show"
+ );
+
+ is(messageBar.hidden, false, "The message bar is now shown");
+
+ is(
+ Services.locale.appLocaleAsBCP47,
+ "en-US",
+ "The app locale remains in English"
+ );
+
+ Assert.deepEqual(
+ Services.locale.requestedLocales,
+ ["en-US", "fr", "he", "de"],
+ "The locale order did not change."
+ );
+
+ await Promise.all(addons.map(addon => addon.uninstall()));
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ assertTelemetryRecorded([["reorder", "main"]]);
+});
+
+add_task(async function testLiveLanguageReloadingBidiOn() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["intl.multilingual.enabled", true],
+ ["intl.multilingual.downloadEnabled", true],
+ ["intl.multilingual.liveReload", true],
+ ["intl.multilingual.liveReloadBidirectional", true],
+ ["intl.locale.requested", "en-US,fr,he,de"],
+ ["extensions.langpacks.signatures.required", false],
+ ],
+ });
+
+ // Clear the telemetry from other tests.
+ Services.telemetry.clearEvents();
+
+ let langpacks = await createTestLangpacks();
+ let addons = await Promise.all(
+ langpacks.map(async ([locale, file]) => {
+ let install = await AddonTestUtils.promiseInstallFile(file);
+ return install.addon;
+ })
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+
+ let available = doc.getElementById("primaryBrowserLocale");
+ let availableLocales = Array.from(available.menupopup.children);
+
+ is(
+ Services.locale.appLocaleAsBCP47,
+ "en-US",
+ "The app locale starts as English."
+ );
+
+ Assert.deepEqual(
+ Services.locale.requestedLocales,
+ ["en-US", "fr", "he", "de"],
+ "The locale order starts as what was initially requested."
+ );
+
+ let messageBar = doc.getElementById("confirmBrowserLanguage");
+ is(messageBar.hidden, true, "The message bar is hidden at first");
+
+ // English is LTR and Hebrew is RTL.
+ let hebrew = availableLocales.find(item => item.value == "he");
+
+ hebrew.click();
+ available.menupopup.hidePopup();
+
+ is(messageBar.hidden, true, "The message bar is still hidden");
+
+ is(
+ Services.locale.appLocaleAsBCP47,
+ "he",
+ "The app locale was changed to Hebrew."
+ );
+
+ Assert.deepEqual(
+ Services.locale.requestedLocales,
+ ["he", "en-US", "fr", "de"],
+ "The locale changed with Hebrew first."
+ );
+
+ await Promise.all(addons.map(addon => addon.uninstall()));
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ assertTelemetryRecorded([["reorder", "main"]]);
+});
diff --git a/browser/components/preferences/tests/browser_bug1018066_resetScrollPosition.js b/browser/components/preferences/tests/browser_bug1018066_resetScrollPosition.js
new file mode 100644
index 0000000000..bc928656ab
--- /dev/null
+++ b/browser/components/preferences/tests/browser_bug1018066_resetScrollPosition.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var originalWindowHeight;
+registerCleanupFunction(function () {
+ window.resizeTo(window.outerWidth, originalWindowHeight);
+ while (gBrowser.tabs[1]) {
+ gBrowser.removeTab(gBrowser.tabs[1]);
+ }
+});
+
+add_task(async function () {
+ originalWindowHeight = window.outerHeight;
+ window.resizeTo(window.outerWidth, 300);
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneSearch", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneSearch", "Search pane was selected");
+ let mainContent = gBrowser.contentDocument.querySelector(".main-content");
+ mainContent.scrollTop = 50;
+ is(mainContent.scrollTop, 50, "main-content should be scrolled 50 pixels");
+
+ await gBrowser.contentWindow.gotoPref("paneGeneral");
+
+ is(
+ mainContent.scrollTop,
+ 0,
+ "Switching to a different category should reset the scroll position"
+ );
+});
diff --git a/browser/components/preferences/tests/browser_bug1020245_openPreferences_to_paneContent.js b/browser/components/preferences/tests/browser_bug1020245_openPreferences_to_paneContent.js
new file mode 100644
index 0000000000..26e79b648d
--- /dev/null
+++ b/browser/components/preferences/tests/browser_bug1020245_openPreferences_to_paneContent.js
@@ -0,0 +1,163 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test opening to the differerent panes and subcategories in Preferences
+add_task(async function () {
+ let prefs = await openPreferencesViaOpenPreferencesAPI("panePrivacy");
+ is(prefs.selectedPane, "panePrivacy", "Privacy pane was selected");
+ prefs = await openPreferencesViaHash("privacy");
+ is(
+ prefs.selectedPane,
+ "panePrivacy",
+ "Privacy pane is selected when hash is 'privacy'"
+ );
+ prefs = await openPreferencesViaOpenPreferencesAPI("nonexistant-category");
+ is(
+ prefs.selectedPane,
+ "paneGeneral",
+ "General pane is selected by default when a nonexistant-category is requested"
+ );
+ prefs = await openPreferencesViaHash("nonexistant-category");
+ is(
+ prefs.selectedPane,
+ "paneGeneral",
+ "General pane is selected when hash is a nonexistant-category"
+ );
+ prefs = await openPreferencesViaHash();
+ is(prefs.selectedPane, "paneGeneral", "General pane is selected by default");
+ prefs = await openPreferencesViaOpenPreferencesAPI("privacy-reports", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "panePrivacy", "Privacy pane is selected by default");
+ let doc = gBrowser.contentDocument;
+ is(
+ doc.location.hash,
+ "#privacy",
+ "The subcategory should be removed from the URI"
+ );
+ await TestUtils.waitForCondition(
+ () => doc.querySelector(".spotlight"),
+ "Wait for the reports section is spotlighted."
+ );
+ is(
+ doc.querySelector(".spotlight").getAttribute("data-subcategory"),
+ "reports",
+ "The reports section is spotlighted."
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Test opening Preferences with subcategory on an existing Preferences tab. See bug 1358475.
+add_task(async function () {
+ let prefs = await openPreferencesViaOpenPreferencesAPI("general", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane is selected by default");
+ let doc = gBrowser.contentDocument;
+ is(
+ doc.location.hash,
+ "#general",
+ "The subcategory should be removed from the URI"
+ );
+ // The reasons that here just call the `openPreferences` API without the helping function are
+ // - already opened one about:preferences tab up there and
+ // - the goal is to test on the existing tab and
+ // - using `openPreferencesViaOpenPreferencesAPI` would introduce more handling of additional about:blank and unneccessary event
+ await openPreferences("privacy-reports");
+ let selectedPane = gBrowser.contentWindow.history.state;
+ is(selectedPane, "panePrivacy", "Privacy pane should be selected");
+ is(
+ doc.location.hash,
+ "#privacy",
+ "The subcategory should be removed from the URI"
+ );
+ await TestUtils.waitForCondition(
+ () => doc.querySelector(".spotlight"),
+ "Wait for the reports section is spotlighted."
+ );
+ is(
+ doc.querySelector(".spotlight").getAttribute("data-subcategory"),
+ "reports",
+ "The reports section is spotlighted."
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Test opening to a subcategory displays the correct values for preferences
+add_task(async function () {
+ // Skip if crash reporting isn't enabled since the checkbox will be missing.
+ if (!AppConstants.MOZ_CRASHREPORTER) {
+ return;
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.crashReports.unsubmittedCheck.autoSubmit2", true]],
+ });
+ await openPreferencesViaOpenPreferencesAPI("privacy-reports", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ ok(
+ doc.querySelector("#automaticallySubmitCrashesBox").checked,
+ "Checkbox for automatically submitting crashes should be checked when the pref is true and only Reports are requested"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function () {
+ // Skip if crash reporting isn't enabled since the checkbox will be missing.
+ if (!AppConstants.MOZ_CRASHREPORTER) {
+ return;
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.crashReports.unsubmittedCheck.autoSubmit2", false]],
+ });
+ await openPreferencesViaOpenPreferencesAPI("privacy-reports", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ ok(
+ !doc.querySelector("#automaticallySubmitCrashesBox").checked,
+ "Checkbox for automatically submitting crashes should not be checked when the pref is false only Reports are requested"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await SpecialPowers.popPrefEnv();
+});
+
+function openPreferencesViaHash(aPane) {
+ return new Promise(resolve => {
+ let finalPrefPaneLoaded = TestUtils.topicObserved(
+ "sync-pane-loaded",
+ () => true
+ );
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "about:preferences" + (aPane ? "#" + aPane : "")
+ );
+ let newTabBrowser = gBrowser.selectedBrowser;
+
+ newTabBrowser.addEventListener(
+ "Initialized",
+ function () {
+ newTabBrowser.contentWindow.addEventListener(
+ "load",
+ async function () {
+ let win = gBrowser.contentWindow;
+ let selectedPane = win.history.state;
+ await finalPrefPaneLoaded;
+ gBrowser.removeCurrentTab();
+ resolve({ selectedPane });
+ },
+ { once: true }
+ );
+ },
+ { capture: true, once: true }
+ );
+ });
+}
diff --git a/browser/components/preferences/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js b/browser/components/preferences/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js
new file mode 100644
index 0000000000..ef387f9a9d
--- /dev/null
+++ b/browser/components/preferences/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js
@@ -0,0 +1,116 @@
+const triggeringPrincipal_base64 = E10SUtils.SERIALIZED_SYSTEMPRINCIPAL;
+
+add_task(async function () {
+ waitForExplicitFinish();
+
+ const tabURL =
+ getRootDirectory(gTestPath) +
+ "browser_bug1184989_prevent_scrolling_when_preferences_flipped.xhtml";
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: tabURL },
+ async function (browser) {
+ let doc = browser.contentDocument;
+ let container = doc.getElementById("container");
+
+ // Test button
+ let button = doc.getElementById("button");
+ button.focus();
+ let initialScrollTop = container.scrollTop;
+ EventUtils.sendString(" ");
+ await checkPageScrolling(container, "button", initialScrollTop);
+
+ // Test checkbox
+ let checkbox = doc.getElementById("checkbox");
+ checkbox.focus();
+ initialScrollTop = container.scrollTop;
+ EventUtils.sendString(" ");
+ ok(checkbox.checked, "Checkbox is checked");
+ await checkPageScrolling(container, "checkbox", initialScrollTop);
+
+ // Test radio
+ let radiogroup = doc.getElementById("radiogroup");
+ radiogroup.focus();
+ initialScrollTop = container.scrollTop;
+ EventUtils.sendString(" ");
+ await checkPageScrolling(container, "radio", initialScrollTop);
+ }
+ );
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#search" },
+ async function (browser) {
+ let doc = browser.contentDocument;
+ let container = doc.getElementsByClassName("main-content")[0];
+
+ // Test search
+ let engineList = doc.getElementById("engineList");
+ engineList.focus();
+ let initialScrollTop = container.scrollTop;
+ EventUtils.sendString(" ");
+ is(
+ engineList.view.selection.currentIndex,
+ 0,
+ "Search engineList is selected"
+ );
+ EventUtils.sendString(" ");
+ await checkPageScrolling(
+ container,
+ "search engineList",
+ initialScrollTop
+ );
+ }
+ );
+
+ // Test session restore
+ const CRASH_URL = "about:mozilla";
+ const CRASH_FAVICON = "chrome://branding/content/icon32.png";
+ const CRASH_SHENTRY = { url: CRASH_URL };
+ const CRASH_TAB = { entries: [CRASH_SHENTRY], image: CRASH_FAVICON };
+ const CRASH_STATE = { windows: [{ tabs: [CRASH_TAB] }] };
+
+ const TAB_URL = "about:sessionrestore";
+ const TAB_FORMDATA = { url: TAB_URL, id: { sessionData: CRASH_STATE } };
+ const TAB_SHENTRY = { url: TAB_URL, triggeringPrincipal_base64 };
+ const TAB_STATE = { entries: [TAB_SHENTRY], formdata: TAB_FORMDATA };
+
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "about:blank"
+ ));
+
+ // Fake a post-crash tab
+ SessionStore.setTabState(tab, JSON.stringify(TAB_STATE));
+
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ let doc = tab.linkedBrowser.contentDocument;
+
+ // Make body scrollable
+ doc.body.style.height = doc.body.clientHeight + 100 + "px";
+
+ let tabsToggle = doc.getElementById("tabsToggle");
+ tabsToggle.focus();
+ let initialScrollTop = doc.documentElement.scrollTop;
+ EventUtils.sendString(" ");
+ await checkPageScrolling(
+ doc.documentElement,
+ "session restore",
+ initialScrollTop
+ );
+
+ gBrowser.removeCurrentTab();
+ finish();
+});
+
+function checkPageScrolling(container, type, initialScrollTop = 0) {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ is(
+ container.scrollTop,
+ initialScrollTop,
+ "Page should not scroll when " + type + " flipped"
+ );
+ resolve();
+ }, 0);
+ });
+}
diff --git a/browser/components/preferences/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xhtml b/browser/components/preferences/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xhtml
new file mode 100644
index 0000000000..8b3e39a39c
--- /dev/null
+++ b/browser/components/preferences/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xhtml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<!--
+ XUL Widget Test for Bug 1184989
+ -->
+<window title="Bug 1184989 Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<vbox id="container" style="height: 200px; overflow: auto;">
+ <vbox style="height: 500px;">
+ <hbox>
+ <button id="button" label="button" />
+ </hbox>
+
+ <hbox>
+ <checkbox id="checkbox" label="checkbox" />
+ </hbox>
+
+ <hbox>
+ <radiogroup id="radiogroup">
+ <radio id="radio" label="radio" />
+ </radiogroup>
+ </hbox>
+ </vbox>
+</vbox>
+
+</window>
diff --git a/browser/components/preferences/tests/browser_bug1547020_lockedDownloadDir.js b/browser/components/preferences/tests/browser_bug1547020_lockedDownloadDir.js
new file mode 100644
index 0000000000..cd5b00b5f2
--- /dev/null
+++ b/browser/components/preferences/tests/browser_bug1547020_lockedDownloadDir.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ Services.prefs.lockPref("browser.download.useDownloadDir");
+
+ await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ var downloadFolder = doc.getElementById("downloadFolder");
+ var chooseFolder = doc.getElementById("chooseFolder");
+ is(
+ downloadFolder.disabled,
+ false,
+ "Download folder field should not be disabled."
+ );
+ is(chooseFolder.disabled, false, "Choose folder should not be disabled.");
+
+ gBrowser.removeCurrentTab();
+
+ Services.prefs.unlockPref("browser.download.useDownloadDir");
+});
diff --git a/browser/components/preferences/tests/browser_bug1579418.js b/browser/components/preferences/tests/browser_bug1579418.js
new file mode 100644
index 0000000000..a179ae9936
--- /dev/null
+++ b/browser/components/preferences/tests/browser_bug1579418.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function default_homepage_test() {
+ let oldHomepagePref = Services.prefs.getCharPref("browser.startup.homepage");
+ let oldStartpagePref = Services.prefs.getIntPref("browser.startup.page");
+
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+
+ let doc = gBrowser.contentDocument;
+ let homeMode = doc.getElementById("homeMode");
+ let customSettings = doc.getElementById("customSettings");
+
+ // HOME_MODE_FIREFOX_HOME
+ homeMode.value = 0;
+
+ homeMode.dispatchEvent(new Event("command"));
+
+ is(Services.prefs.getCharPref("browser.startup.homepage"), "about:home");
+
+ // HOME_MODE_BLANK
+ homeMode.value = 1;
+
+ homeMode.dispatchEvent(new Event("command"));
+
+ await TestUtils.waitForCondition(
+ () => customSettings.hidden === true,
+ "Wait for customSettings to be hidden."
+ );
+
+ is(
+ Services.prefs.getCharPref("browser.startup.homepage"),
+ "chrome://browser/content/blanktab.html"
+ );
+
+ // HOME_MODE_CUSTOM
+ homeMode.value = 2;
+
+ homeMode.dispatchEvent(new Event("command"));
+
+ await TestUtils.waitForCondition(
+ () => customSettings.hidden === false,
+ "Wait for customSettings to be shown."
+ );
+
+ is(customSettings.hidden, false, "homePageURL should be visible");
+
+ registerCleanupFunction(async () => {
+ Services.prefs.setCharPref("browser.startup.homepage", oldHomepagePref);
+ Services.prefs.setIntPref("browser.startup.page", oldStartpagePref);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+});
diff --git a/browser/components/preferences/tests/browser_bug410900.js b/browser/components/preferences/tests/browser_bug410900.js
new file mode 100644
index 0000000000..4fd4bb21f5
--- /dev/null
+++ b/browser/components/preferences/tests/browser_bug410900.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+
+ // Setup a phony handler to ensure the app pane will be populated.
+ var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(
+ Ci.nsIWebHandlerApp
+ );
+ handler.name = "App pane alive test";
+ handler.uriTemplate = "http://test.mozilla.org/%s";
+
+ var extps = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ var info = extps.getProtocolHandlerInfo("apppanetest");
+ info.possibleApplicationHandlers.appendElement(handler);
+
+ var hserv = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
+ Ci.nsIHandlerService
+ );
+ hserv.store(info);
+
+ openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true })
+ .then(() => gBrowser.selectedBrowser.contentWindow.promiseLoadHandlersList)
+ .then(() => runTest(gBrowser.selectedBrowser.contentWindow));
+}
+
+function runTest(win) {
+ var rbox = win.document.getElementById("handlersView");
+ ok(rbox, "handlersView is present");
+
+ var items = rbox && rbox.getElementsByTagName("richlistitem");
+ ok(items && !!items.length, "App handler list populated");
+
+ var handlerAdded = false;
+ for (let i = 0; i < items.length; i++) {
+ if (items[i].getAttribute("type") == "apppanetest") {
+ handlerAdded = true;
+ }
+ }
+ ok(handlerAdded, "apppanetest protocol handler was successfully added");
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/components/preferences/tests/browser_bug731866.js b/browser/components/preferences/tests/browser_bug731866.js
new file mode 100644
index 0000000000..b090535a49
--- /dev/null
+++ b/browser/components/preferences/tests/browser_bug731866.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const browserContainersGroupDisabled = !SpecialPowers.getBoolPref(
+ "privacy.userContext.ui.enabled"
+);
+const cookieBannerHandlingDisabled = !SpecialPowers.getBoolPref(
+ "cookiebanners.ui.desktop.enabled"
+);
+const updatePrefContainers = ["updatesCategory", "updateApp"];
+const updateContainersGroupDisabled =
+ AppConstants.platform === "win" &&
+ Services.sysinfo.getProperty("hasWinPackageId");
+
+function test() {
+ waitForExplicitFinish();
+ open_preferences(runTest);
+}
+
+var gElements;
+
+function checkElements(expectedPane) {
+ for (let element of gElements) {
+ // keyset elements fail is_element_visible checks because they are never visible.
+ // special-case the drmGroup item because its visibility depends on pref + OS version
+ if (element.nodeName == "keyset" || element.id === "drmGroup") {
+ continue;
+ }
+
+ // The browserContainersGroup is still only pref-on on Nightly
+ if (
+ element.id == "browserContainersGroup" &&
+ browserContainersGroupDisabled
+ ) {
+ is_element_hidden(
+ element,
+ "Disabled browserContainersGroup should be hidden"
+ );
+ continue;
+ }
+
+ // Cookie Banner Handling is currently disabled by default (bug 1800679)
+ if (
+ element.id == "cookieBannerHandlingGroup" &&
+ cookieBannerHandlingDisabled
+ ) {
+ is_element_hidden(
+ element,
+ "Disabled cookieBannerHandlingGroup should be hidden"
+ );
+ continue;
+ }
+
+ // Update prefs are hidden when running an MSIX build
+ if (
+ updatePrefContainers.includes(element.id) &&
+ updateContainersGroupDisabled
+ ) {
+ is_element_hidden(element, "Disabled " + element + " should be hidden");
+ continue;
+ }
+
+ let attributeValue = element.getAttribute("data-category");
+ let suffix = " (id=" + element.id + ")";
+ if (attributeValue == "pane" + expectedPane) {
+ is_element_visible(
+ element,
+ expectedPane + " elements should be visible" + suffix
+ );
+ } else {
+ is_element_hidden(
+ element,
+ "Elements not in " + expectedPane + " should be hidden" + suffix
+ );
+ }
+ }
+}
+
+async function runTest(win) {
+ is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
+
+ let tab = win.document;
+ gElements = tab.getElementById("mainPrefPane").children;
+
+ let panes = ["General", "Search", "Privacy", "Sync"];
+
+ for (let pane of panes) {
+ await win.gotoPref("pane" + pane);
+ checkElements(pane);
+ }
+
+ gBrowser.removeCurrentTab();
+ win.close();
+ finish();
+}
diff --git a/browser/components/preferences/tests/browser_bug795764_cachedisabled.js b/browser/components/preferences/tests/browser_bug795764_cachedisabled.js
new file mode 100644
index 0000000000..97f5aaf48f
--- /dev/null
+++ b/browser/components/preferences/tests/browser_bug795764_cachedisabled.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+
+ // Adding one fake site so that the SiteDataManager would run.
+ // Otherwise, without any site then it would just return so we would end up in not testing SiteDataManager.
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "https://www.foo.com"
+ );
+ Services.perms.addFromPrincipal(
+ principal,
+ "persistent-storage",
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ );
+ registerCleanupFunction(function () {
+ Services.perms.removeFromPrincipal(principal, "persistent-storage");
+ });
+
+ SpecialPowers.pushPrefEnv({
+ set: [["privacy.userContext.ui.enabled", true]],
+ }).then(() => open_preferences(runTest));
+}
+
+async function runTest(win) {
+ is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
+
+ let tab = win.document;
+ let elements = tab.getElementById("mainPrefPane").children;
+
+ // Test if privacy pane is opened correctly
+ await win.gotoPref("panePrivacy");
+ for (let element of elements) {
+ let attributeValue = element.getAttribute("data-category");
+
+ // Ignore the cookie banner handling section, as it is currently preffed
+ // off by default (bug 1800679).
+ if (element.id === "cookieBannerHandlingGroup") {
+ continue;
+ }
+
+ if (attributeValue == "panePrivacy") {
+ is_element_visible(element, "HTTPSOnly should be visible");
+
+ is_element_visible(
+ element,
+ `Privacy element of id=${element.id} should be visible`
+ );
+ } else {
+ is_element_hidden(
+ element,
+ `Non-Privacy element of id=${element.id} should be hidden`
+ );
+ }
+ }
+
+ gBrowser.removeCurrentTab();
+ win.close();
+ finish();
+}
diff --git a/browser/components/preferences/tests/browser_cert_export.js b/browser/components/preferences/tests/browser_cert_export.js
new file mode 100644
index 0000000000..48769f84e6
--- /dev/null
+++ b/browser/components/preferences/tests/browser_cert_export.js
@@ -0,0 +1,161 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+
+function createTemporarySaveDirectory() {
+ var saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists()) {
+ info("create testsavedir!");
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+ info("return from createTempSaveDir: " + saveDir.path);
+ return saveDir;
+}
+
+// Create the folder the certificates will be saved into.
+var destDir = createTemporarySaveDirectory();
+registerCleanupFunction(function () {
+ destDir.remove(true);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+function stringOrArrayEquals(actual, expected, message) {
+ is(
+ typeof actual,
+ typeof expected,
+ "actual, expected should have the same type"
+ );
+ if (typeof expected == "string") {
+ is(actual, expected, message);
+ } else {
+ is(actual.toString(), expected.toString(), message);
+ }
+}
+
+var dialogWin;
+var exportButton;
+var expectedCert;
+
+async function setupTest() {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let certButton = gBrowser.selectedBrowser.contentDocument.getElementById(
+ "viewCertificatesButton"
+ );
+ certButton.scrollIntoView();
+ let certDialogLoaded = promiseLoadSubDialog(
+ "chrome://pippki/content/certManager.xhtml"
+ );
+ certButton.click();
+ dialogWin = await certDialogLoaded;
+ let doc = dialogWin.document;
+ doc.getElementById("certmanagertabs").selectedTab =
+ doc.getElementById("ca_tab");
+ let treeView = doc.getElementById("ca-tree").view;
+ // Select any which cert. Ignore parent rows (ie rows without certs):
+ for (let i = 0; i < treeView.rowCount; i++) {
+ treeView.selection.select(i);
+ dialogWin.getSelectedCerts();
+ let certs = dialogWin.selected_certs; // yuck... but this is how the dialog works.
+ if (certs && certs.length == 1 && certs[0]) {
+ expectedCert = certs[0];
+ // OK, we managed to select a cert!
+ break;
+ }
+ }
+
+ exportButton = doc.getElementById("ca_exportButton");
+ is(exportButton.disabled, false, "Should enable export button");
+}
+
+async function checkCertExportWorks(
+ exportType,
+ encoding,
+ expectedFileContents
+) {
+ MockFilePicker.displayDirectory = destDir;
+ var destFile = destDir.clone();
+ MockFilePicker.init(window);
+ MockFilePicker.filterIndex = exportType;
+ MockFilePicker.showCallback = function (fp) {
+ info("showCallback");
+ let fileName = fp.defaultString;
+ info("fileName: " + fileName);
+ destFile.append(fileName);
+ MockFilePicker.setFiles([destFile]);
+ info("done showCallback");
+ };
+ let finishedExporting = TestUtils.topicObserved("cert-export-finished");
+ exportButton.click();
+ await finishedExporting;
+ MockFilePicker.cleanup();
+ if (destFile && destFile.exists()) {
+ let contents;
+ if (encoding === "utf-8") {
+ contents = await IOUtils.readUTF8(destFile.path);
+ } else {
+ is(encoding, "", "expected either utf-8 or empty string for encoding");
+ contents = await IOUtils.read(destFile.path);
+ }
+ stringOrArrayEquals(
+ contents,
+ expectedFileContents,
+ "Should have written correct contents"
+ );
+ destFile.remove(false);
+ } else {
+ ok(false, "No cert saved!");
+ }
+}
+
+add_task(setupTest);
+
+add_task(async function checkCertPEMExportWorks() {
+ let expectedContents = dialogWin.getPEMString(expectedCert);
+ await checkCertExportWorks(0, /* 0 = PEM */ "utf-8", expectedContents);
+});
+
+add_task(async function checkCertPEMChainExportWorks() {
+ let expectedContents = dialogWin.getPEMString(expectedCert);
+ await checkCertExportWorks(
+ 1, // 1 = PEM chain, but the chain is of length 1
+ "utf-8",
+ expectedContents
+ );
+});
+
+add_task(async function checkCertDERExportWorks() {
+ let expectedContents = Uint8Array.from(expectedCert.getRawDER());
+ await checkCertExportWorks(2, /* 2 = DER */ "", expectedContents);
+});
+
+function stringToTypedArray(str) {
+ let arr = new Uint8Array(str.length);
+ for (let i = 0; i < arr.length; i++) {
+ arr[i] = str.charCodeAt(i);
+ }
+ return arr;
+}
+
+add_task(async function checkCertPKCS7ExportWorks() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ let expectedContents = stringToTypedArray(certdb.asPKCS7Blob([expectedCert]));
+ await checkCertExportWorks(3, /* 3 = PKCS7 */ "", expectedContents);
+});
+
+add_task(async function checkCertPKCS7ChainExportWorks() {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ let expectedContents = stringToTypedArray(certdb.asPKCS7Blob([expectedCert]));
+ await checkCertExportWorks(
+ 4, // 4 = PKCS7 chain, but the chain is of length 1
+ "",
+ expectedContents
+ );
+});
diff --git a/browser/components/preferences/tests/browser_change_app_handler.js b/browser/components/preferences/tests/browser_change_app_handler.js
new file mode 100644
index 0000000000..b4ca92592f
--- /dev/null
+++ b/browser/components/preferences/tests/browser_change_app_handler.js
@@ -0,0 +1,155 @@
+var gMimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+var gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
+ Ci.nsIHandlerService
+);
+
+SimpleTest.requestCompleteLog();
+
+function setupFakeHandler() {
+ let info = gMimeSvc.getFromTypeAndExtension("text/plain", "foo.txt");
+ ok(
+ info.possibleLocalHandlers.length,
+ "Should have at least one known handler"
+ );
+ let handler = info.possibleLocalHandlers.queryElementAt(
+ 0,
+ Ci.nsILocalHandlerApp
+ );
+
+ let infoToModify = gMimeSvc.getFromTypeAndExtension(
+ "text/x-test-handler",
+ null
+ );
+ infoToModify.possibleApplicationHandlers.appendElement(handler);
+
+ gHandlerSvc.store(infoToModify);
+}
+
+add_task(async function () {
+ setupFakeHandler();
+
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+ let win = gBrowser.selectedBrowser.contentWindow;
+
+ let container = win.document.getElementById("handlersView");
+ let ourItem = container.querySelector(
+ "richlistitem[type='text/x-test-handler']"
+ );
+ ok(ourItem, "handlersView is present");
+ ourItem.scrollIntoView();
+ container.selectItem(ourItem);
+ ok(ourItem.selected, "Should be able to select our item.");
+
+ let list = ourItem.querySelector(".actionsMenu");
+
+ let chooseItem = list.menupopup.querySelector(".choose-app-item");
+ let dialogLoadedPromise = promiseLoadSubDialog(
+ "chrome://global/content/appPicker.xhtml"
+ );
+ let cmdEvent = win.document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent(
+ "command",
+ true,
+ true,
+ win,
+ 0,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null,
+ 0
+ );
+ chooseItem.dispatchEvent(cmdEvent);
+
+ let dialog = await dialogLoadedPromise;
+ info("Dialog loaded");
+
+ let dialogDoc = dialog.document;
+ let dialogElement = dialogDoc.getElementById("app-picker");
+ let dialogList = dialogDoc.getElementById("app-picker-listbox");
+ dialogList.selectItem(dialogList.firstElementChild);
+ let selectedApp = dialogList.firstElementChild.handlerApp;
+ dialogElement.acceptDialog();
+
+ // Verify results are correct in mime service:
+ let mimeInfo = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ ok(
+ mimeInfo.preferredApplicationHandler.equals(selectedApp),
+ "App should be set as preferred."
+ );
+
+ // Check that we display this result:
+ ok(list.selectedItem, "Should have a selected item");
+ ok(
+ mimeInfo.preferredApplicationHandler.equals(list.selectedItem.handlerApp),
+ "App should be visible as preferred item."
+ );
+
+ // Now try to 'manage' this list:
+ dialogLoadedPromise = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/applicationManager.xhtml"
+ );
+
+ let manageItem = list.menupopup.querySelector(".manage-app-item");
+ cmdEvent = win.document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent(
+ "command",
+ true,
+ true,
+ win,
+ 0,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null,
+ 0
+ );
+ manageItem.dispatchEvent(cmdEvent);
+
+ dialog = await dialogLoadedPromise;
+ info("Dialog loaded the second time");
+
+ dialogDoc = dialog.document;
+ dialogElement = dialogDoc.getElementById("appManager");
+ dialogList = dialogDoc.getElementById("appList");
+ let itemToRemove = dialogList.querySelector(
+ 'richlistitem > label[value="' + selectedApp.name + '"]'
+ ).parentNode;
+ dialogList.selectItem(itemToRemove);
+ let itemsBefore = dialogList.children.length;
+ dialogDoc.getElementById("remove").click();
+ ok(!itemToRemove.parentNode, "Item got removed from DOM");
+ is(dialogList.children.length, itemsBefore - 1, "Item got removed");
+ dialogElement.acceptDialog();
+
+ // Verify results are correct in mime service:
+ mimeInfo = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ ok(
+ !mimeInfo.preferredApplicationHandler,
+ "App should no longer be set as preferred."
+ );
+
+ // Check that we display this result:
+ ok(list.selectedItem, "Should have a selected item");
+ ok(
+ !list.selectedItem.handlerApp,
+ "No app should be visible as preferred item."
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+registerCleanupFunction(function () {
+ let infoToModify = gMimeSvc.getFromTypeAndExtension(
+ "text/x-test-handler",
+ null
+ );
+ gHandlerSvc.remove(infoToModify);
+});
diff --git a/browser/components/preferences/tests/browser_checkspelling.js b/browser/components/preferences/tests/browser_checkspelling.js
new file mode 100644
index 0000000000..a7895b4201
--- /dev/null
+++ b/browser/components/preferences/tests/browser_checkspelling.js
@@ -0,0 +1,34 @@
+add_task(async function () {
+ SpecialPowers.pushPrefEnv({ set: [["layout.spellcheckDefault", 2]] });
+
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ let checkbox = doc.querySelector("#checkSpelling");
+ is(
+ checkbox.checked,
+ Services.prefs.getIntPref("layout.spellcheckDefault") == 2,
+ "checkbox should represent pref value before clicking on checkbox"
+ );
+ ok(
+ checkbox.checked,
+ "checkbox should be checked before clicking on checkbox"
+ );
+
+ checkbox.click();
+
+ is(
+ checkbox.checked,
+ Services.prefs.getIntPref("layout.spellcheckDefault") == 2,
+ "checkbox should represent pref value after clicking on checkbox"
+ );
+ ok(
+ !checkbox.checked,
+ "checkbox should not be checked after clicking on checkbox"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_connection.js b/browser/components/preferences/tests/browser_connection.js
new file mode 100644
index 0000000000..01cdf571f2
--- /dev/null
+++ b/browser/components/preferences/tests/browser_connection.js
@@ -0,0 +1,145 @@
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ // network.proxy.type needs to be backed up and restored because mochitest
+ // changes this setting from the default
+ let oldNetworkProxyType = Services.prefs.getIntPref("network.proxy.type");
+ registerCleanupFunction(function () {
+ Services.prefs.setIntPref("network.proxy.type", oldNetworkProxyType);
+ Services.prefs.clearUserPref("network.proxy.no_proxies_on");
+ // On accepting the dialog, we also write TRR values, so we need to clear
+ // them. They are tested separately in browser_privacy_dnsoverhttps.js.
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("network.trr.uri");
+ });
+
+ let connectionURL =
+ "chrome://browser/content/preferences/dialogs/connection.xhtml";
+
+ /*
+ The connection dialog alone won't save onaccept since it uses type="child",
+ 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) {
+ is(
+ gBrowser.currentURI.spec,
+ "about:preferences",
+ "about:preferences loaded"
+ );
+ let dialog = await openAndLoadSubDialog(connectionURL);
+ let dialogElement = dialog.document.getElementById("ConnectionsDialog");
+ let dialogClosingPromise = BrowserTestUtils.waitForEvent(
+ dialogElement,
+ "dialogclosing"
+ );
+
+ ok(dialog, "connection window opened");
+ runConnectionTests(dialog);
+ dialogElement.acceptDialog();
+
+ let dialogClosingEvent = await dialogClosingPromise;
+ ok(dialogClosingEvent, "connection window closed");
+ // runConnectionTests will have changed this pref - make sure it was
+ // sanitized correctly when the dialog was accepted
+ is(
+ Services.prefs.getCharPref("network.proxy.no_proxies_on"),
+ ".a.com,.b.com,.c.com",
+ "no_proxies_on pref has correct value"
+ );
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+}
+
+// run a bunch of tests on the window containing connection.xul
+function runConnectionTests(win) {
+ let doc = win.document;
+ let networkProxyNone = doc.getElementById("networkProxyNone");
+ let networkProxyNonePref = win.Preferences.get("network.proxy.no_proxies_on");
+ let networkProxyTypePref = win.Preferences.get("network.proxy.type");
+
+ // make sure the networkProxyNone textbox is formatted properly
+ is(networkProxyNone.localName, "textarea", "networkProxyNone is a textarea");
+ is(
+ networkProxyNone.getAttribute("rows"),
+ "2",
+ "networkProxyNone textbox has two rows"
+ );
+
+ // make sure manual proxy controls are disabled when the window is opened
+ let networkProxyHTTP = doc.getElementById("networkProxyHTTP");
+ is(networkProxyHTTP.disabled, true, "networkProxyHTTP textbox is disabled");
+
+ // check if sanitizing the given input for the no_proxies_on pref results in
+ // expected string
+ function testSanitize(input, expected, errorMessage) {
+ networkProxyNonePref.value = input;
+ win.gConnectionsDialog.sanitizeNoProxiesPref();
+ is(networkProxyNonePref.value, expected, errorMessage);
+ }
+
+ // change this pref so proxy exceptions are actually configurable
+ networkProxyTypePref.value = 1;
+ is(networkProxyNone.disabled, false, "networkProxyNone textbox is enabled");
+
+ testSanitize(".a.com", ".a.com", "sanitize doesn't mess up single filter");
+ testSanitize(
+ ".a.com, .b.com, .c.com",
+ ".a.com, .b.com, .c.com",
+ "sanitize doesn't mess up multiple comma/space sep filters"
+ );
+ testSanitize(
+ ".a.com\n.b.com",
+ ".a.com,.b.com",
+ "sanitize turns line break into comma"
+ );
+ testSanitize(
+ ".a.com,\n.b.com",
+ ".a.com,.b.com",
+ "sanitize doesn't add duplicate comma after comma"
+ );
+ testSanitize(
+ ".a.com\n,.b.com",
+ ".a.com,.b.com",
+ "sanitize doesn't add duplicate comma before comma"
+ );
+ testSanitize(
+ ".a.com,\n,.b.com",
+ ".a.com,,.b.com",
+ "sanitize doesn't add duplicate comma surrounded by commas"
+ );
+ testSanitize(
+ ".a.com, \n.b.com",
+ ".a.com, .b.com",
+ "sanitize doesn't add comma after comma/space"
+ );
+ testSanitize(
+ ".a.com\n .b.com",
+ ".a.com, .b.com",
+ "sanitize adds comma before space"
+ );
+ testSanitize(
+ ".a.com\n\n\n;;\n;\n.b.com",
+ ".a.com,.b.com",
+ "sanitize only adds one comma per substring of bad chars"
+ );
+ testSanitize(
+ ".a.com,,.b.com",
+ ".a.com,,.b.com",
+ "duplicate commas from user are untouched"
+ );
+ testSanitize(
+ ".a.com\n.b.com\n.c.com,\n.d.com,\n.e.com",
+ ".a.com,.b.com,.c.com,.d.com,.e.com",
+ "sanitize replaces things globally"
+ );
+
+ // will check that this was sanitized properly after window closes
+ networkProxyNonePref.value = ".a.com;.b.com\n.c.com";
+}
diff --git a/browser/components/preferences/tests/browser_connection_bug1445991.js b/browser/components/preferences/tests/browser_connection_bug1445991.js
new file mode 100644
index 0000000000..ecb5068a26
--- /dev/null
+++ b/browser/components/preferences/tests/browser_connection_bug1445991.js
@@ -0,0 +1,31 @@
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the disabled status of the autoconfig Reload button when the proxy type
+// is autoconfig (network.proxy.type == 2).
+add_task(async function testAutoconfigReloadButton() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.proxy.type", 2],
+ ["network.proxy.autoconfig_url", "file:///nonexistent.pac"],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+ const connectionURL =
+ "chrome://browser/content/preferences/dialogs/connection.xhtml";
+ const promiseDialogLoaded = promiseLoadSubDialog(connectionURL);
+ gBrowser.contentDocument.getElementById("connectionSettings").click();
+ const dialog = await promiseDialogLoaded;
+
+ ok(
+ !dialog.document.getElementById("autoReload").disabled,
+ "Reload button is enabled when proxy type is autoconfig"
+ );
+
+ dialog.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_connection_bug1505330.js b/browser/components/preferences/tests/browser_connection_bug1505330.js
new file mode 100644
index 0000000000..94dfd4706e
--- /dev/null
+++ b/browser/components/preferences/tests/browser_connection_bug1505330.js
@@ -0,0 +1,31 @@
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the disabled status of the autoconfig Reload button when the proxy type
+// is autoconfig (network.proxy.type == 2).
+add_task(async function testAutoconfigReloadButton() {
+ Services.prefs.lockPref("signon.autologin.proxy");
+
+ await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+ const connectionURL =
+ "chrome://browser/content/preferences/dialogs/connection.xhtml";
+ const promiseDialogLoaded = promiseLoadSubDialog(connectionURL);
+ gBrowser.contentDocument.getElementById("connectionSettings").click();
+ const dialog = await promiseDialogLoaded;
+
+ ok(
+ !dialog.document.getElementById("networkProxyType").firstChild.disabled,
+ "Connection options should not be disabled"
+ );
+ ok(
+ dialog.document.getElementById("autologinProxy").disabled,
+ "Proxy autologin should be disabled"
+ );
+
+ dialog.close();
+ Services.prefs.unlockPref("signon.autologin.proxy");
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_connection_bug388287.js b/browser/components/preferences/tests/browser_connection_bug388287.js
new file mode 100644
index 0000000000..d6f0c3c9d0
--- /dev/null
+++ b/browser/components/preferences/tests/browser_connection_bug388287.js
@@ -0,0 +1,124 @@
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+ const connectionURL =
+ "chrome://browser/content/preferences/dialogs/connection.xhtml";
+ let closeable = false;
+ let finalTest = false;
+
+ // The changed preferences need to be backed up and restored because this mochitest
+ // changes them setting from the default
+ let oldNetworkProxyType = Services.prefs.getIntPref("network.proxy.type");
+ registerCleanupFunction(function () {
+ Services.prefs.setIntPref("network.proxy.type", oldNetworkProxyType);
+ Services.prefs.clearUserPref("network.proxy.share_proxy_settings");
+ for (let proxyType of ["http", "ssl", "socks"]) {
+ Services.prefs.clearUserPref("network.proxy." + proxyType);
+ Services.prefs.clearUserPref("network.proxy." + proxyType + "_port");
+ if (proxyType == "http") {
+ continue;
+ }
+ Services.prefs.clearUserPref("network.proxy.backup." + proxyType);
+ Services.prefs.clearUserPref(
+ "network.proxy.backup." + proxyType + "_port"
+ );
+ }
+ // On accepting the dialog, we also write TRR values, so we need to clear
+ // them. They are tested separately in browser_privacy_dnsoverhttps.js.
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("network.trr.uri");
+ });
+
+ /*
+ The connection dialog alone won't save onaccept since it uses type="child",
+ 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) {
+ let dialog, dialogClosingPromise, dialogElement;
+ let proxyTypePref, sharePref, httpPref, httpPortPref;
+
+ // Convenient function to reset the variables for the new window
+ async function setDoc() {
+ if (closeable) {
+ let dialogClosingEvent = await dialogClosingPromise;
+ ok(dialogClosingEvent, "Connection dialog closed");
+ }
+
+ if (finalTest) {
+ gBrowser.removeCurrentTab();
+ finish();
+ return;
+ }
+
+ dialog = await openAndLoadSubDialog(connectionURL);
+ dialogElement = dialog.document.getElementById("ConnectionsDialog");
+ dialogClosingPromise = BrowserTestUtils.waitForEvent(
+ dialogElement,
+ "dialogclosing"
+ );
+
+ proxyTypePref = dialog.Preferences.get("network.proxy.type");
+ sharePref = dialog.Preferences.get("network.proxy.share_proxy_settings");
+ httpPref = dialog.Preferences.get("network.proxy.http");
+ httpPortPref = dialog.Preferences.get("network.proxy.http_port");
+ }
+
+ // This batch of tests should not close the dialog
+ await setDoc();
+
+ // Testing HTTP port 0 with share on
+ proxyTypePref.value = 1;
+ sharePref.value = true;
+ httpPref.value = "localhost";
+ httpPortPref.value = 0;
+ dialogElement.acceptDialog();
+
+ // Testing HTTP port 0 + FTP port 80 with share off
+ sharePref.value = false;
+ dialogElement.acceptDialog();
+
+ // Testing HTTP port 80 + FTP port 0 with share off
+ httpPortPref.value = 80;
+ dialogElement.acceptDialog();
+
+ // From now on, the dialog should close since we are giving it legitimate inputs.
+ // The test will timeout if the onbeforeaccept kicks in erroneously.
+ closeable = true;
+
+ // Both ports 80, share on
+ httpPortPref.value = 80;
+ dialogElement.acceptDialog();
+
+ // HTTP 80, FTP 0, with share on
+ await setDoc();
+ proxyTypePref.value = 1;
+ sharePref.value = true;
+ httpPref.value = "localhost";
+ httpPortPref.value = 80;
+ dialogElement.acceptDialog();
+
+ // HTTP host empty, port 0 with share on
+ await setDoc();
+ proxyTypePref.value = 1;
+ sharePref.value = true;
+ httpPref.value = "";
+ httpPortPref.value = 0;
+ dialogElement.acceptDialog();
+
+ // HTTP 0, but in no proxy mode
+ await setDoc();
+ proxyTypePref.value = 0;
+ sharePref.value = true;
+ httpPref.value = "localhost";
+ httpPortPref.value = 0;
+
+ // This is the final test, don't spawn another connection window
+ finalTest = true;
+ dialogElement.acceptDialog();
+ await setDoc();
+ });
+}
diff --git a/browser/components/preferences/tests/browser_containers_name_input.js b/browser/components/preferences/tests/browser_containers_name_input.js
new file mode 100644
index 0000000000..38785d3cb0
--- /dev/null
+++ b/browser/components/preferences/tests/browser_containers_name_input.js
@@ -0,0 +1,72 @@
+const CONTAINERS_URL =
+ "chrome://browser/content/preferences/dialogs/containers.xhtml";
+
+add_setup(async function () {
+ await openPreferencesViaOpenPreferencesAPI("containers", { leaveOpen: true });
+ registerCleanupFunction(async function () {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+});
+
+add_task(async function () {
+ async function openDialog() {
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ let dialogPromise = promiseLoadSubDialog(CONTAINERS_URL);
+
+ let addButton = doc.getElementById("containersAdd");
+ addButton.doCommand();
+
+ let dialog = await dialogPromise;
+
+ return dialog.document;
+ }
+
+ let { contentDocument } = gBrowser.selectedBrowser;
+ let containerNodes = Array.from(
+ contentDocument.querySelectorAll("[data-category=paneContainers]")
+ );
+ ok(
+ containerNodes.find(node => node.getBoundingClientRect().width > 0),
+ "Should actually be showing the container nodes."
+ );
+
+ let doc = await openDialog();
+
+ let name = doc.getElementById("name");
+ let btnApplyChanges = doc.querySelector("dialog").getButton("accept");
+
+ Assert.equal(name.value, "", "The name textbox should initlally be empty");
+ Assert.ok(
+ btnApplyChanges.disabled,
+ "The done button should initially be disabled"
+ );
+
+ function setName(value) {
+ name.value = value;
+
+ let event = new doc.defaultView.InputEvent("input", { data: value });
+ SpecialPowers.dispatchEvent(doc.defaultView, name, event);
+ }
+
+ setName("test");
+
+ Assert.ok(
+ !btnApplyChanges.disabled,
+ "The done button should be enabled when the value is not empty"
+ );
+
+ setName("");
+
+ Assert.ok(
+ btnApplyChanges.disabled,
+ "The done button should be disabled when the value is empty"
+ );
+
+ setName("\u0009\u000B\u000C\u0020\u00A0\uFEFF\u000A\u000D\u2028\u2029");
+
+ Assert.ok(
+ btnApplyChanges.disabled,
+ "The done button should be disabled when the value contains only whitespaces"
+ );
+});
diff --git a/browser/components/preferences/tests/browser_contentblocking.js b/browser/components/preferences/tests/browser_contentblocking.js
new file mode 100644
index 0000000000..19bd45153b
--- /dev/null
+++ b/browser/components/preferences/tests/browser_contentblocking.js
@@ -0,0 +1,1382 @@
+/* eslint-env webextensions */
+
+"use strict";
+
+const { Preferences } = ChromeUtils.importESModule(
+ "resource://gre/modules/Preferences.sys.mjs"
+);
+
+const TP_PREF = "privacy.trackingprotection.enabled";
+const TP_PBM_PREF = "privacy.trackingprotection.pbmode.enabled";
+const NCB_PREF = "network.cookie.cookieBehavior";
+const NCBP_PREF = "network.cookie.cookieBehavior.pbmode";
+const CAT_PREF = "browser.contentblocking.category";
+const FP_PREF = "privacy.trackingprotection.fingerprinting.enabled";
+const STP_PREF = "privacy.trackingprotection.socialtracking.enabled";
+const CM_PREF = "privacy.trackingprotection.cryptomining.enabled";
+const EMAIL_TP_PREF = "privacy.trackingprotection.emailtracking.enabled";
+const EMAIL_TP_PBM_PREF =
+ "privacy.trackingprotection.emailtracking.pbmode.enabled";
+const LEVEL2_PREF = "privacy.annotate_channels.strict_list.enabled";
+const REFERRER_PREF = "network.http.referer.disallowCrossSiteRelaxingDefault";
+const REFERRER_TOP_PREF =
+ "network.http.referer.disallowCrossSiteRelaxingDefault.top_navigation";
+const OCSP_PREF = "privacy.partition.network_state.ocsp_cache";
+const QUERY_PARAM_STRIP_PREF = "privacy.query_stripping.enabled";
+const QUERY_PARAM_STRIP_PBM_PREF = "privacy.query_stripping.enabled.pbmode";
+const PREF_TEST_NOTIFICATIONS =
+ "browser.safebrowsing.test-notifications.enabled";
+const STRICT_PREF = "browser.contentblocking.features.strict";
+const PRIVACY_PAGE = "about:preferences#privacy";
+const ISOLATE_UI_PREF =
+ "browser.contentblocking.reject-and-isolate-cookies.preferences.ui.enabled";
+const FPI_PREF = "privacy.firstparty.isolate";
+
+const { EnterprisePolicyTesting, PoliciesPrefTracker } =
+ ChromeUtils.importESModule(
+ "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
+ );
+
+requestLongerTimeout(2);
+
+add_task(async function testListUpdate() {
+ SpecialPowers.pushPrefEnv({ set: [[PREF_TEST_NOTIFICATIONS, true]] });
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+
+ let fingerprintersCheckbox = doc.getElementById(
+ "contentBlockingFingerprintersCheckbox"
+ );
+ let updateObserved = TestUtils.topicObserved("safebrowsing-update-attempt");
+ fingerprintersCheckbox.click();
+ let url = (await updateObserved)[1];
+
+ ok(true, "Has tried to update after the fingerprinting checkbox was toggled");
+ is(
+ url,
+ "http://127.0.0.1:8888/safebrowsing-dummy/update",
+ "Using the correct list url to update"
+ );
+
+ let cryptominersCheckbox = doc.getElementById(
+ "contentBlockingCryptominersCheckbox"
+ );
+ updateObserved = TestUtils.topicObserved("safebrowsing-update-attempt");
+ cryptominersCheckbox.click();
+ url = (await updateObserved)[1];
+
+ ok(true, "Has tried to update after the cryptomining checkbox was toggled");
+ is(
+ url,
+ "http://127.0.0.1:8888/safebrowsing-dummy/update",
+ "Using the correct list url to update"
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+// Tests that the content blocking main category checkboxes have the correct default state.
+add_task(async function testContentBlockingMainCategory() {
+ let prefs = [
+ [TP_PREF, false],
+ [TP_PBM_PREF, true],
+ [STP_PREF, false],
+ [NCB_PREF, Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER],
+ [
+ NCBP_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ [ISOLATE_UI_PREF, true],
+ [FPI_PREF, false],
+ ];
+
+ for (let pref of prefs) {
+ switch (typeof pref[1]) {
+ case "boolean":
+ SpecialPowers.setBoolPref(pref[0], pref[1]);
+ break;
+ case "number":
+ SpecialPowers.setIntPref(pref[0], pref[1]);
+ break;
+ }
+ }
+
+ let checkboxes = [
+ "#contentBlockingTrackingProtectionCheckbox",
+ "#contentBlockingBlockCookiesCheckbox",
+ ];
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+
+ for (let selector of checkboxes) {
+ let element = doc.querySelector(selector);
+ ok(element, "checkbox " + selector + " exists");
+ is(
+ element.getAttribute("checked"),
+ "true",
+ "checkbox " + selector + " is checked"
+ );
+ }
+
+ // Ensure the dependent controls of the tracking protection subsection behave properly.
+ let tpCheckbox = doc.querySelector(checkboxes[0]);
+
+ let dependentControls = ["#trackingProtectionMenu"];
+ let alwaysEnabledControls = [
+ "#trackingProtectionMenuDesc",
+ ".content-blocking-category-name",
+ "#changeBlockListLink",
+ ];
+
+ tpCheckbox.checked = true;
+
+ // Select "Always" under "All Detected Trackers".
+ let menu = doc.querySelector("#trackingProtectionMenu");
+ let always = doc.querySelector(
+ "#trackingProtectionMenu > menupopup > menuitem[value=always]"
+ );
+ let privateElement = doc.querySelector(
+ "#trackingProtectionMenu > menupopup > menuitem[value=private]"
+ );
+ menu.selectedItem = always;
+ ok(
+ !privateElement.selected,
+ "The Only in private windows item should not be selected"
+ );
+ ok(always.selected, "The Always item should be selected");
+
+ // The first time, privacy-pane-tp-ui-updated won't be dispatched since the
+ // assignment above is a no-op.
+
+ // Ensure the dependent controls are enabled
+ checkControlState(doc, dependentControls, true);
+ checkControlState(doc, alwaysEnabledControls, true);
+
+ let promise = TestUtils.topicObserved("privacy-pane-tp-ui-updated");
+ tpCheckbox.click();
+
+ await promise;
+ ok(!tpCheckbox.checked, "The checkbox should now be unchecked");
+
+ // Ensure the dependent controls are disabled
+ checkControlState(doc, dependentControls, false);
+ checkControlState(doc, alwaysEnabledControls, true);
+
+ // Make sure the selection in the tracking protection submenu persists after
+ // a few times of checking and unchecking All Detected Trackers.
+ // Doing this in a loop in order to avoid typing in the unrolled version manually.
+ // We need to go from the checked state of the checkbox to unchecked back to
+ // checked again...
+ for (let i = 0; i < 3; ++i) {
+ promise = TestUtils.topicObserved("privacy-pane-tp-ui-updated");
+ tpCheckbox.click();
+
+ await promise;
+ is(tpCheckbox.checked, i % 2 == 0, "The checkbox should now be unchecked");
+ is(
+ privateElement.selected,
+ i % 2 == 0,
+ "The Only in private windows item should be selected by default, when the checkbox is checked"
+ );
+ ok(!always.selected, "The Always item should no longer be selected");
+ }
+
+ let cookieMenu = doc.querySelector("#blockCookiesMenu");
+ let cookieMenuTrackers = cookieMenu.querySelector(
+ "menupopup > menuitem[value=trackers]"
+ );
+ let cookieMenuTrackersPlusIsolate = cookieMenu.querySelector(
+ "menupopup > menuitem[value=trackers-plus-isolate]"
+ );
+ let cookieMenuUnvisited = cookieMenu.querySelector(
+ "menupopup > menuitem[value=unvisited]"
+ );
+ let cookieMenuAllThirdParties = doc.querySelector(
+ "menupopup > menuitem[value=all-third-parties]"
+ );
+ let cookieMenuAll = cookieMenu.querySelector(
+ "menupopup > menuitem[value=always]"
+ );
+ // Select block trackers
+ cookieMenuTrackers.click();
+ ok(cookieMenuTrackers.selected, "The trackers item should be selected");
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ `${NCB_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER}`
+ );
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ `${NCBP_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN}`
+ );
+ // Select block trackers and isolate
+ cookieMenuTrackersPlusIsolate.click();
+ ok(
+ cookieMenuTrackersPlusIsolate.selected,
+ "The trackers plus isolate item should be selected"
+ );
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ `${NCB_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN}`
+ );
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ `${NCBP_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN}`
+ );
+ // Select block unvisited
+ cookieMenuUnvisited.click();
+ ok(cookieMenuUnvisited.selected, "The unvisited item should be selected");
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN,
+ `${NCB_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN}`
+ );
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ `${NCBP_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN}`
+ );
+ // Select block all third party
+ cookieMenuAllThirdParties.click();
+ ok(
+ cookieMenuAllThirdParties.selected,
+ "The all-third-parties item should be selected"
+ );
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ `${NCB_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN}`
+ );
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ `${NCBP_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN}`
+ );
+ // Select block all third party
+ cookieMenuAll.click();
+ ok(cookieMenuAll.selected, "The all cookies item should be selected");
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT,
+ `${NCB_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT}`
+ );
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ `${NCBP_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN}`
+ );
+
+ gBrowser.removeCurrentTab();
+
+ // Ensure the block-trackers-plus-isolate option only shows in the dropdown if the UI pref is set.
+ Services.prefs.setBoolPref(ISOLATE_UI_PREF, false);
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ doc = gBrowser.contentDocument;
+ cookieMenuTrackersPlusIsolate = doc.querySelector(
+ "#blockCookiesMenu menupopup > menuitem[value=trackers-plus-isolate]"
+ );
+ ok(
+ cookieMenuTrackersPlusIsolate.hidden,
+ "Trackers plus isolate option is hidden from the dropdown if the ui pref is not set."
+ );
+
+ gBrowser.removeCurrentTab();
+
+ // Ensure the block-trackers-plus-isolate option only shows in the dropdown if FPI is disabled.
+ SpecialPowers.setIntPref(
+ NCB_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
+ );
+ SpecialPowers.setBoolPref(FPI_PREF, true);
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ doc = gBrowser.contentDocument;
+ cookieMenuTrackers = doc.querySelector(
+ "#blockCookiesMenu menupopup > menuitem[value=trackers]"
+ );
+ cookieMenuTrackersPlusIsolate = doc.querySelector(
+ "#blockCookiesMenu menupopup > menuitem[value=trackers-plus-isolate]"
+ );
+ ok(cookieMenuTrackers.selected, "The trackers item should be selected");
+ ok(
+ cookieMenuTrackersPlusIsolate.hidden,
+ "Trackers plus isolate option is hidden from the dropdown if the FPI pref is set."
+ );
+ gBrowser.removeCurrentTab();
+
+ for (let pref of prefs) {
+ SpecialPowers.clearUserPref(pref[0]);
+ }
+});
+
+// Tests that the content blocking "Standard" category radio sets the prefs to their default values.
+add_task(async function testContentBlockingStandardCategory() {
+ let prefs = {
+ [TP_PREF]: null,
+ [TP_PBM_PREF]: null,
+ [NCB_PREF]: null,
+ [NCBP_PREF]: null,
+ [FP_PREF]: null,
+ [STP_PREF]: null,
+ [CM_PREF]: null,
+ [EMAIL_TP_PREF]: null,
+ [EMAIL_TP_PBM_PREF]: null,
+ [LEVEL2_PREF]: null,
+ [REFERRER_PREF]: null,
+ [REFERRER_TOP_PREF]: null,
+ [OCSP_PREF]: null,
+ [QUERY_PARAM_STRIP_PREF]: null,
+ [QUERY_PARAM_STRIP_PBM_PREF]: null,
+ };
+
+ for (let pref in prefs) {
+ Services.prefs.clearUserPref(pref);
+ switch (Services.prefs.getPrefType(pref)) {
+ case Services.prefs.PREF_BOOL:
+ prefs[pref] = Services.prefs.getBoolPref(pref);
+ break;
+ case Services.prefs.PREF_INT:
+ prefs[pref] = Services.prefs.getIntPref(pref);
+ break;
+ case Services.prefs.PREF_STRING:
+ prefs[pref] = Services.prefs.getCharPref(pref);
+ break;
+ default:
+ ok(false, `Unknown pref type for ${pref}`);
+ }
+ }
+
+ Services.prefs.setBoolPref(TP_PREF, true);
+ Services.prefs.setBoolPref(TP_PBM_PREF, false);
+ Services.prefs.setIntPref(
+ NCB_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+ Services.prefs.setIntPref(
+ NCBP_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+ Services.prefs.setBoolPref(STP_PREF, !Services.prefs.getBoolPref(STP_PREF));
+ Services.prefs.setBoolPref(FP_PREF, !Services.prefs.getBoolPref(FP_PREF));
+ Services.prefs.setBoolPref(CM_PREF, !Services.prefs.getBoolPref(CM_PREF));
+ Services.prefs.setBoolPref(
+ EMAIL_TP_PREF,
+ !Services.prefs.getBoolPref(EMAIL_TP_PREF)
+ );
+ Services.prefs.setBoolPref(
+ EMAIL_TP_PBM_PREF,
+ !Services.prefs.getBoolPref(EMAIL_TP_PBM_PREF)
+ );
+ Services.prefs.setBoolPref(
+ LEVEL2_PREF,
+ !Services.prefs.getBoolPref(LEVEL2_PREF)
+ );
+ Services.prefs.setBoolPref(
+ REFERRER_PREF,
+ !Services.prefs.getBoolPref(REFERRER_PREF)
+ );
+ Services.prefs.setBoolPref(
+ REFERRER_TOP_PREF,
+ !Services.prefs.getBoolPref(REFERRER_TOP_PREF)
+ );
+ Services.prefs.setBoolPref(OCSP_PREF, !Services.prefs.getBoolPref(OCSP_PREF));
+ Services.prefs.setBoolPref(
+ QUERY_PARAM_STRIP_PREF,
+ !Services.prefs.getBoolPref(QUERY_PARAM_STRIP_PREF)
+ );
+ Services.prefs.setBoolPref(
+ QUERY_PARAM_STRIP_PBM_PREF,
+ !Services.prefs.getBoolPref(QUERY_PARAM_STRIP_PBM_PREF)
+ );
+
+ for (let pref in prefs) {
+ switch (Services.prefs.getPrefType(pref)) {
+ case Services.prefs.PREF_BOOL:
+ // Account for prefs that may have retained their default value
+ if (Services.prefs.getBoolPref(pref) != prefs[pref]) {
+ ok(
+ Services.prefs.prefHasUserValue(pref),
+ `modified the pref ${pref}`
+ );
+ }
+ break;
+ case Services.prefs.PREF_INT:
+ if (Services.prefs.getIntPref(pref) != prefs[pref]) {
+ ok(
+ Services.prefs.prefHasUserValue(pref),
+ `modified the pref ${pref}`
+ );
+ }
+ break;
+ case Services.prefs.PREF_STRING:
+ if (Services.prefs.getCharPref(pref) != prefs[pref]) {
+ ok(
+ Services.prefs.prefHasUserValue(pref),
+ `modified the pref ${pref}`
+ );
+ }
+ break;
+ default:
+ ok(false, `Unknown pref type for ${pref}`);
+ }
+ }
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+
+ let standardRadioOption = doc.getElementById("standardRadio");
+ standardRadioOption.click();
+
+ // TP prefs are reset async to check for extensions controlling them.
+ await TestUtils.waitForCondition(
+ () => !Services.prefs.prefHasUserValue(TP_PREF)
+ );
+
+ for (let pref in prefs) {
+ ok(!Services.prefs.prefHasUserValue(pref), `reset the pref ${pref}`);
+ }
+ is(
+ Services.prefs.getStringPref(CAT_PREF),
+ "standard",
+ `${CAT_PREF} has been set to standard`
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+// Tests that the content blocking "Strict" category radio sets the prefs to the expected values.
+add_task(async function testContentBlockingStrictCategory() {
+ Services.prefs.setBoolPref(TP_PREF, false);
+ Services.prefs.setBoolPref(TP_PBM_PREF, false);
+ Services.prefs.setBoolPref(EMAIL_TP_PREF, false);
+ Services.prefs.setBoolPref(EMAIL_TP_PBM_PREF, false);
+ Services.prefs.setBoolPref(LEVEL2_PREF, false);
+ Services.prefs.setBoolPref(REFERRER_PREF, false);
+ Services.prefs.setBoolPref(REFERRER_TOP_PREF, false);
+ Services.prefs.setBoolPref(OCSP_PREF, false);
+ Services.prefs.setBoolPref(QUERY_PARAM_STRIP_PREF, false);
+ Services.prefs.setBoolPref(QUERY_PARAM_STRIP_PBM_PREF, false);
+ Services.prefs.setIntPref(
+ NCB_PREF,
+ Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN
+ );
+ Services.prefs.setIntPref(
+ NCBP_PREF,
+ Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN
+ );
+ let strict_pref = Services.prefs.getStringPref(STRICT_PREF).split(",");
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+
+ let strictRadioOption = doc.getElementById("strictRadio");
+ strictRadioOption.click();
+
+ // TP prefs are reset async to check for extensions controlling them.
+ await TestUtils.waitForCondition(
+ () => Services.prefs.getStringPref(CAT_PREF) == "strict"
+ );
+ // Depending on the definition of the STRICT_PREF, the dependant prefs may have been
+ // set to varying values. Ensure they have been set according to this definition.
+ for (let pref of strict_pref) {
+ switch (pref) {
+ case "tp":
+ is(
+ Services.prefs.getBoolPref(TP_PREF),
+ true,
+ `${TP_PREF} has been set to true`
+ );
+ break;
+ case "-tp":
+ is(
+ Services.prefs.getBoolPref(TP_PREF),
+ false,
+ `${TP_PREF} has been set to false`
+ );
+ break;
+ case "tpPrivate":
+ is(
+ Services.prefs.getBoolPref(TP_PBM_PREF),
+ true,
+ `${TP_PBM_PREF} has been set to true`
+ );
+ break;
+ case "-tpPrivate":
+ is(
+ Services.prefs.getBoolPref(TP_PBM_PREF),
+ false,
+ `${TP_PBM_PREF} has been set to false`
+ );
+ break;
+ case "fp":
+ is(
+ Services.prefs.getBoolPref(FP_PREF),
+ true,
+ `${FP_PREF} has been set to true`
+ );
+ break;
+ case "-fp":
+ is(
+ Services.prefs.getBoolPref(FP_PREF),
+ false,
+ `${FP_PREF} has been set to false`
+ );
+ break;
+ case "stp":
+ is(
+ Services.prefs.getBoolPref(STP_PREF),
+ true,
+ `${STP_PREF} has been set to true`
+ );
+ break;
+ case "-stp":
+ is(
+ Services.prefs.getBoolPref(STP_PREF),
+ false,
+ `${STP_PREF} has been set to false`
+ );
+ break;
+ case "cm":
+ is(
+ Services.prefs.getBoolPref(CM_PREF),
+ true,
+ `${CM_PREF} has been set to true`
+ );
+ break;
+ case "-cm":
+ is(
+ Services.prefs.getBoolPref(CM_PREF),
+ false,
+ `${CM_PREF} has been set to false`
+ );
+ break;
+ case "emailTP":
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PREF),
+ true,
+ `${EMAIL_TP_PREF} has been set to true`
+ );
+ break;
+ case "-emailTP":
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PREF),
+ false,
+ `${EMAIL_TP_PREF} has been set to false`
+ );
+ break;
+ case "emailTPPrivate":
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PBM_PREF),
+ true,
+ `${EMAIL_TP_PBM_PREF} has been set to true`
+ );
+ break;
+ case "-emailTPPrivate":
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PBM_PREF),
+ false,
+ `${EMAIL_TP_PBM_PREF} has been set to false`
+ );
+ break;
+ case "lvl2":
+ is(
+ Services.prefs.getBoolPref(LEVEL2_PREF),
+ true,
+ `${CM_PREF} has been set to true`
+ );
+ break;
+ case "-lvl2":
+ is(
+ Services.prefs.getBoolPref(LEVEL2_PREF),
+ false,
+ `${CM_PREF} has been set to false`
+ );
+ break;
+ case "rp":
+ is(
+ Services.prefs.getBoolPref(REFERRER_PREF),
+ true,
+ `${REFERRER_PREF} has been set to true`
+ );
+ break;
+ case "-rp":
+ is(
+ Services.prefs.getBoolPref(REFERRER_PREF),
+ false,
+ `${REFERRER_PREF} has been set to false`
+ );
+ break;
+ case "rpTop":
+ is(
+ Services.prefs.getBoolPref(REFERRER_TOP_PREF),
+ true,
+ `${REFERRER_TOP_PREF} has been set to true`
+ );
+ break;
+ case "-rpTop":
+ is(
+ Services.prefs.getBoolPref(REFERRER_TOP_PREF),
+ false,
+ `${REFERRER_TOP_PREF} has been set to false`
+ );
+ break;
+ case "ocsp":
+ is(
+ Services.prefs.getBoolPref(OCSP_PREF),
+ true,
+ `${OCSP_PREF} has been set to true`
+ );
+ break;
+ case "-ocsp":
+ is(
+ Services.prefs.getBoolPref(OCSP_PREF),
+ false,
+ `${OCSP_PREF} has been set to false`
+ );
+ break;
+ case "qps":
+ is(
+ Services.prefs.getBoolPref(QUERY_PARAM_STRIP_PREF),
+ true,
+ `${QUERY_PARAM_STRIP_PREF} has been set to true`
+ );
+ break;
+ case "-qps":
+ is(
+ Services.prefs.getBoolPref(QUERY_PARAM_STRIP_PREF),
+ false,
+ `${QUERY_PARAM_STRIP_PREF} has been set to false`
+ );
+ break;
+ case "qpsPBM":
+ is(
+ Services.prefs.getBoolPref(QUERY_PARAM_STRIP_PBM_PREF),
+ true,
+ `${QUERY_PARAM_STRIP_PBM_PREF} has been set to true`
+ );
+ break;
+ case "-qpsPBM":
+ is(
+ Services.prefs.getBoolPref(QUERY_PARAM_STRIP_PBM_PREF),
+ false,
+ `${QUERY_PARAM_STRIP_PBM_PREF} has been set to false`
+ );
+ break;
+ case "cookieBehavior0":
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_ACCEPT,
+ `${NCB_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_ACCEPT}`
+ );
+ break;
+ case "cookieBehavior1":
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ `${NCB_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN}`
+ );
+ break;
+ case "cookieBehavior2":
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT,
+ `${NCB_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT}`
+ );
+ break;
+ case "cookieBehavior3":
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN,
+ `${NCB_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN}`
+ );
+ break;
+ case "cookieBehavior4":
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ `${NCB_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER}`
+ );
+ break;
+ case "cookieBehavior5":
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ `${NCB_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN}`
+ );
+ break;
+ case "cookieBehaviorPBM0":
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_ACCEPT,
+ `${NCBP_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_ACCEPT}`
+ );
+ break;
+ case "cookieBehaviorPBM1":
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ `${NCBP_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN}`
+ );
+ break;
+ case "cookieBehaviorPBM2":
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT,
+ `${NCBP_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT}`
+ );
+ break;
+ case "cookieBehaviorPBM3":
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN,
+ `${NCBP_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN}`
+ );
+ break;
+ case "cookieBehaviorPBM4":
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ `${NCBP_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER}`
+ );
+ break;
+ case "cookieBehaviorPBM5":
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ `${NCBP_PREF} has been set to ${Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN}`
+ );
+ break;
+ default:
+ ok(false, "unknown option was added to the strict pref");
+ break;
+ }
+ }
+
+ gBrowser.removeCurrentTab();
+});
+
+// Tests that the content blocking "Custom" category behaves as expected.
+add_task(async function testContentBlockingCustomCategory() {
+ let untouchedPrefs = [
+ TP_PREF,
+ TP_PBM_PREF,
+ NCB_PREF,
+ NCBP_PREF,
+ FP_PREF,
+ STP_PREF,
+ CM_PREF,
+ REFERRER_PREF,
+ REFERRER_TOP_PREF,
+ OCSP_PREF,
+ QUERY_PARAM_STRIP_PREF,
+ QUERY_PARAM_STRIP_PBM_PREF,
+ ];
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+ let strictRadioOption = doc.getElementById("strictRadio");
+ let standardRadioOption = doc.getElementById("standardRadio");
+ let customRadioOption = doc.getElementById("customRadio");
+ let defaults = new Preferences({ defaultBranch: true });
+
+ standardRadioOption.click();
+ await TestUtils.waitForCondition(
+ () => !Services.prefs.prefHasUserValue(TP_PREF)
+ );
+
+ customRadioOption.click();
+ await TestUtils.waitForCondition(
+ () => Services.prefs.getStringPref(CAT_PREF) == "custom"
+ );
+
+ // The custom option will only force change of some prefs, like CAT_PREF. All
+ // other prefs should remain as they were for standard.
+ for (let pref of untouchedPrefs) {
+ ok(
+ !Services.prefs.prefHasUserValue(pref),
+ `the pref ${pref} remains as default value`
+ );
+ }
+
+ is(
+ Services.prefs.getStringPref(CAT_PREF),
+ "custom",
+ `${CAT_PREF} has been set to custom`
+ );
+
+ strictRadioOption.click();
+ await TestUtils.waitForCondition(
+ () => Services.prefs.getStringPref(CAT_PREF) == "strict"
+ );
+
+ // Changing the following prefs should necessarily set CAT_PREF to "custom"
+ for (let pref of [
+ FP_PREF,
+ STP_PREF,
+ CM_PREF,
+ TP_PREF,
+ TP_PBM_PREF,
+ REFERRER_PREF,
+ REFERRER_TOP_PREF,
+ OCSP_PREF,
+ QUERY_PARAM_STRIP_PREF,
+ QUERY_PARAM_STRIP_PBM_PREF,
+ ]) {
+ Services.prefs.setBoolPref(pref, !Services.prefs.getBoolPref(pref));
+ await TestUtils.waitForCondition(
+ () => Services.prefs.getStringPref(CAT_PREF) == "custom"
+ );
+ is(
+ Services.prefs.getStringPref(CAT_PREF),
+ "custom",
+ `${CAT_PREF} has been set to custom`
+ );
+
+ strictRadioOption.click();
+ await TestUtils.waitForCondition(
+ () => Services.prefs.getStringPref(CAT_PREF) == "strict"
+ );
+ }
+
+ // Changing the NCB_PREF should necessarily set CAT_PREF to "custom"
+ let defaultNCB = defaults.get(NCB_PREF);
+ let nonDefaultNCB;
+ switch (defaultNCB) {
+ case Ci.nsICookieService.BEHAVIOR_ACCEPT:
+ nonDefaultNCB = Ci.nsICookieService.BEHAVIOR_REJECT;
+ break;
+ case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
+ case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN:
+ nonDefaultNCB = Ci.nsICookieService.BEHAVIOR_ACCEPT;
+ break;
+ default:
+ ok(
+ false,
+ "Unexpected default value found for " + NCB_PREF + ": " + defaultNCB
+ );
+ break;
+ }
+ Services.prefs.setIntPref(NCB_PREF, nonDefaultNCB);
+ await TestUtils.waitForCondition(() =>
+ Services.prefs.prefHasUserValue(NCB_PREF)
+ );
+ is(
+ Services.prefs.getStringPref(CAT_PREF),
+ "custom",
+ `${CAT_PREF} has been set to custom`
+ );
+
+ strictRadioOption.click();
+ await TestUtils.waitForCondition(
+ () => Services.prefs.getStringPref(CAT_PREF) == "strict"
+ );
+
+ // Changing the NCBP_PREF should necessarily set CAT_PREF to "custom"
+ let defaultNCBP = defaults.get(NCBP_PREF);
+ let nonDefaultNCBP;
+ switch (defaultNCBP) {
+ case Ci.nsICookieService.BEHAVIOR_ACCEPT:
+ nonDefaultNCBP = Ci.nsICookieService.BEHAVIOR_REJECT;
+ break;
+ case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
+ case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN:
+ nonDefaultNCBP = Ci.nsICookieService.BEHAVIOR_ACCEPT;
+ break;
+ default:
+ ok(
+ false,
+ "Unexpected default value found for " + NCBP_PREF + ": " + defaultNCBP
+ );
+ break;
+ }
+ Services.prefs.setIntPref(NCBP_PREF, nonDefaultNCBP);
+ await TestUtils.waitForCondition(() =>
+ Services.prefs.prefHasUserValue(NCBP_PREF)
+ );
+ is(
+ Services.prefs.getStringPref(CAT_PREF),
+ "custom",
+ `${CAT_PREF} has been set to custom`
+ );
+
+ for (let pref of untouchedPrefs) {
+ SpecialPowers.clearUserPref(pref);
+ }
+
+ gBrowser.removeCurrentTab();
+});
+
+function checkControlState(doc, controls, enabled) {
+ for (let selector of controls) {
+ for (let control of doc.querySelectorAll(selector)) {
+ if (enabled) {
+ ok(!control.hasAttribute("disabled"), `${selector} is enabled.`);
+ } else {
+ is(
+ control.getAttribute("disabled"),
+ "true",
+ `${selector} is disabled.`
+ );
+ }
+ }
+ }
+}
+
+// Checks that the menulists for tracking protection and cookie blocking are disabled when all TP prefs are off.
+add_task(async function testContentBlockingDependentTPControls() {
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [TP_PREF, false],
+ [TP_PBM_PREF, false],
+ [NCB_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT],
+ [CAT_PREF, "custom"],
+ ],
+ });
+
+ let disabledControls = ["#trackingProtectionMenu", "#blockCookiesMenu"];
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+ checkControlState(doc, disabledControls, false);
+
+ gBrowser.removeCurrentTab();
+});
+
+// Checks that disabling tracking protection also disables email tracking protection.
+add_task(async function testDisableTPCheckBoxDisablesEmailTP() {
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [TP_PREF, false],
+ [TP_PBM_PREF, true],
+ [EMAIL_TP_PREF, false],
+ [EMAIL_TP_PBM_PREF, true],
+ [CAT_PREF, "custom"],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+
+ // Click the checkbox to disable TP and check if this disables Email TP.
+ let tpCheckbox = doc.getElementById(
+ "contentBlockingTrackingProtectionCheckbox"
+ );
+
+ // Verify the initial check state of the tracking protection checkbox.
+ is(
+ tpCheckbox.getAttribute("checked"),
+ "true",
+ "Tracking protection checkbox is checked initially"
+ );
+
+ tpCheckbox.click();
+
+ // Verify the checkbox is unchecked after clicking.
+ is(
+ tpCheckbox.getAttribute("checked"),
+ "",
+ "Tracking protection checkbox is unchecked"
+ );
+
+ // Verify the pref states.
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PREF),
+ false,
+ `${EMAIL_TP_PREF} has been set to false`
+ );
+
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PBM_PREF),
+ false,
+ `${EMAIL_TP_PBM_PREF} has been set to false`
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+// Checks that the email tracking prefs set properly with tracking protection
+// drop downs.
+add_task(async function testTPMenuForEmailTP() {
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [TP_PREF, false],
+ [TP_PBM_PREF, true],
+ [EMAIL_TP_PREF, false],
+ [EMAIL_TP_PBM_PREF, true],
+ [CAT_PREF, "custom"],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+
+ let menu = doc.querySelector("#trackingProtectionMenu");
+ let always = doc.querySelector(
+ "#trackingProtectionMenu > menupopup > menuitem[value=always]"
+ );
+ let privateElement = doc.querySelector(
+ "#trackingProtectionMenu > menupopup > menuitem[value=private]"
+ );
+
+ // Click the always option on the tracking protection drop down.
+ menu.selectedItem = always;
+ always.click();
+
+ // Verify the pref states.
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PREF),
+ true,
+ `${EMAIL_TP_PREF} has been set to true`
+ );
+
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PBM_PREF),
+ true,
+ `${EMAIL_TP_PBM_PREF} has been set to true`
+ );
+
+ // Click the private-only option on the tracking protection drop down.
+ menu.selectedItem = privateElement;
+ privateElement.click();
+
+ // Verify the pref states.
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PREF),
+ false,
+ `${EMAIL_TP_PREF} has been set to false`
+ );
+
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PBM_PREF),
+ true,
+ `${EMAIL_TP_PBM_PREF} has been set to true`
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+// Checks that social media trackers, cryptomining and fingerprinting visibility
+// can be controlled via pref.
+add_task(async function testCustomOptionsVisibility() {
+ Services.prefs.setBoolPref(
+ "browser.contentblocking.cryptomining.preferences.ui.enabled",
+ false
+ );
+ Services.prefs.setBoolPref(
+ "browser.contentblocking.fingerprinting.preferences.ui.enabled",
+ false
+ );
+ Services.prefs.setBoolPref(
+ "privacy.socialtracking.block_cookies.enabled",
+ false
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let doc = gBrowser.contentDocument;
+ let cryptominersOption = doc.getElementById(
+ "contentBlockingCryptominersOption"
+ );
+ let fingerprintersOption = doc.getElementById(
+ "contentBlockingFingerprintersOption"
+ );
+
+ ok(cryptominersOption.hidden, "Cryptomining is hidden");
+ ok(fingerprintersOption.hidden, "Fingerprinting is hidden");
+
+ gBrowser.removeCurrentTab();
+
+ Services.prefs.setBoolPref(
+ "browser.contentblocking.cryptomining.preferences.ui.enabled",
+ true
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ doc = gBrowser.contentDocument;
+ cryptominersOption = doc.getElementById("contentBlockingCryptominersOption");
+ fingerprintersOption = doc.getElementById(
+ "contentBlockingFingerprintersOption"
+ );
+
+ ok(!cryptominersOption.hidden, "Cryptomining is shown");
+ ok(fingerprintersOption.hidden, "Fingerprinting is hidden");
+
+ gBrowser.removeCurrentTab();
+
+ Services.prefs.setBoolPref(
+ "browser.contentblocking.fingerprinting.preferences.ui.enabled",
+ true
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ doc = gBrowser.contentDocument;
+ cryptominersOption = doc.getElementById("contentBlockingCryptominersOption");
+ fingerprintersOption = doc.getElementById(
+ "contentBlockingFingerprintersOption"
+ );
+
+ ok(!cryptominersOption.hidden, "Cryptomining is shown");
+ ok(!fingerprintersOption.hidden, "Fingerprinting is shown");
+
+ gBrowser.removeCurrentTab();
+
+ // Social media trackers UI should be hidden
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ doc = gBrowser.contentDocument;
+ let socialTrackingUI = [...doc.querySelectorAll(".social-media-option")];
+
+ ok(
+ socialTrackingUI.every(el => el.hidden),
+ "All Social media tracker UI instances are hidden"
+ );
+
+ gBrowser.removeCurrentTab();
+
+ // Social media trackers UI should be visible
+ Services.prefs.setBoolPref(
+ "privacy.socialtracking.block_cookies.enabled",
+ true
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ doc = gBrowser.contentDocument;
+ socialTrackingUI = [...doc.querySelectorAll(".social-media-option")];
+
+ ok(
+ !socialTrackingUI.every(el => el.hidden),
+ "All Social media tracker UI instances are visible"
+ );
+
+ gBrowser.removeCurrentTab();
+
+ Services.prefs.clearUserPref(
+ "browser.contentblocking.cryptomining.preferences.ui.enabled"
+ );
+ Services.prefs.clearUserPref(
+ "browser.contentblocking.fingerprinting.preferences.ui.enabled"
+ );
+ Services.prefs.clearUserPref("privacy.socialtracking.block_cookies.enabled");
+});
+
+// Checks that adding a custom enterprise policy will put the user in the custom category.
+// Other categories will be disabled.
+add_task(async function testPolicyCategorization() {
+ Services.prefs.setStringPref(CAT_PREF, "standard");
+ is(
+ Services.prefs.getStringPref(CAT_PREF),
+ "standard",
+ `${CAT_PREF} starts on standard`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(TP_PREF),
+ `${TP_PREF} starts with the default value`
+ );
+ PoliciesPrefTracker.start();
+
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson({
+ policies: {
+ EnableTrackingProtection: {
+ Value: true,
+ },
+ },
+ });
+ EnterprisePolicyTesting.checkPolicyPref(TP_PREF, true, false);
+ is(
+ Services.prefs.getStringPref(CAT_PREF),
+ "custom",
+ `${CAT_PREF} has been set to custom`
+ );
+
+ Services.prefs.setStringPref(CAT_PREF, "standard");
+ is(
+ Services.prefs.getStringPref(CAT_PREF),
+ "standard",
+ `${CAT_PREF} starts on standard`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(NCB_PREF),
+ `${NCB_PREF} starts with the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(NCBP_PREF),
+ `${NCBP_PREF} starts with the default value`
+ );
+
+ let uiUpdatedPromise = TestUtils.topicObserved("privacy-pane-tp-ui-updated");
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson({
+ policies: {
+ Cookies: {
+ AcceptThirdParty: "never",
+ Locked: true,
+ },
+ },
+ });
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await uiUpdatedPromise;
+
+ EnterprisePolicyTesting.checkPolicyPref(
+ NCB_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ true
+ );
+ EnterprisePolicyTesting.checkPolicyPref(
+ NCBP_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
+ true
+ );
+ is(
+ Services.prefs.getStringPref(CAT_PREF),
+ "custom",
+ `${CAT_PREF} has been set to custom`
+ );
+
+ let doc = gBrowser.contentDocument;
+ let strictRadioOption = doc.getElementById("strictRadio");
+ let standardRadioOption = doc.getElementById("standardRadio");
+ is(strictRadioOption.disabled, true, "the strict option is disabled");
+ is(standardRadioOption.disabled, true, "the standard option is disabled");
+
+ gBrowser.removeCurrentTab();
+
+ // Cleanup after this particular test.
+ if (Services.policies.status != Ci.nsIEnterprisePolicies.INACTIVE) {
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson({
+ policies: {
+ Cookies: {
+ Locked: false,
+ },
+ },
+ });
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson("");
+ }
+ is(
+ Services.policies.status,
+ Ci.nsIEnterprisePolicies.INACTIVE,
+ "Engine is inactive at the end of the test"
+ );
+
+ EnterprisePolicyTesting.resetRunOnceState();
+ PoliciesPrefTracker.stop();
+});
+
+// Tests that changing a content blocking pref shows the content blocking warning
+// to reload tabs to apply changes.
+add_task(async function testContentBlockingReloadWarning() {
+ Services.prefs.setStringPref(CAT_PREF, "standard");
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+ let reloadWarnings = [
+ ...doc.querySelectorAll(".content-blocking-warning.reload-tabs"),
+ ];
+ let allHidden = reloadWarnings.every(el => el.hidden);
+ ok(allHidden, "all of the warnings to reload tabs are initially hidden");
+
+ Services.prefs.setStringPref(CAT_PREF, "strict");
+
+ let strictWarning = doc.querySelector(
+ "#contentBlockingOptionStrict .content-blocking-warning.reload-tabs"
+ );
+ ok(
+ !BrowserTestUtils.is_hidden(strictWarning),
+ "The warning in the strict section should be showing"
+ );
+
+ Services.prefs.setStringPref(CAT_PREF, "standard");
+ gBrowser.removeCurrentTab();
+});
+
+// Tests that changing a content blocking pref does not show the content blocking warning
+// if it is the only tab.
+add_task(async function testContentBlockingReloadWarningSingleTab() {
+ Services.prefs.setStringPref(CAT_PREF, "standard");
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, PRIVACY_PAGE);
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ PRIVACY_PAGE
+ );
+
+ let reloadWarnings = [
+ ...gBrowser.contentDocument.querySelectorAll(
+ ".content-blocking-warning.reload-tabs"
+ ),
+ ];
+ ok(reloadWarnings.length, "must have at least one reload warning");
+ ok(
+ reloadWarnings.every(el => el.hidden),
+ "all of the warnings to reload tabs are initially hidden"
+ );
+
+ is(BrowserWindowTracker.windowCount, 1, "There is only one window open");
+ is(gBrowser.tabs.length, 1, "There is only one tab open");
+ Services.prefs.setStringPref(CAT_PREF, "strict");
+
+ ok(
+ reloadWarnings.every(el => el.hidden),
+ "all of the warnings to reload tabs are still hidden"
+ );
+ Services.prefs.setStringPref(CAT_PREF, "standard");
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, "about:newtab");
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+});
+
+// Checks that the reload tabs message reloads all tabs except the active tab.
+add_task(async function testReloadTabsMessage() {
+ Services.prefs.setStringPref(CAT_PREF, "strict");
+ let exampleTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://example.com"
+ );
+ let examplePinnedTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://example.com"
+ );
+ gBrowser.pinTab(examplePinnedTab);
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+ let standardWarning = doc.querySelector(
+ "#contentBlockingOptionStandard .content-blocking-warning.reload-tabs"
+ );
+ let standardReloadButton = doc.querySelector(
+ "#contentBlockingOptionStandard .reload-tabs-button"
+ );
+
+ Services.prefs.setStringPref(CAT_PREF, "standard");
+ ok(
+ !BrowserTestUtils.is_hidden(standardWarning),
+ "The warning in the standard section should be showing"
+ );
+
+ let exampleTabBrowserDiscardedPromise = BrowserTestUtils.waitForEvent(
+ exampleTab,
+ "TabBrowserDiscarded"
+ );
+ let examplePinnedTabLoadPromise = BrowserTestUtils.browserLoaded(
+ examplePinnedTab.linkedBrowser
+ );
+ standardReloadButton.click();
+ // The pinned example page had a load event
+ await examplePinnedTabLoadPromise;
+ // The other one had its browser discarded
+ await exampleTabBrowserDiscardedPromise;
+
+ ok(
+ BrowserTestUtils.is_hidden(standardWarning),
+ "The warning in the standard section should have hidden after being clicked"
+ );
+
+ // cleanup
+ Services.prefs.setStringPref(CAT_PREF, "standard");
+ gBrowser.removeTab(exampleTab);
+ gBrowser.removeTab(examplePinnedTab);
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_contentblocking_categories.js b/browser/components/preferences/tests/browser_contentblocking_categories.js
new file mode 100644
index 0000000000..3b9e16a2fe
--- /dev/null
+++ b/browser/components/preferences/tests/browser_contentblocking_categories.js
@@ -0,0 +1,487 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env webextensions */
+
+ChromeUtils.defineESModuleGetters(this, {
+ Preferences: "resource://gre/modules/Preferences.sys.mjs",
+});
+
+const TP_PREF = "privacy.trackingprotection.enabled";
+const TP_PBM_PREF = "privacy.trackingprotection.pbmode.enabled";
+const NCB_PREF = "network.cookie.cookieBehavior";
+const NCBP_PREF = "network.cookie.cookieBehavior.pbmode";
+const CAT_PREF = "browser.contentblocking.category";
+const FP_PREF = "privacy.trackingprotection.fingerprinting.enabled";
+const CM_PREF = "privacy.trackingprotection.cryptomining.enabled";
+const STP_PREF = "privacy.trackingprotection.socialtracking.enabled";
+const EMAIL_TP_PREF = "privacy.trackingprotection.emailtracking.enabled";
+const EMAIL_TP_PBM_PREF =
+ "privacy.trackingprotection.emailtracking.pbmode.enabled";
+const LEVEL2_PREF = "privacy.annotate_channels.strict_list.enabled";
+const REFERRER_PREF = "network.http.referer.disallowCrossSiteRelaxingDefault";
+const REFERRER_TOP_PREF =
+ "network.http.referer.disallowCrossSiteRelaxingDefault.top_navigation";
+const OCSP_PREF = "privacy.partition.network_state.ocsp_cache";
+const QUERY_PARAM_STRIP_PREF = "privacy.query_stripping.enabled";
+const QUERY_PARAM_STRIP_PBM_PREF = "privacy.query_stripping.enabled.pbmode";
+const STRICT_DEF_PREF = "browser.contentblocking.features.strict";
+
+// Tests that the content blocking standard category definition is based on the default settings of
+// the content blocking prefs.
+// Changing the definition does not remove the user from the category.
+add_task(async function testContentBlockingStandardDefinition() {
+ Services.prefs.setStringPref(CAT_PREF, "strict");
+ Services.prefs.setStringPref(CAT_PREF, "standard");
+ is(
+ Services.prefs.getStringPref(CAT_PREF),
+ "standard",
+ `${CAT_PREF} starts on standard`
+ );
+
+ ok(
+ !Services.prefs.prefHasUserValue(TP_PREF),
+ `${TP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(TP_PBM_PREF),
+ `${TP_PBM_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(FP_PREF),
+ `${FP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(CM_PREF),
+ `${CM_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(STP_PREF),
+ `${STP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(EMAIL_TP_PREF),
+ `${EMAIL_TP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(EMAIL_TP_PBM_PREF),
+ `${EMAIL_TP_PBM_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(NCB_PREF),
+ `${NCB_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(NCBP_PREF),
+ `${NCBP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(LEVEL2_PREF),
+ `${LEVEL2_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(REFERRER_PREF),
+ `${REFERRER_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(REFERRER_TOP_PREF),
+ `${REFERRER_TOP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(OCSP_PREF),
+ `${OCSP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(QUERY_PARAM_STRIP_PREF),
+ `${QUERY_PARAM_STRIP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(QUERY_PARAM_STRIP_PBM_PREF),
+ `${QUERY_PARAM_STRIP_PBM_PREF} pref has the default value`
+ );
+
+ let defaults = Services.prefs.getDefaultBranch("");
+ let originalTP = defaults.getBoolPref(TP_PREF);
+ let originalTPPBM = defaults.getBoolPref(TP_PBM_PREF);
+ let originalFP = defaults.getBoolPref(FP_PREF);
+ let originalCM = defaults.getBoolPref(CM_PREF);
+ let originalSTP = defaults.getBoolPref(STP_PREF);
+ let originalEmailTP = defaults.getBoolPref(EMAIL_TP_PREF);
+ let originalEmailTPPBM = defaults.getBoolPref(EMAIL_TP_PBM_PREF);
+ let originalNCB = defaults.getIntPref(NCB_PREF);
+ let originalNCBP = defaults.getIntPref(NCBP_PREF);
+ let originalLEVEL2 = defaults.getBoolPref(LEVEL2_PREF);
+ let originalREFERRER = defaults.getBoolPref(REFERRER_PREF);
+ let originalREFERRERTOP = defaults.getBoolPref(REFERRER_TOP_PREF);
+ let originalOCSP = defaults.getBoolPref(OCSP_PREF);
+ let originalQueryParamStrip = defaults.getBoolPref(QUERY_PARAM_STRIP_PREF);
+ let originalQueryParamStripPBM = defaults.getBoolPref(
+ QUERY_PARAM_STRIP_PBM_PREF
+ );
+
+ let nonDefaultNCB;
+ switch (originalNCB) {
+ case Ci.nsICookieService.BEHAVIOR_ACCEPT:
+ nonDefaultNCB = Ci.nsICookieService.BEHAVIOR_REJECT;
+ break;
+ default:
+ nonDefaultNCB = Ci.nsICookieService.BEHAVIOR_ACCEPT;
+ break;
+ }
+ let nonDefaultNCBP;
+ switch (originalNCBP) {
+ case Ci.nsICookieService.BEHAVIOR_ACCEPT:
+ nonDefaultNCBP = Ci.nsICookieService.BEHAVIOR_REJECT;
+ break;
+ default:
+ nonDefaultNCBP = Ci.nsICookieService.BEHAVIOR_ACCEPT;
+ break;
+ }
+ defaults.setIntPref(NCB_PREF, nonDefaultNCB);
+ defaults.setIntPref(NCBP_PREF, nonDefaultNCBP);
+ defaults.setBoolPref(TP_PREF, !originalTP);
+ defaults.setBoolPref(TP_PBM_PREF, !originalTPPBM);
+ defaults.setBoolPref(FP_PREF, !originalFP);
+ defaults.setBoolPref(CM_PREF, !originalCM);
+ defaults.setBoolPref(CM_PREF, !originalSTP);
+ defaults.setBoolPref(EMAIL_TP_PREF, !originalEmailTP);
+ defaults.setBoolPref(EMAIL_TP_PBM_PREF, !originalEmailTPPBM);
+ defaults.setIntPref(NCB_PREF, !originalNCB);
+ defaults.setBoolPref(LEVEL2_PREF, !originalLEVEL2);
+ defaults.setBoolPref(REFERRER_PREF, !originalREFERRER);
+ defaults.setBoolPref(REFERRER_TOP_PREF, !originalREFERRERTOP);
+ defaults.setBoolPref(OCSP_PREF, !originalOCSP);
+ defaults.setBoolPref(QUERY_PARAM_STRIP_PREF, !originalQueryParamStrip);
+ defaults.setBoolPref(QUERY_PARAM_STRIP_PBM_PREF, !originalQueryParamStripPBM);
+
+ ok(
+ !Services.prefs.prefHasUserValue(TP_PREF),
+ `${TP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(TP_PBM_PREF),
+ `${TP_PBM_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(FP_PREF),
+ `${FP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(CM_PREF),
+ `${CM_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(STP_PREF),
+ `${STP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(EMAIL_TP_PREF),
+ `${EMAIL_TP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(EMAIL_TP_PBM_PREF),
+ `${EMAIL_TP_PBM_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(NCB_PREF),
+ `${NCB_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(NCBP_PREF),
+ `${NCBP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(LEVEL2_PREF),
+ `${LEVEL2_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(REFERRER_PREF),
+ `${REFERRER_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(REFERRER_TOP_PREF),
+ `${REFERRER_TOP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(OCSP_PREF),
+ `${OCSP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(QUERY_PARAM_STRIP_PREF),
+ `${QUERY_PARAM_STRIP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(QUERY_PARAM_STRIP_PBM_PREF),
+ `${QUERY_PARAM_STRIP_PBM_PREF} pref has the default value`
+ );
+
+ // cleanup
+ defaults.setIntPref(NCB_PREF, originalNCB);
+ defaults.setBoolPref(TP_PREF, originalTP);
+ defaults.setBoolPref(TP_PBM_PREF, originalTPPBM);
+ defaults.setBoolPref(FP_PREF, originalFP);
+ defaults.setBoolPref(CM_PREF, originalCM);
+ defaults.setBoolPref(STP_PREF, originalSTP);
+ defaults.setBoolPref(EMAIL_TP_PREF, originalEmailTP);
+ defaults.setBoolPref(EMAIL_TP_PBM_PREF, originalEmailTPPBM);
+ defaults.setIntPref(NCB_PREF, originalNCB);
+ defaults.setIntPref(NCBP_PREF, originalNCBP);
+ defaults.setBoolPref(LEVEL2_PREF, originalLEVEL2);
+ defaults.setBoolPref(REFERRER_PREF, originalREFERRER);
+ defaults.setBoolPref(REFERRER_TOP_PREF, originalREFERRERTOP);
+ defaults.setBoolPref(OCSP_PREF, originalOCSP);
+ defaults.setBoolPref(QUERY_PARAM_STRIP_PREF, originalQueryParamStrip);
+ defaults.setBoolPref(QUERY_PARAM_STRIP_PBM_PREF, originalQueryParamStripPBM);
+});
+
+// Tests that the content blocking strict category definition changes the behavior
+// of the strict category pref and all prefs it controls.
+// Changing the definition does not remove the user from the category.
+add_task(async function testContentBlockingStrictDefinition() {
+ let defaults = Services.prefs.getDefaultBranch("");
+ let originalStrictPref = defaults.getStringPref(STRICT_DEF_PREF);
+ defaults.setStringPref(
+ STRICT_DEF_PREF,
+ "tp,tpPrivate,fp,cm,cookieBehavior0,cookieBehaviorPBM0,stp,emailTP,emailTPPrivate,lvl2,rp,rpTop,ocsp,qps,qpsPBM"
+ );
+ Services.prefs.setStringPref(CAT_PREF, "strict");
+ is(
+ Services.prefs.getStringPref(CAT_PREF),
+ "strict",
+ `${CAT_PREF} has changed to strict`
+ );
+
+ ok(
+ !Services.prefs.prefHasUserValue(STRICT_DEF_PREF),
+ `We changed the default value of ${STRICT_DEF_PREF}`
+ );
+ is(
+ Services.prefs.getStringPref(STRICT_DEF_PREF),
+ "tp,tpPrivate,fp,cm,cookieBehavior0,cookieBehaviorPBM0,stp,emailTP,emailTPPrivate,lvl2,rp,rpTop,ocsp,qps,qpsPBM",
+ `${STRICT_DEF_PREF} changed to what we set.`
+ );
+
+ is(
+ Services.prefs.getBoolPref(TP_PREF),
+ true,
+ `${TP_PREF} pref has been set to true`
+ );
+ is(
+ Services.prefs.getBoolPref(TP_PBM_PREF),
+ true,
+ `${TP_PBM_PREF} pref has been set to true`
+ );
+ is(
+ Services.prefs.getBoolPref(FP_PREF),
+ true,
+ `${CM_PREF} pref has been set to true`
+ );
+ is(
+ Services.prefs.getBoolPref(CM_PREF),
+ true,
+ `${CM_PREF} pref has been set to true`
+ );
+ is(
+ Services.prefs.getBoolPref(STP_PREF),
+ true,
+ `${STP_PREF} pref has been set to true`
+ );
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PREF),
+ true,
+ `${EMAIL_TP_PREF} pref has been set to true`
+ );
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PBM_PREF),
+ true,
+ `${EMAIL_TP_PBM_PREF} pref has been set to true`
+ );
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_ACCEPT,
+ `${NCB_PREF} has been set to BEHAVIOR_ACCEPT`
+ );
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_ACCEPT,
+ `${NCBP_PREF} has been set to BEHAVIOR_ACCEPT`
+ );
+ is(
+ Services.prefs.getBoolPref(LEVEL2_PREF),
+ true,
+ `${LEVEL2_PREF} pref has been set to true`
+ );
+ is(
+ Services.prefs.getBoolPref(REFERRER_PREF),
+ true,
+ `${REFERRER_PREF} pref has been set to true`
+ );
+ is(
+ Services.prefs.getBoolPref(REFERRER_TOP_PREF),
+ true,
+ `${REFERRER_TOP_PREF} pref has been set to true`
+ );
+ is(
+ Services.prefs.getBoolPref(OCSP_PREF),
+ true,
+ `${OCSP_PREF} pref has been set to true`
+ );
+ is(
+ Services.prefs.getBoolPref(QUERY_PARAM_STRIP_PREF),
+ true,
+ `${QUERY_PARAM_STRIP_PREF} pref has been set to true`
+ );
+ is(
+ Services.prefs.getBoolPref(QUERY_PARAM_STRIP_PBM_PREF),
+ true,
+ `${QUERY_PARAM_STRIP_PBM_PREF} pref has been set to true`
+ );
+
+ // Note, if a pref is not listed it will use the default value, however this is only meant as a
+ // backup if a mistake is made. The UI will not respond correctly.
+ defaults.setStringPref(STRICT_DEF_PREF, "");
+ ok(
+ !Services.prefs.prefHasUserValue(TP_PREF),
+ `${TP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(TP_PBM_PREF),
+ `${TP_PBM_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(FP_PREF),
+ `${FP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(CM_PREF),
+ `${CM_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(STP_PREF),
+ `${STP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(EMAIL_TP_PREF),
+ `${EMAIL_TP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(EMAIL_TP_PBM_PREF),
+ `${EMAIL_TP_PBM_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(NCB_PREF),
+ `${NCB_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(NCBP_PREF),
+ `${NCBP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(LEVEL2_PREF),
+ `${LEVEL2_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(REFERRER_PREF),
+ `${REFERRER_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(REFERRER_TOP_PREF),
+ `${REFERRER_TOP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(OCSP_PREF),
+ `${OCSP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(QUERY_PARAM_STRIP_PREF),
+ `${QUERY_PARAM_STRIP_PREF} pref has the default value`
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(QUERY_PARAM_STRIP_PBM_PREF),
+ `${QUERY_PARAM_STRIP_PBM_PREF} pref has the default value`
+ );
+
+ defaults.setStringPref(
+ STRICT_DEF_PREF,
+ "-tpPrivate,-fp,-cm,-tp,cookieBehavior3,cookieBehaviorPBM2,-stp,-emailTP,-emailTPPrivate,-lvl2,-rp,-ocsp,-qps,-qpsPBM"
+ );
+ is(
+ Services.prefs.getBoolPref(TP_PREF),
+ false,
+ `${TP_PREF} pref has been set to false`
+ );
+ is(
+ Services.prefs.getBoolPref(TP_PBM_PREF),
+ false,
+ `${TP_PBM_PREF} pref has been set to false`
+ );
+ is(
+ Services.prefs.getBoolPref(FP_PREF),
+ false,
+ `${FP_PREF} pref has been set to false`
+ );
+ is(
+ Services.prefs.getBoolPref(CM_PREF),
+ false,
+ `${CM_PREF} pref has been set to false`
+ );
+ is(
+ Services.prefs.getBoolPref(STP_PREF),
+ false,
+ `${STP_PREF} pref has been set to false`
+ );
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PREF),
+ false,
+ `${EMAIL_TP_PREF} pref has been set to false`
+ );
+ is(
+ Services.prefs.getBoolPref(EMAIL_TP_PBM_PREF),
+ false,
+ `${EMAIL_TP_PBM_PREF} pref has been set to false`
+ );
+ is(
+ Services.prefs.getIntPref(NCB_PREF),
+ Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN,
+ `${NCB_PREF} has been set to BEHAVIOR_REJECT_TRACKER`
+ );
+ is(
+ Services.prefs.getIntPref(NCBP_PREF),
+ Ci.nsICookieService.BEHAVIOR_REJECT,
+ `${NCBP_PREF} has been set to BEHAVIOR_REJECT`
+ );
+ is(
+ Services.prefs.getBoolPref(LEVEL2_PREF),
+ false,
+ `${LEVEL2_PREF} pref has been set to false`
+ );
+ is(
+ Services.prefs.getBoolPref(REFERRER_PREF),
+ false,
+ `${REFERRER_PREF} pref has been set to false`
+ );
+ is(
+ Services.prefs.getBoolPref(REFERRER_TOP_PREF),
+ false,
+ `${REFERRER_TOP_PREF} pref has been set to false`
+ );
+ is(
+ Services.prefs.getBoolPref(OCSP_PREF),
+ false,
+ `${OCSP_PREF} pref has been set to false`
+ );
+ is(
+ Services.prefs.getBoolPref(QUERY_PARAM_STRIP_PREF),
+ false,
+ `${QUERY_PARAM_STRIP_PREF} pref has been set to false`
+ );
+ is(
+ Services.prefs.getBoolPref(QUERY_PARAM_STRIP_PBM_PREF),
+ false,
+ `${QUERY_PARAM_STRIP_PBM_PREF} pref has been set to false`
+ );
+
+ // cleanup
+ defaults.setStringPref(STRICT_DEF_PREF, originalStrictPref);
+ Services.prefs.setStringPref(CAT_PREF, "standard");
+});
diff --git a/browser/components/preferences/tests/browser_contentblocking_standard_tcp_section.js b/browser/components/preferences/tests/browser_contentblocking_standard_tcp_section.js
new file mode 100644
index 0000000000..249a42317a
--- /dev/null
+++ b/browser/components/preferences/tests/browser_contentblocking_standard_tcp_section.js
@@ -0,0 +1,148 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the TCP info box in the ETP standard section of about:preferences#privacy.
+ */
+
+ChromeUtils.defineESModuleGetters(this, {
+ Preferences: "resource://gre/modules/Preferences.sys.mjs",
+});
+
+const COOKIE_BEHAVIOR_PREF = "network.cookie.cookieBehavior";
+const CAT_PREF = "browser.contentblocking.category";
+
+const LEARN_MORE_URL =
+ Services.urlFormatter.formatURLPref("app.support.baseURL") +
+ "total-cookie-protection";
+
+const {
+ BEHAVIOR_REJECT_TRACKER,
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+} = Ci.nsICookieService;
+
+async function testTCPSection({ dFPIEnabled }) {
+ info(
+ "Testing TCP preferences section in standard " +
+ JSON.stringify({ dFPIEnabled })
+ );
+
+ // In order to test the "standard" category we need to set the default value
+ // for the cookie behavior pref. A user value would get cleared as soon as we
+ // switch to "standard".
+ Services.prefs
+ .getDefaultBranch("")
+ .setIntPref(
+ COOKIE_BEHAVIOR_PREF,
+ dFPIEnabled
+ ? BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
+ : BEHAVIOR_REJECT_TRACKER
+ );
+
+ // Setting to standard category explicitly, since changing the default cookie
+ // behavior still switches us to custom initially.
+ await SpecialPowers.pushPrefEnv({
+ set: [[CAT_PREF, "standard"]],
+ });
+
+ const uiEnabled =
+ Services.prefs.getIntPref(COOKIE_BEHAVIOR_PREF) ==
+ BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+
+ let standardRadioOption = doc.getElementById("standardRadio");
+ let strictRadioOption = doc.getElementById("strictRadio");
+ let customRadioOption = doc.getElementById("customRadio");
+
+ ok(standardRadioOption.selected, "Standard category is selected");
+
+ let etpStandardTCPBox = doc.getElementById("etpStandardTCPBox");
+ is(
+ BrowserTestUtils.is_visible(etpStandardTCPBox),
+ uiEnabled,
+ `TCP section in standard is ${uiEnabled ? " " : "not "}visible.`
+ );
+
+ if (uiEnabled) {
+ // If visible, test the TCP section elements.
+ let learnMoreLink = etpStandardTCPBox.querySelector("#tcp-learn-more-link");
+ ok(learnMoreLink, "Should have a learn more link");
+ BrowserTestUtils.is_visible(
+ learnMoreLink,
+ "Learn more link should be visible."
+ );
+ ok(
+ learnMoreLink.href && !learnMoreLink.href.startsWith("about:blank"),
+ "Learn more link should be valid."
+ );
+ is(
+ learnMoreLink.href,
+ LEARN_MORE_URL,
+ "Learn more link should have the correct target."
+ );
+
+ let description = etpStandardTCPBox.querySelector(".tail-with-learn-more");
+ ok(description, "Should have a description element.");
+ BrowserTestUtils.is_visible(description, "Description should be visible.");
+
+ let title = etpStandardTCPBox.querySelector(
+ ".content-blocking-warning-title"
+ );
+ ok(title, "Should have a title element.");
+ BrowserTestUtils.is_visible(title, "Title should be visible.");
+ }
+
+ info("Switch to ETP strict.");
+ let categoryPrefChange = waitForAndAssertPrefState(CAT_PREF, "strict");
+ strictRadioOption.click();
+ await categoryPrefChange;
+ ok(
+ !BrowserTestUtils.is_visible(etpStandardTCPBox),
+ "When strict is selected TCP UI is not visible."
+ );
+
+ info("Switch to ETP custom.");
+ categoryPrefChange = waitForAndAssertPrefState(CAT_PREF, "custom");
+ customRadioOption.click();
+ await categoryPrefChange;
+ ok(
+ !BrowserTestUtils.is_visible(etpStandardTCPBox),
+ "When custom is selected TCP UI is not visible."
+ );
+
+ info("Switch back to standard and ensure we show the TCP UI again.");
+ categoryPrefChange = waitForAndAssertPrefState(CAT_PREF, "standard");
+ standardRadioOption.click();
+ await categoryPrefChange;
+ is(
+ BrowserTestUtils.is_visible(etpStandardTCPBox),
+ uiEnabled,
+ `TCP section in standard is ${uiEnabled ? " " : "not "}visible.`
+ );
+
+ gBrowser.removeCurrentTab();
+ await SpecialPowers.popPrefEnv();
+ Services.prefs.setStringPref(CAT_PREF, "standard");
+}
+
+add_setup(async function () {
+ // Register cleanup function to restore default cookie behavior.
+ const defaultPrefs = Services.prefs.getDefaultBranch("");
+ const previousDefaultCB = defaultPrefs.getIntPref(COOKIE_BEHAVIOR_PREF);
+
+ registerCleanupFunction(function () {
+ defaultPrefs.setIntPref(COOKIE_BEHAVIOR_PREF, previousDefaultCB);
+ });
+});
+
+// Clients which don't have dFPI enabled should not see the
+// preferences section.
+add_task(async function test_dfpi_disabled() {
+ await testTCPSection({ dFPIEnabled: false });
+});
+
+add_task(async function test_dfpi_enabled() {
+ await testTCPSection({ dFPIEnabled: true });
+});
diff --git a/browser/components/preferences/tests/browser_cookie_exceptions_addRemove.js b/browser/components/preferences/tests/browser_cookie_exceptions_addRemove.js
new file mode 100644
index 0000000000..1b70170ddc
--- /dev/null
+++ b/browser/components/preferences/tests/browser_cookie_exceptions_addRemove.js
@@ -0,0 +1,299 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+if (AppConstants.TSAN || AppConstants.DEBUG) {
+ requestLongerTimeout(2);
+}
+
+const PERMISSIONS_URL =
+ "chrome://browser/content/preferences/dialogs/permissions.xhtml";
+
+async function openCookiesDialog(doc) {
+ let cookieExceptionsButton = doc.getElementById("cookieExceptions");
+ ok(cookieExceptionsButton, "cookieExceptionsButton found");
+ let dialogPromise = promiseLoadSubDialog(PERMISSIONS_URL);
+ cookieExceptionsButton.click();
+ let dialog = await dialogPromise;
+ return dialog;
+}
+
+function checkCookiesDialog(dialog) {
+ ok(dialog, "dialog loaded");
+ let buttonIds = ["removePermission", "removeAllPermissions"];
+
+ for (let buttonId of buttonIds) {
+ let button = dialog.document.getElementById(buttonId);
+ ok(button, `${buttonId} found`);
+ }
+
+ let dialogEl = dialog.document.querySelector("dialog");
+ let acceptBtn = dialogEl.getButton("accept");
+ let cancelBtn = dialogEl.getButton("cancel");
+
+ ok(!acceptBtn.hidden, "acceptButton found");
+ ok(!cancelBtn.hidden, "cancelButton found");
+}
+
+function addNewPermission(websiteAddress, dialog) {
+ let url = dialog.document.getElementById("url");
+ let buttonDialog = dialog.document.getElementById("btnBlock");
+ let permissionsBox = dialog.document.getElementById("permissionsBox");
+ let currentPermissions = permissionsBox.itemCount;
+
+ url.value = websiteAddress;
+ url.dispatchEvent(new Event("input", { bubbles: true }));
+ is(
+ buttonDialog.hasAttribute("disabled"),
+ false,
+ "When the user add an url the button should be clickable"
+ );
+ buttonDialog.click();
+
+ is(
+ permissionsBox.itemCount,
+ currentPermissions + 1,
+ "Website added in url should be in the list"
+ );
+}
+
+async function cleanList(dialog) {
+ let removeAllButton = dialog.document.getElementById("removeAllPermissions");
+ if (!removeAllButton.hasAttribute("disabled")) {
+ removeAllButton.click();
+ }
+}
+
+function addData(websites, dialog) {
+ for (let website of websites) {
+ addNewPermission(website, dialog);
+ }
+}
+
+function deletePermission(permission, dialog) {
+ let permissionsBox = dialog.document.getElementById("permissionsBox");
+ let elements = permissionsBox.getElementsByAttribute("origin", permission);
+ is(elements.length, 1, "It should find only one entry");
+ permissionsBox.selectItem(elements[0]);
+ let removePermissionButton =
+ dialog.document.getElementById("removePermission");
+ is(
+ removePermissionButton.hasAttribute("disabled"),
+ false,
+ "The button should be clickable to remove selected item"
+ );
+ removePermissionButton.click();
+}
+
+function save(dialog) {
+ let saveButton = dialog.document.querySelector("dialog").getButton("accept");
+ saveButton.click();
+}
+
+function cancel(dialog) {
+ let cancelButton = dialog.document
+ .querySelector("dialog")
+ .getButton("cancel");
+ ok(!cancelButton.hidden, "cancelButton found");
+ cancelButton.click();
+}
+
+async function checkExpected(expected, doc) {
+ let dialog = await openCookiesDialog(doc);
+ let permissionsBox = dialog.document.getElementById("permissionsBox");
+
+ is(
+ permissionsBox.itemCount,
+ expected.length,
+ `There should be ${expected.length} elements in the list`
+ );
+
+ for (let website of expected) {
+ let elements = permissionsBox.getElementsByAttribute("origin", website);
+ is(elements.length, 1, "It should find only one entry");
+ }
+ return dialog;
+}
+
+async function runTest(test, websites, doc) {
+ let dialog = await openCookiesDialog(doc);
+ checkCookiesDialog(dialog);
+
+ if (test.needPreviousData) {
+ addData(websites, dialog);
+ save(dialog);
+ dialog = await openCookiesDialog(doc);
+ }
+
+ for (let step of test.steps) {
+ switch (step) {
+ case "addNewPermission":
+ addNewPermission(test.newData, dialog);
+ break;
+ case "deletePermission":
+ deletePermission(test.newData, dialog);
+ break;
+ case "deleteAllPermission":
+ await cleanList(dialog);
+ break;
+ case "save":
+ save(dialog);
+ break;
+ case "cancel":
+ cancel(dialog);
+ break;
+ case "openPane":
+ dialog = await openCookiesDialog(doc);
+ break;
+ default:
+ // code block
+ }
+ }
+ dialog = await checkExpected(test.expected, doc);
+ await cleanList(dialog);
+ save(dialog);
+}
+
+add_task(async function checkPermissions() {
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = win.document;
+ let websites = ["http://test1.com", "http://test2.com"];
+
+ let tests = [
+ {
+ needPreviousData: false,
+ newData: "https://mytest.com",
+ steps: ["addNewPermission", "save"],
+ expected: ["https://mytest.com"], // when open the pane again it should find this in the list
+ },
+ {
+ needPreviousData: false,
+ newData: "https://mytest.com",
+ steps: ["addNewPermission", "cancel"],
+ expected: [],
+ },
+ {
+ needPreviousData: false,
+ newData: "https://mytest.com",
+ steps: ["addNewPermission", "deletePermission", "save"],
+ expected: [],
+ },
+ {
+ needPreviousData: false,
+ newData: "https://mytest.com",
+ steps: ["addNewPermission", "deletePermission", "cancel"],
+ expected: [],
+ },
+ {
+ needPreviousData: false,
+ newData: "https://mytest.com",
+ steps: [
+ "addNewPermission",
+ "save",
+ "openPane",
+ "deletePermission",
+ "save",
+ ],
+ expected: [],
+ },
+ {
+ needPreviousData: false,
+ newData: "https://mytest.com",
+ steps: [
+ "addNewPermission",
+ "save",
+ "openPane",
+ "deletePermission",
+ "cancel",
+ ],
+ expected: ["https://mytest.com"],
+ },
+ {
+ needPreviousData: false,
+ newData: "https://mytest.com",
+ steps: ["addNewPermission", "deleteAllPermission", "save"],
+ expected: [],
+ },
+ {
+ needPreviousData: false,
+ newData: "https://mytest.com",
+ steps: ["addNewPermission", "deleteAllPermission", "cancel"],
+ expected: [],
+ },
+ {
+ needPreviousData: false,
+ newData: "https://mytest.com",
+ steps: [
+ "addNewPermission",
+ "save",
+ "openPane",
+ "deleteAllPermission",
+ "save",
+ ],
+ expected: [],
+ },
+ {
+ needPreviousData: false,
+ newData: "https://mytest.com",
+ steps: [
+ "addNewPermission",
+ "save",
+ "openPane",
+ "deleteAllPermission",
+ "cancel",
+ ],
+ expected: ["https://mytest.com"],
+ },
+ {
+ needPreviousData: true,
+ newData: "https://mytest.com",
+ steps: ["deleteAllPermission", "save"],
+ expected: [],
+ },
+ {
+ needPreviousData: true,
+ newData: "https://mytest.com",
+ steps: ["deleteAllPermission", "cancel"],
+ expected: websites,
+ },
+ {
+ needPreviousData: true,
+ newData: "https://mytest.com",
+ steps: ["addNewPermission", "save"],
+ expected: (function () {
+ let result = websites.slice();
+ result.push("https://mytest.com");
+ return result;
+ })(),
+ },
+ {
+ needPreviousData: true,
+ newData: "https://mytest.com",
+ steps: ["addNewPermission", "cancel"],
+ expected: websites,
+ },
+ {
+ needPreviousData: false,
+ newData: "https://mytest.com",
+ steps: [
+ "addNewPermission",
+ "save",
+ "openPane",
+ "deleteAllPermission",
+ "addNewPermission",
+ "save",
+ ],
+ expected: ["https://mytest.com"],
+ },
+ ];
+
+ for (let test of tests) {
+ await runTest(test, websites, doc);
+ }
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_cookies_exceptions.js b/browser/components/preferences/tests/browser_cookies_exceptions.js
new file mode 100644
index 0000000000..d2d538a48a
--- /dev/null
+++ b/browser/components/preferences/tests/browser_cookies_exceptions.js
@@ -0,0 +1,568 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(3);
+
+add_task(async function testAllow() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ assertListContents(params, []);
+
+ params.url.value = "test.com";
+ params.btnAllow.doCommand();
+
+ assertListContents(params, [
+ ["http://test.com", params.allowL10nId],
+ ["https://test.com", params.allowL10nId],
+ ]);
+
+ apply();
+ await observeAllPromise;
+ },
+ params => {
+ return [
+ {
+ type: "cookie",
+ origin: "http://test.com",
+ data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ {
+ type: "cookie",
+ origin: "https://test.com",
+ data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ ];
+ }
+ );
+});
+
+add_task(async function testBlock() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ params.url.value = "test.com";
+ params.btnBlock.doCommand();
+
+ assertListContents(params, [
+ ["http://test.com", params.denyL10nId],
+ ["https://test.com", params.denyL10nId],
+ ]);
+
+ apply();
+ await observeAllPromise;
+ },
+ params => {
+ return [
+ {
+ type: "cookie",
+ origin: "http://test.com",
+ data: "changed",
+ capability: Ci.nsIPermissionManager.DENY_ACTION,
+ },
+ {
+ type: "cookie",
+ origin: "https://test.com",
+ data: "changed",
+ capability: Ci.nsIPermissionManager.DENY_ACTION,
+ },
+ ];
+ }
+ );
+});
+
+add_task(async function testAllowAgain() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ params.url.value = "test.com";
+ params.btnAllow.doCommand();
+
+ assertListContents(params, [
+ ["http://test.com", params.allowL10nId],
+ ["https://test.com", params.allowL10nId],
+ ]);
+
+ apply();
+ await observeAllPromise;
+ },
+ params => {
+ return [
+ {
+ type: "cookie",
+ origin: "http://test.com",
+ data: "changed",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ {
+ type: "cookie",
+ origin: "https://test.com",
+ data: "changed",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ ];
+ }
+ );
+});
+
+add_task(async function testRemove() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ while (params.richlistbox.itemCount) {
+ params.richlistbox.selectedIndex = 0;
+ params.btnRemove.doCommand();
+ }
+ assertListContents(params, []);
+
+ apply();
+ await observeAllPromise;
+ },
+ params => {
+ let richlistItems = params.richlistbox.getElementsByAttribute(
+ "origin",
+ "*"
+ );
+ let observances = [];
+ for (let item of richlistItems) {
+ observances.push({
+ type: "cookie",
+ origin: item.getAttribute("origin"),
+ data: "deleted",
+ });
+ }
+ return observances;
+ }
+ );
+});
+
+add_task(async function testAdd() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ let uri = Services.io.newURI("http://test.com");
+ PermissionTestUtils.add(
+ uri,
+ "popup",
+ Ci.nsIPermissionManager.DENY_ACTION
+ );
+
+ info("Adding unrelated permission should not change display.");
+ assertListContents(params, []);
+
+ apply();
+ await observeAllPromise;
+
+ PermissionTestUtils.remove(uri, "popup");
+ },
+ params => {
+ return [
+ {
+ type: "popup",
+ origin: "http://test.com",
+ data: "added",
+ capability: Ci.nsIPermissionManager.DENY_ACTION,
+ },
+ ];
+ }
+ );
+});
+
+add_task(async function testAllowHTTPSWithPort() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ params.url.value = "https://test.com:12345";
+ params.btnAllow.doCommand();
+
+ assertListContents(params, [
+ ["https://test.com:12345", params.allowL10nId],
+ ]);
+
+ apply();
+ await observeAllPromise;
+ },
+ params => {
+ return [
+ {
+ type: "cookie",
+ origin: "https://test.com:12345",
+ data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ ];
+ }
+ );
+});
+
+add_task(async function testBlockHTTPSWithPort() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ params.url.value = "https://test.com:12345";
+ params.btnBlock.doCommand();
+
+ assertListContents(params, [
+ ["https://test.com:12345", params.denyL10nId],
+ ]);
+
+ apply();
+ await observeAllPromise;
+ },
+ params => {
+ return [
+ {
+ type: "cookie",
+ origin: "https://test.com:12345",
+ data: "changed",
+ capability: Ci.nsIPermissionManager.DENY_ACTION,
+ },
+ ];
+ }
+ );
+});
+
+add_task(async function testAllowAgainHTTPSWithPort() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ params.url.value = "https://test.com:12345";
+ params.btnAllow.doCommand();
+
+ assertListContents(params, [
+ ["https://test.com:12345", params.allowL10nId],
+ ]);
+
+ apply();
+ await observeAllPromise;
+ },
+ params => {
+ return [
+ {
+ type: "cookie",
+ origin: "https://test.com:12345",
+ data: "changed",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ ];
+ }
+ );
+});
+
+add_task(async function testRemoveHTTPSWithPort() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ while (params.richlistbox.itemCount) {
+ params.richlistbox.selectedIndex = 0;
+ params.btnRemove.doCommand();
+ }
+
+ assertListContents(params, []);
+
+ apply();
+ await observeAllPromise;
+ },
+ params => {
+ let richlistItems = params.richlistbox.getElementsByAttribute(
+ "origin",
+ "*"
+ );
+ let observances = [];
+ for (let item of richlistItems) {
+ observances.push({
+ type: "cookie",
+ origin: item.getAttribute("origin"),
+ data: "deleted",
+ });
+ }
+ return observances;
+ }
+ );
+});
+
+add_task(async function testAllowPort() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ params.url.value = "localhost:12345";
+ params.btnAllow.doCommand();
+
+ assertListContents(params, [
+ ["http://localhost:12345", params.allowL10nId],
+ ["https://localhost:12345", params.allowL10nId],
+ ]);
+
+ apply();
+ await observeAllPromise;
+ },
+ params => {
+ return [
+ {
+ type: "cookie",
+ origin: "http://localhost:12345",
+ data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ {
+ type: "cookie",
+ origin: "https://localhost:12345",
+ data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ ];
+ }
+ );
+});
+
+add_task(async function testBlockPort() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ params.url.value = "localhost:12345";
+ params.btnBlock.doCommand();
+
+ assertListContents(params, [
+ ["http://localhost:12345", params.denyL10nId],
+ ["https://localhost:12345", params.denyL10nId],
+ ]);
+
+ apply();
+ await observeAllPromise;
+ },
+ params => {
+ return [
+ {
+ type: "cookie",
+ origin: "http://localhost:12345",
+ data: "changed",
+ capability: Ci.nsIPermissionManager.DENY_ACTION,
+ },
+ {
+ type: "cookie",
+ origin: "https://localhost:12345",
+ data: "changed",
+ capability: Ci.nsIPermissionManager.DENY_ACTION,
+ },
+ ];
+ }
+ );
+});
+
+add_task(async function testAllowAgainPort() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ params.url.value = "localhost:12345";
+ params.btnAllow.doCommand();
+
+ assertListContents(params, [
+ ["http://localhost:12345", params.allowL10nId],
+ ["https://localhost:12345", params.allowL10nId],
+ ]);
+
+ apply();
+ await observeAllPromise;
+ },
+ params => {
+ return [
+ {
+ type: "cookie",
+ origin: "http://localhost:12345",
+ data: "changed",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ {
+ type: "cookie",
+ origin: "https://localhost:12345",
+ data: "changed",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ ];
+ }
+ );
+});
+
+add_task(async function testRemovePort() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ while (params.richlistbox.itemCount) {
+ params.richlistbox.selectedIndex = 0;
+ params.btnRemove.doCommand();
+ }
+
+ assertListContents(params, []);
+
+ apply();
+ await observeAllPromise;
+ },
+ params => {
+ let richlistItems = params.richlistbox.getElementsByAttribute(
+ "origin",
+ "*"
+ );
+ let observances = [];
+ for (let item of richlistItems) {
+ observances.push({
+ type: "cookie",
+ origin: item.getAttribute("origin"),
+ data: "deleted",
+ });
+ }
+ return observances;
+ }
+ );
+});
+
+add_task(async function testSort() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ // Sort by site name.
+ EventUtils.synthesizeMouseAtCenter(
+ params.doc.getElementById("siteCol"),
+ {},
+ params.doc.defaultView
+ );
+
+ for (let URL of ["http://a", "http://z", "http://b"]) {
+ let URI = Services.io.newURI(URL);
+ PermissionTestUtils.add(
+ URI,
+ "cookie",
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ );
+ }
+
+ assertListContents(params, [
+ ["http://a", params.allowL10nId],
+ ["http://b", params.allowL10nId],
+ ["http://z", params.allowL10nId],
+ ]);
+
+ // Sort by site name in descending order.
+ EventUtils.synthesizeMouseAtCenter(
+ params.doc.getElementById("siteCol"),
+ {},
+ params.doc.defaultView
+ );
+
+ assertListContents(params, [
+ ["http://z", params.allowL10nId],
+ ["http://b", params.allowL10nId],
+ ["http://a", params.allowL10nId],
+ ]);
+
+ apply();
+ await observeAllPromise;
+
+ for (let URL of ["http://a", "http://z", "http://b"]) {
+ let uri = Services.io.newURI(URL);
+ PermissionTestUtils.remove(uri, "cookie");
+ }
+ },
+ params => {
+ return [
+ {
+ type: "cookie",
+ origin: "http://a",
+ data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ {
+ type: "cookie",
+ origin: "http://z",
+ data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ {
+ type: "cookie",
+ origin: "http://b",
+ data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ },
+ ];
+ }
+ );
+});
+
+add_task(async function testPrivateBrowsingSessionPermissionsAreHidden() {
+ await runTest(
+ async (params, observeAllPromise, apply) => {
+ assertListContents(params, []);
+
+ let uri = Services.io.newURI("http://test.com");
+ let privateBrowsingPrincipal =
+ Services.scriptSecurityManager.createContentPrincipal(uri, {
+ privateBrowsingId: 1,
+ });
+
+ // Add a session permission for private browsing.
+ PermissionTestUtils.add(
+ privateBrowsingPrincipal,
+ "cookie",
+ Services.perms.ALLOW_ACTION,
+ Services.perms.EXPIRE_SESSION
+ );
+
+ assertListContents(params, []);
+
+ PermissionTestUtils.remove(uri, "cookie");
+ },
+ params => {
+ return [];
+ }
+ );
+});
+
+function assertListContents(params, expected) {
+ Assert.equal(params.richlistbox.itemCount, expected.length);
+
+ for (let i = 0; i < expected.length; i++) {
+ let website = expected[i][0];
+ let elements = params.richlistbox.getElementsByAttribute("origin", website);
+ Assert.equal(elements.length, 1); // "It should find only one coincidence"
+ Assert.equal(
+ elements[0]
+ .querySelector(".website-capability-value")
+ .getAttribute("data-l10n-id"),
+ expected[i][1]
+ );
+ }
+}
+
+async function runTest(test, getObservances) {
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("privacy.history.custom");
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let historyMode = doc.getElementById("historyMode");
+ historyMode.value = "custom";
+ historyMode.doCommand();
+
+ let promiseSubDialogLoaded = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/permissions.xhtml"
+ );
+ doc.getElementById("cookieExceptions").doCommand();
+
+ let win = await promiseSubDialogLoaded;
+
+ doc = win.document;
+ let params = {
+ doc,
+ richlistbox: doc.getElementById("permissionsBox"),
+ url: doc.getElementById("url"),
+ btnAllow: doc.getElementById("btnAllow"),
+ btnBlock: doc.getElementById("btnBlock"),
+ btnRemove: doc.getElementById("removePermission"),
+ allowL10nId: win.gPermissionManager._getCapabilityL10nId(
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ ),
+ denyL10nId: win.gPermissionManager._getCapabilityL10nId(
+ Ci.nsIPermissionManager.DENY_ACTION
+ ),
+ allow: Ci.nsIPermissionManager.ALLOW_ACTION,
+ deny: Ci.nsIPermissionManager.DENY_ACTION,
+ };
+ let btnApplyChanges = doc.querySelector("dialog").getButton("accept");
+ let observances = getObservances(params);
+ let observeAllPromise = createObserveAllPromise(observances);
+
+ await test(params, observeAllPromise, () => btnApplyChanges.doCommand());
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}
diff --git a/browser/components/preferences/tests/browser_defaultbrowser_alwayscheck.js b/browser/components/preferences/tests/browser_defaultbrowser_alwayscheck.js
new file mode 100644
index 0000000000..dc88e36cef
--- /dev/null
+++ b/browser/components/preferences/tests/browser_defaultbrowser_alwayscheck.js
@@ -0,0 +1,185 @@
+"use strict";
+
+const CHECK_DEFAULT_INITIAL = Services.prefs.getBoolPref(
+ "browser.shell.checkDefaultBrowser"
+);
+
+add_task(async function clicking_make_default_checks_alwaysCheck_checkbox() {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
+
+ await test_with_mock_shellservice({ isDefault: false }, async function () {
+ let checkDefaultBrowserState = isDefault => {
+ let isDefaultPane = content.document.getElementById("isDefaultPane");
+ let isNotDefaultPane =
+ content.document.getElementById("isNotDefaultPane");
+ Assert.equal(
+ ContentTaskUtils.is_hidden(isDefaultPane),
+ !isDefault,
+ "The 'browser is default' pane should be hidden when browser is not default"
+ );
+ Assert.equal(
+ ContentTaskUtils.is_hidden(isNotDefaultPane),
+ isDefault,
+ "The 'make default' pane should be hidden when browser is default"
+ );
+ };
+
+ checkDefaultBrowserState(false);
+
+ let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
+ Assert.ok(!alwaysCheck.checked, "Always Check is unchecked by default");
+ Assert.ok(
+ !Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "alwaysCheck pref should be false by default in test runs"
+ );
+
+ let setDefaultButton = content.document.getElementById("setDefaultButton");
+ setDefaultButton.click();
+ content.window.gMainPane.updateSetDefaultBrowser();
+
+ await ContentTaskUtils.waitForCondition(
+ () => alwaysCheck.checked,
+ "'Always Check' checkbox should get checked after clicking the 'Set Default' button"
+ );
+
+ Assert.ok(
+ alwaysCheck.checked,
+ "Clicking 'Make Default' checks the 'Always Check' checkbox"
+ );
+ Assert.ok(
+ Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "Checking the checkbox should set the pref to true"
+ );
+ Assert.ok(
+ alwaysCheck.disabled,
+ "'Always Check' checkbox is locked with default browser and alwaysCheck=true"
+ );
+ checkDefaultBrowserState(true);
+ Assert.ok(
+ Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "checkDefaultBrowser pref is now enabled"
+ );
+ });
+
+ gBrowser.removeCurrentTab();
+ Services.prefs.clearUserPref("browser.shell.checkDefaultBrowser");
+});
+
+add_task(async function clicking_make_default_checks_alwaysCheck_checkbox() {
+ Services.prefs.lockPref("browser.shell.checkDefaultBrowser");
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
+
+ await test_with_mock_shellservice({ isDefault: false }, async function () {
+ let isDefaultPane = content.document.getElementById("isDefaultPane");
+ let isNotDefaultPane = content.document.getElementById("isNotDefaultPane");
+ Assert.ok(
+ ContentTaskUtils.is_hidden(isDefaultPane),
+ "The 'browser is default' pane should be hidden when not default"
+ );
+ Assert.ok(
+ ContentTaskUtils.is_visible(isNotDefaultPane),
+ "The 'make default' pane should be visible when not default"
+ );
+
+ let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
+ Assert.ok(alwaysCheck.disabled, "Always Check is disabled when locked");
+ Assert.ok(
+ alwaysCheck.checked,
+ "Always Check is checked because defaultPref is true and pref is locked"
+ );
+ Assert.ok(
+ Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "alwaysCheck pref should ship with 'true' by default"
+ );
+
+ let setDefaultButton = content.document.getElementById("setDefaultButton");
+ setDefaultButton.click();
+ content.window.gMainPane.updateSetDefaultBrowser();
+
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.is_visible(isDefaultPane),
+ "Browser is now default"
+ );
+
+ Assert.ok(
+ alwaysCheck.checked,
+ "'Always Check' is still checked because it's locked"
+ );
+ Assert.ok(
+ alwaysCheck.disabled,
+ "'Always Check is disabled because it's locked"
+ );
+ Assert.ok(
+ Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "The pref is locked and so doesn't get changed"
+ );
+ });
+
+ Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function make_default_disabled_until_prefs_are_loaded() {
+ // Testcase with Firefox not set as the default browser
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
+ await test_with_mock_shellservice({ isDefault: false }, async function () {
+ let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
+ Assert.ok(
+ !alwaysCheck.disabled,
+ "'Always Check' is enabled after default browser updated"
+ );
+ });
+ gBrowser.removeCurrentTab();
+
+ // Testcase with Firefox set as the default browser
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
+ await test_with_mock_shellservice({ isDefault: true }, async function () {
+ let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
+ Assert.ok(
+ alwaysCheck.disabled,
+ "'Always Check' is still disabled after default browser updated"
+ );
+ });
+ gBrowser.removeCurrentTab();
+});
+
+registerCleanupFunction(function () {
+ Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
+ Services.prefs.setBoolPref(
+ "browser.shell.checkDefaultBrowser",
+ CHECK_DEFAULT_INITIAL
+ );
+});
+
+async function test_with_mock_shellservice(options, testFn) {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [options],
+ async function (contentOptions) {
+ let doc = content.document;
+ let win = doc.defaultView;
+ win.oldShellService = win.getShellService();
+ let mockShellService = {
+ _isDefault: false,
+ isDefaultBrowser() {
+ return this._isDefault;
+ },
+ setDefaultBrowser() {
+ this._isDefault = true;
+ },
+ };
+ win.getShellService = function () {
+ return mockShellService;
+ };
+ mockShellService._isDefault = contentOptions.isDefault;
+ win.gMainPane.updateSetDefaultBrowser();
+ }
+ );
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], testFn);
+
+ Services.prefs.setBoolPref(
+ "browser.shell.checkDefaultBrowser",
+ CHECK_DEFAULT_INITIAL
+ );
+}
diff --git a/browser/components/preferences/tests/browser_engines.js b/browser/components/preferences/tests/browser_engines.js
new file mode 100644
index 0000000000..aa681e7039
--- /dev/null
+++ b/browser/components/preferences/tests/browser_engines.js
@@ -0,0 +1,141 @@
+const { SearchTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SearchTestUtils.sys.mjs"
+);
+
+SearchTestUtils.init(this);
+
+function getCellText(tree, i, cellName) {
+ return tree.view.getCellText(i, tree.columns.getNamedColumn(cellName));
+}
+
+add_setup(async function () {
+ await SearchTestUtils.installSearchExtension({
+ keyword: ["testing", "customkeyword"],
+ search_url: "https://example.com/engine1",
+ search_url_get_params: "search={searchTerms}",
+ });
+});
+
+add_task(async function test_engine_list() {
+ let prefs = await openPreferencesViaOpenPreferencesAPI("search", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneSearch", "Search pane is selected by default");
+ let doc = gBrowser.contentDocument;
+
+ let tree = doc.querySelector("#engineList");
+ ok(
+ !tree.hidden,
+ "The search engine list should be visible when Search is requested"
+ );
+
+ // Check for default search engines to be displayed in the engineList
+ let defaultEngines = await Services.search.getAppProvidedEngines();
+ for (let i = 0; i < defaultEngines.length; i++) {
+ let engine = defaultEngines[i];
+ is(
+ getCellText(tree, i, "engineName"),
+ engine.name,
+ "Default search engine " + engine.name + " displayed correctly"
+ );
+ }
+
+ let customEngineIndex = defaultEngines.length;
+ is(
+ getCellText(tree, customEngineIndex, "engineKeyword"),
+ "testing, customkeyword",
+ "Show internal aliases"
+ );
+
+ // Scroll the treeview into view since mouse operations
+ // off screen can act confusingly.
+ tree.scrollIntoView();
+ let rect = tree.getCoordsForCellItem(
+ customEngineIndex,
+ tree.columns.getNamedColumn("engineKeyword"),
+ "text"
+ );
+ let x = rect.x + rect.width / 2;
+ let y = rect.y + rect.height / 2;
+ let win = tree.ownerGlobal;
+
+ let promise = BrowserTestUtils.waitForEvent(tree, "dblclick");
+ EventUtils.synthesizeMouse(tree.body, x, y, { clickCount: 1 }, win);
+ EventUtils.synthesizeMouse(tree.body, x, y, { clickCount: 2 }, win);
+ await promise;
+
+ EventUtils.sendString("newkeyword");
+ EventUtils.sendKey("RETURN");
+
+ await TestUtils.waitForCondition(() => {
+ return (
+ getCellText(tree, customEngineIndex, "engineKeyword") ===
+ "newkeyword, testing, customkeyword"
+ );
+ });
+
+ // Avoid duplicated keywords
+ tree.view.setCellText(
+ 0,
+ tree.columns.getNamedColumn("engineKeyword"),
+ "keyword"
+ );
+ tree.view.setCellText(
+ 1,
+ tree.columns.getNamedColumn("engineKeyword"),
+ "keyword"
+ );
+ isnot(
+ getCellText(tree, 1, "engineKeyword"),
+ "keyword",
+ "Do not allow duplicated keywords"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_remove_button_disabled_state() {
+ let prefs = await openPreferencesViaOpenPreferencesAPI("search", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneSearch", "Search pane is selected by default");
+ let doc = gBrowser.contentDocument;
+
+ let tree = doc.querySelector("#engineList");
+ ok(
+ !tree.hidden,
+ "The search engine list should be visible when Search is requested"
+ );
+
+ let defaultEngines = await Services.search.getAppProvidedEngines();
+ for (let i = 0; i < defaultEngines.length; i++) {
+ let engine = defaultEngines[i];
+
+ let isDefaultSearchEngine =
+ engine.name == Services.search.defaultEngine.name ||
+ engine.name == Services.search.defaultPrivateEngine.name;
+
+ tree.scrollIntoView();
+ let rect = tree.getCoordsForCellItem(
+ i,
+ tree.columns.getNamedColumn("engineName"),
+ "text"
+ );
+ let x = rect.x + rect.width / 2;
+ let y = rect.y + rect.height / 2;
+ let win = tree.ownerGlobal;
+
+ let promise = BrowserTestUtils.waitForEvent(tree, "click");
+ EventUtils.synthesizeMouse(tree.body, x, y, { clickCount: 1 }, win);
+ await promise;
+
+ let removeButton = doc.querySelector("#removeEngineButton");
+ is(
+ removeButton.disabled,
+ isDefaultSearchEngine,
+ "Remove button is in correct disable state"
+ );
+ }
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_etp_exceptions_dialog.js b/browser/components/preferences/tests/browser_etp_exceptions_dialog.js
new file mode 100644
index 0000000000..349223995c
--- /dev/null
+++ b/browser/components/preferences/tests/browser_etp_exceptions_dialog.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PERMISSIONS_URL =
+ "chrome://browser/content/preferences/dialogs/permissions.xhtml";
+
+const TRACKING_URL = "https://example.com";
+
+async function openETPExceptionsDialog(doc) {
+ let exceptionsButton = doc.getElementById("trackingProtectionExceptions");
+ ok(exceptionsButton, "trackingProtectionExceptions button found");
+ let dialogPromise = promiseLoadSubDialog(PERMISSIONS_URL);
+ exceptionsButton.click();
+ let dialog = await dialogPromise;
+ return dialog;
+}
+
+async function addETPPermission(doc) {
+ let dialog = await openETPExceptionsDialog(doc);
+ let url = dialog.document.getElementById("url");
+ let buttonDisableETP = dialog.document.getElementById("btnDisableETP");
+ let permissionsBox = dialog.document.getElementById("permissionsBox");
+ let currentPermissions = permissionsBox.itemCount;
+
+ url.value = TRACKING_URL;
+ url.dispatchEvent(new Event("input", { bubbles: true }));
+ is(
+ buttonDisableETP.hasAttribute("disabled"),
+ false,
+ "Disable ETP button is selectable after url is entered"
+ );
+ buttonDisableETP.click();
+
+ // Website is listed
+ is(
+ permissionsBox.itemCount,
+ currentPermissions + 1,
+ "Website added in url should be in the list"
+ );
+ let saveButton = dialog.document.querySelector("dialog").getButton("accept");
+ saveButton.click();
+ BrowserTestUtils.waitForEvent(dialog, "unload");
+}
+
+async function removeETPPermission(doc) {
+ let dialog = await openETPExceptionsDialog(doc);
+ let permissionsBox = dialog.document.getElementById("permissionsBox");
+ let elements = permissionsBox.getElementsByAttribute("origin", TRACKING_URL);
+ // Website is listed
+ ok(permissionsBox.itemCount, "List is not empty");
+ permissionsBox.selectItem(elements[0]);
+ let removePermissionButton =
+ dialog.document.getElementById("removePermission");
+ is(
+ removePermissionButton.hasAttribute("disabled"),
+ false,
+ "The button should be clickable to remove selected item"
+ );
+ removePermissionButton.click();
+
+ let saveButton = dialog.document.querySelector("dialog").getButton("accept");
+ saveButton.click();
+ BrowserTestUtils.waitForEvent(dialog, "unload");
+}
+
+async function checkShieldIcon(shieldIcon) {
+ // Open the website and check that the tracking protection icon is enabled/disabled
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TRACKING_URL);
+ let icon = document.getElementById("tracking-protection-icon");
+ is(
+ gBrowser.ownerGlobal
+ .getComputedStyle(icon)
+ .getPropertyValue("list-style-image"),
+ shieldIcon,
+ `The tracking protection icon shows the icon ${shieldIcon}`
+ );
+ BrowserTestUtils.removeTab(tab);
+}
+
+// test adds and removes an ETP permission via the about:preferences#privacy and checks if the ProtectionsUI shield icon resembles the state
+add_task(async function ETPPermissionSyncedFromPrivacyPane() {
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = win.document;
+ await addETPPermission(doc);
+ await checkShieldIcon(
+ `url("chrome://browser/skin/tracking-protection-disabled.svg")`
+ );
+ await removeETPPermission(doc);
+ await checkShieldIcon(`url("chrome://browser/skin/tracking-protection.svg")`);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_experimental_features.js b/browser/components/preferences/tests/browser_experimental_features.js
new file mode 100644
index 0000000000..cecbb60893
--- /dev/null
+++ b/browser/components/preferences/tests/browser_experimental_features.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function testPrefRequired() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.experimental", false]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+
+ let experimentalCategory = doc.getElementById("category-experimental");
+ ok(experimentalCategory, "The category exists");
+ ok(experimentalCategory.hidden, "The category is hidden");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function testCanOpenWithPref() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.experimental", true]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+
+ let experimentalCategory = doc.getElementById("category-experimental");
+ ok(experimentalCategory, "The category exists");
+ ok(!experimentalCategory.hidden, "The category is not hidden");
+
+ let categoryHeader = await TestUtils.waitForCondition(
+ () => doc.getElementById("firefoxExperimentalCategory"),
+ "Waiting for experimental features category to get initialized"
+ );
+ ok(
+ categoryHeader.hidden,
+ "The category header should be hidden when Home is selected"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(experimentalCategory, {}, doc.ownerGlobal);
+ await TestUtils.waitForCondition(
+ () => !categoryHeader.hidden,
+ "Waiting until category is visible"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function testSearchFindsExperiments() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.experimental", true]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+
+ let experimentalCategory = doc.getElementById("category-experimental");
+ ok(experimentalCategory, "The category exists");
+ ok(!experimentalCategory.hidden, "The category is not hidden");
+
+ await TestUtils.waitForCondition(
+ () => doc.getElementById("firefoxExperimentalCategory"),
+ "Waiting for experimental features category to get initialized"
+ );
+ await evaluateSearchResults(
+ "advanced configuration",
+ ["pane-experimental-featureGates"],
+ /* include experiments */ true
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_experimental_features_filter.js b/browser/components/preferences/tests/browser_experimental_features_filter.js
new file mode 100644
index 0000000000..6bd66db555
--- /dev/null
+++ b/browser/components/preferences/tests/browser_experimental_features_filter.js
@@ -0,0 +1,183 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This test verifies that searching filters the features to just that subset that
+// contains the search terms.
+add_task(async function testFilterFeatures() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.experimental", true]],
+ });
+
+ // Add a number of test features.
+ const server = new DefinitionServer();
+ let definitions = [
+ {
+ id: "test-featureA",
+ preference: "test.featureA",
+ title: "Experimental Feature 1",
+ description: "This is a fun experimental feature you can enable",
+ result: true,
+ },
+ {
+ id: "test-featureB",
+ preference: "test.featureB",
+ title: "Experimental Thing 2",
+ description: "This is a very boring experimental tool",
+ result: false,
+ },
+ {
+ id: "test-featureC",
+ preference: "test.featureC",
+ title: "Experimental Thing 3",
+ description: "This is a fun experimental feature for you can enable",
+ result: true,
+ },
+ {
+ id: "test-featureD",
+ preference: "test.featureD",
+ title: "Experimental Thing 4",
+ description: "This is a not a checkbox that you should be enabling",
+ result: false,
+ },
+ ];
+ for (let { id, preference } of definitions) {
+ server.addDefinition({ id, preference, isPublic: true });
+ }
+
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `about:preferences?definitionsUrl=${encodeURIComponent(
+ server.definitionsUrl
+ )}#paneExperimental`
+ );
+ let doc = gBrowser.contentDocument;
+
+ await TestUtils.waitForCondition(
+ () => doc.getElementById(definitions[definitions.length - 1].id),
+ "wait for the first public feature to get added to the DOM"
+ );
+
+ // Manually modify the labels of the features that were just added, so that the test
+ // can rely on consistent search terms.
+ for (let definition of definitions) {
+ let mainItem = doc.getElementById(definition.id);
+ mainItem.label = definition.title;
+ mainItem.removeAttribute("data-l10n-id");
+ let descItem = doc.getElementById(definition.id + "-description");
+ descItem.textContent = definition.description;
+ descItem.removeAttribute("data-l10n-id");
+ }
+
+ // First, check that all of the items are visible by default.
+ for (let definition of definitions) {
+ checkVisibility(
+ doc.getElementById(definition.id),
+ true,
+ `${definition.id} should be initially visible`
+ );
+ }
+
+ // After searching, only a subset should be visible.
+ await enterSearch(doc, "feature");
+
+ for (let definition of definitions) {
+ checkVisibility(
+ doc.getElementById(definition.id),
+ definition.result,
+ `${definition.id} should be ${
+ definition.result ? "visible" : "hidden"
+ } after first search`
+ );
+ info("Text for item was: " + doc.getElementById(definition.id).textContent);
+ }
+
+ // Further restrict the search to only a single item.
+ await enterSearch(doc, " you");
+
+ let shouldBeVisible = true;
+ for (let definition of definitions) {
+ checkVisibility(
+ doc.getElementById(definition.id),
+ shouldBeVisible,
+ `${definition.id} should be ${
+ shouldBeVisible ? "visible" : "hidden"
+ } after further search`
+ );
+ shouldBeVisible = false;
+ }
+
+ // Reset the search entirely.
+ let searchInput = doc.getElementById("searchInput");
+ searchInput.value = "";
+ searchInput.doCommand();
+
+ // Clearing the search will go to the general pane so switch back to the experimental pane.
+ EventUtils.synthesizeMouseAtCenter(
+ doc.getElementById("category-experimental"),
+ {},
+ gBrowser.contentWindow
+ );
+
+ for (let definition of definitions) {
+ checkVisibility(
+ doc.getElementById(definition.id),
+ true,
+ `${definition.id} should be visible after search cleared`
+ );
+ }
+
+ // Simulate entering a search and then clicking one of the category labels. The search
+ // should reset each time.
+ for (let category of ["category-search", "category-experimental"]) {
+ await enterSearch(doc, "feature");
+
+ for (let definition of definitions) {
+ checkVisibility(
+ doc.getElementById(definition.id),
+ definition.result,
+ `${definition.id} should be ${
+ definition.result ? "visible" : "hidden"
+ } after next search`
+ );
+ }
+
+ EventUtils.synthesizeMouseAtCenter(
+ doc.getElementById(category),
+ {},
+ gBrowser.contentWindow
+ );
+
+ for (let definition of definitions) {
+ checkVisibility(
+ doc.getElementById(definition.id),
+ true,
+ `${definition.id} should be visible after category change to ${category}`
+ );
+ }
+ }
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+function checkVisibility(element, expected, desc) {
+ return expected
+ ? is_element_visible(element, desc)
+ : is_element_hidden(element, desc);
+}
+
+function enterSearch(doc, query) {
+ let searchInput = doc.getElementById("searchInput");
+ searchInput.focus();
+
+ let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == query
+ );
+
+ EventUtils.sendString(query);
+
+ return searchCompletedPromise;
+}
diff --git a/browser/components/preferences/tests/browser_experimental_features_hidden_when_not_public.js b/browser/components/preferences/tests/browser_experimental_features_hidden_when_not_public.js
new file mode 100644
index 0000000000..e1e2adced9
--- /dev/null
+++ b/browser/components/preferences/tests/browser_experimental_features_hidden_when_not_public.js
@@ -0,0 +1,86 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function testNonPublicFeaturesShouldntGetDisplayed() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.experimental", true]],
+ });
+
+ const server = new DefinitionServer();
+ let definitions = [
+ { id: "test-featureA", isPublic: true, preference: "test.feature.a" },
+ { id: "test-featureB", isPublic: false, preference: "test.feature.b" },
+ { id: "test-featureC", isPublic: true, preference: "test.feature.c" },
+ ];
+ for (let { id, isPublic, preference } of definitions) {
+ server.addDefinition({ id, isPublic, preference });
+ }
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `about:preferences?definitionsUrl=${encodeURIComponent(
+ server.definitionsUrl
+ )}#paneExperimental`
+ );
+ let doc = gBrowser.contentDocument;
+
+ await TestUtils.waitForCondition(
+ () => doc.getElementById(definitions.find(d => d.isPublic).id),
+ "wait for the first public feature to get added to the DOM"
+ );
+
+ for (let definition of definitions) {
+ is(
+ !!doc.getElementById(definition.id),
+ definition.isPublic,
+ "feature should only be in DOM if it's public: " + definition.id
+ );
+ }
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function testNonPublicFeaturesShouldntGetDisplayed() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.preferences.experimental", true],
+ ["browser.preferences.experimental.hidden", false],
+ ],
+ });
+
+ const server = new DefinitionServer();
+ server.addDefinition({
+ id: "test-hidden",
+ isPublic: false,
+ preference: "test.feature.hidden",
+ });
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `about:preferences?definitionsUrl=${encodeURIComponent(
+ server.definitionsUrl
+ )}#paneExperimental`
+ );
+ let doc = gBrowser.contentDocument;
+
+ await TestUtils.waitForCondition(
+ () => doc.getElementById("category-experimental").hidden,
+ "Wait for Experimental Features section to get hidden"
+ );
+
+ ok(
+ doc.getElementById("category-experimental").hidden,
+ "Experimental Features section should be hidden when all features are hidden"
+ );
+ ok(
+ !doc.getElementById("firefoxExperimentalCategory"),
+ "Experimental Features header should not exist when all features are hidden"
+ );
+ is(
+ doc.querySelector(".category[selected]").id,
+ "category-general",
+ "When the experimental features section is hidden, navigating to #experimental should redirect to #general"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_experimental_features_resetall.js b/browser/components/preferences/tests/browser_experimental_features_resetall.js
new file mode 100644
index 0000000000..636374c057
--- /dev/null
+++ b/browser/components/preferences/tests/browser_experimental_features_resetall.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// It doesn't matter what two preferences are used here, as long as the first is a built-in
+// one that defaults to false and the second defaults to true.
+const KNOWN_PREF_1 = "browser.display.use_system_colors";
+const KNOWN_PREF_2 = "browser.underline_anchors";
+
+// This test verifies that pressing the reset all button for experimental features
+// resets all of the checkboxes to their default state.
+add_task(async function testResetAll() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.preferences.experimental", true],
+ ["test.featureA", false],
+ ["test.featureB", true],
+ [KNOWN_PREF_1, false],
+ [KNOWN_PREF_2, true],
+ ],
+ });
+
+ // Add a number of test features.
+ const server = new DefinitionServer();
+ let definitions = [
+ {
+ id: "test-featureA",
+ preference: "test.featureA",
+ defaultValue: false,
+ },
+ {
+ id: "test-featureB",
+ preference: "test.featureB",
+ defaultValue: true,
+ },
+ {
+ id: "test-featureC",
+ preference: KNOWN_PREF_1,
+ defaultValue: false,
+ },
+ {
+ id: "test-featureD",
+ preference: KNOWN_PREF_2,
+ defaultValue: true,
+ },
+ ];
+ for (let { id, preference, defaultValue } of definitions) {
+ server.addDefinition({ id, preference, defaultValue, isPublic: true });
+ }
+
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `about:preferences?definitionsUrl=${encodeURIComponent(
+ server.definitionsUrl
+ )}#paneExperimental`
+ );
+ let doc = gBrowser.contentDocument;
+
+ await TestUtils.waitForCondition(
+ () => doc.getElementById(definitions[definitions.length - 1].id),
+ "wait for the first public feature to get added to the DOM"
+ );
+
+ // Check the initial state of each feature.
+ ok(!Services.prefs.getBoolPref("test.featureA"), "initial state A");
+ ok(Services.prefs.getBoolPref("test.featureB"), "initial state B");
+ ok(!Services.prefs.getBoolPref(KNOWN_PREF_1), "initial state C");
+ ok(Services.prefs.getBoolPref(KNOWN_PREF_2), "initial state D");
+
+ // Modify the state of some of the features.
+ doc.getElementById("test-featureC").click();
+ doc.getElementById("test-featureD").click();
+ ok(!Services.prefs.getBoolPref("test.featureA"), "modified state A");
+ ok(Services.prefs.getBoolPref("test.featureB"), "modified state B");
+ ok(Services.prefs.getBoolPref(KNOWN_PREF_1), "modified state C");
+ ok(!Services.prefs.getBoolPref(KNOWN_PREF_2), "modified state D");
+
+ // State after reset.
+ let prefChangedPromise = new Promise(resolve => {
+ Services.prefs.addObserver(KNOWN_PREF_2, function observer() {
+ Services.prefs.removeObserver(KNOWN_PREF_2, observer);
+ resolve();
+ });
+ });
+ doc.getElementById("experimentalCategory-reset").click();
+ await prefChangedPromise;
+
+ // The preferences will be reset to the default value for the feature.
+ ok(!Services.prefs.getBoolPref("test.featureA"), "after reset state A");
+ ok(Services.prefs.getBoolPref("test.featureB"), "after reset state B");
+ ok(!Services.prefs.getBoolPref(KNOWN_PREF_1), "after reset state C");
+ ok(Services.prefs.getBoolPref(KNOWN_PREF_2), "after reset state D");
+ ok(
+ !doc.getElementById("test-featureA").checked,
+ "after reset checkbox state A"
+ );
+ ok(
+ doc.getElementById("test-featureB").checked,
+ "after reset checkbox state B"
+ );
+ ok(
+ !doc.getElementById("test-featureC").checked,
+ "after reset checkbox state C"
+ );
+ ok(
+ doc.getElementById("test-featureD").checked,
+ "after reset checkbox state D"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_extension_controlled.js b/browser/components/preferences/tests/browser_extension_controlled.js
new file mode 100644
index 0000000000..6cec9ba93a
--- /dev/null
+++ b/browser/components/preferences/tests/browser_extension_controlled.js
@@ -0,0 +1,1447 @@
+/* eslint-env webextensions */
+
+const PROXY_PREF = "network.proxy.type";
+const HOMEPAGE_URL_PREF = "browser.startup.homepage";
+const HOMEPAGE_OVERRIDE_KEY = "homepage_override";
+const URL_OVERRIDES_TYPE = "url_overrides";
+const NEW_TAB_KEY = "newTabURL";
+const PREF_SETTING_TYPE = "prefs";
+
+ChromeUtils.defineESModuleGetters(this, {
+ ExtensionSettingsStore:
+ "resource://gre/modules/ExtensionSettingsStore.sys.mjs",
+});
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "AboutNewTab",
+ "resource:///modules/AboutNewTab.jsm"
+);
+
+XPCOMUtils.defineLazyPreferenceGetter(this, "proxyType", PROXY_PREF);
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+AddonTestUtils.initMochitest(this);
+
+const { ExtensionPreferencesManager } = ChromeUtils.importESModule(
+ "resource://gre/modules/ExtensionPreferencesManager.sys.mjs"
+);
+
+const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
+const CHROME_URL_ROOT = TEST_DIR + "/";
+const PERMISSIONS_URL =
+ "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml";
+let sitePermissionsDialog;
+
+function getSupportsFile(path) {
+ let cr = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(
+ Ci.nsIChromeRegistry
+ );
+ let uri = Services.io.newURI(CHROME_URL_ROOT + path);
+ let fileurl = cr.convertChromeURL(uri);
+ return fileurl.QueryInterface(Ci.nsIFileURL);
+}
+
+function waitForMessageChange(
+ element,
+ cb,
+ opts = { attributes: true, attributeFilter: ["hidden"] }
+) {
+ return waitForMutation(element, opts, cb);
+}
+
+function getElement(id, doc = gBrowser.contentDocument) {
+ return doc.getElementById(id);
+}
+
+function waitForMessageHidden(messageId, doc) {
+ return waitForMessageChange(
+ getElement(messageId, doc),
+ target => target.hidden
+ );
+}
+
+function waitForMessageShown(messageId, doc) {
+ return waitForMessageChange(
+ getElement(messageId, doc),
+ target => !target.hidden
+ );
+}
+
+function waitForEnableMessage(messageId, doc) {
+ return waitForMessageChange(
+ getElement(messageId, doc),
+ target => target.classList.contains("extension-controlled-disabled"),
+ { attributeFilter: ["class"], attributes: true }
+ );
+}
+
+function waitForMessageContent(messageId, l10nId, doc) {
+ return waitForMessageChange(
+ getElement(messageId, doc),
+ target => doc.l10n.getAttributes(target).id === l10nId,
+ { childList: true }
+ );
+}
+
+async function openNotificationsPermissionDialog() {
+ let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
+ let doc = content.document;
+ let settingsButton = doc.getElementById("notificationSettingsButton");
+ settingsButton.click();
+ });
+
+ sitePermissionsDialog = await dialogOpened;
+ await sitePermissionsDialog.document.mozSubdialogReady;
+}
+
+async function disableExtensionViaClick(labelId, disableButtonId, doc) {
+ let controlledLabel = doc.getElementById(labelId);
+
+ let enableMessageShown = waitForEnableMessage(labelId, doc);
+ doc.getElementById(disableButtonId).click();
+ await enableMessageShown;
+
+ let controlledDescription = controlledLabel.querySelector("description");
+ is(
+ doc.l10n.getAttributes(controlledDescription.querySelector("label")).id,
+ "extension-controlled-enable",
+ "The user is notified of how to enable the extension again."
+ );
+
+ // The user can dismiss the enable instructions.
+ let hidden = waitForMessageHidden(labelId, doc);
+ controlledLabel.querySelector("image:last-of-type").click();
+ await hidden;
+}
+
+async function reEnableExtension(addon, labelId) {
+ let controlledMessageShown = waitForMessageShown(labelId);
+ await addon.enable();
+ await controlledMessageShown;
+}
+
+add_task(async function testExtensionControlledHomepage() {
+ const ADDON_ID = "@set_homepage";
+ const SECOND_ADDON_ID = "@second_set_homepage";
+
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+ let homepagePref = () => Services.prefs.getCharPref(HOMEPAGE_URL_PREF);
+ let originalHomepagePref = homepagePref();
+ is(
+ gBrowser.currentURI.spec,
+ "about:preferences#home",
+ "#home should be in the URI for about:preferences"
+ );
+ let doc = gBrowser.contentDocument;
+ let homeModeEl = doc.getElementById("homeMode");
+ let customSettingsSection = doc.getElementById("customSettings");
+
+ is(homeModeEl.itemCount, 3, "The menu list starts with 3 options");
+
+ let promise = TestUtils.waitForCondition(
+ () => homeModeEl.itemCount === 4,
+ "wait for the addon option to be added as an option in the menu list"
+ );
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ version: "1.0",
+ name: "set_homepage",
+ browser_specific_settings: {
+ gecko: {
+ id: ADDON_ID,
+ },
+ },
+ chrome_settings_overrides: { homepage: "/home.html" },
+ },
+ });
+ await extension.startup();
+ await promise;
+
+ // The homepage is set to the default and the custom settings section is hidden
+ is(homeModeEl.disabled, false, "The homepage menulist is enabled");
+ is(
+ customSettingsSection.hidden,
+ true,
+ "The custom settings element is hidden"
+ );
+
+ let addon = await AddonManager.getAddonByID(ADDON_ID);
+ is(
+ homeModeEl.value,
+ addon.id,
+ "the home select menu's value is set to the addon"
+ );
+
+ promise = TestUtils.waitForPrefChange(HOMEPAGE_URL_PREF);
+ // Set the Menu to the default value
+ homeModeEl.value = "0";
+ homeModeEl.dispatchEvent(new Event("command"));
+ await promise;
+ is(homepagePref(), originalHomepagePref, "homepage is set back to default");
+ let levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
+ addon.id,
+ HOMEPAGE_OVERRIDE_KEY,
+ PREF_SETTING_TYPE
+ );
+ is(
+ levelOfControl,
+ "not_controllable",
+ "getLevelOfControl returns not_controllable."
+ );
+ let setting = await ExtensionPreferencesManager.getSetting(
+ HOMEPAGE_OVERRIDE_KEY
+ );
+ ok(!setting.value, "the setting is not set.");
+
+ promise = TestUtils.waitForPrefChange(HOMEPAGE_URL_PREF);
+ // Set the menu to the addon value
+ homeModeEl.value = ADDON_ID;
+ homeModeEl.dispatchEvent(new Event("command"));
+ await promise;
+ ok(
+ homepagePref().startsWith("moz-extension") &&
+ homepagePref().endsWith("home.html"),
+ "Home url should be provided by the extension."
+ );
+ levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
+ addon.id,
+ HOMEPAGE_OVERRIDE_KEY,
+ PREF_SETTING_TYPE
+ );
+ is(
+ levelOfControl,
+ "controlled_by_this_extension",
+ "getLevelOfControl returns controlled_by_this_extension."
+ );
+ setting = await ExtensionPreferencesManager.getSetting(HOMEPAGE_OVERRIDE_KEY);
+ ok(
+ setting.value.startsWith("moz-extension") &&
+ setting.value.endsWith("home.html"),
+ "The setting value is the same as the extension."
+ );
+
+ // Add a second extension, ensure it is added to the menulist and selected.
+ promise = TestUtils.waitForCondition(
+ () => homeModeEl.itemCount == 5,
+ "addon option is added as an option in the menu list"
+ );
+ let secondExtension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ version: "1.0",
+ name: "second_set_homepage",
+ browser_specific_settings: {
+ gecko: {
+ id: SECOND_ADDON_ID,
+ },
+ },
+ chrome_settings_overrides: { homepage: "/home2.html" },
+ },
+ });
+ await secondExtension.startup();
+ await promise;
+
+ let secondAddon = await AddonManager.getAddonByID(SECOND_ADDON_ID);
+ is(homeModeEl.value, SECOND_ADDON_ID, "home menulist is set to the add-on");
+
+ levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
+ secondAddon.id,
+ HOMEPAGE_OVERRIDE_KEY,
+ PREF_SETTING_TYPE
+ );
+ is(
+ levelOfControl,
+ "controlled_by_this_extension",
+ "getLevelOfControl returns controlled_by_this_extension."
+ );
+ setting = await ExtensionPreferencesManager.getSetting(HOMEPAGE_OVERRIDE_KEY);
+ ok(
+ setting.value.startsWith("moz-extension") &&
+ setting.value.endsWith("home2.html"),
+ "The setting value is the same as the extension."
+ );
+
+ promise = TestUtils.waitForCondition(
+ () => homeModeEl.itemCount == 4,
+ "addon option is no longer an option in the menu list after disable, even if it was not selected"
+ );
+ await addon.disable();
+ await promise;
+
+ // Ensure that re-enabling an addon adds it back to the menulist
+ promise = TestUtils.waitForCondition(
+ () => homeModeEl.itemCount == 5,
+ "addon option is added again to the menulist when enabled"
+ );
+ await addon.enable();
+ await promise;
+
+ promise = TestUtils.waitForCondition(
+ () => homeModeEl.itemCount == 4,
+ "addon option is no longer an option in the menu list after disable"
+ );
+ await secondAddon.disable();
+ await promise;
+
+ promise = TestUtils.waitForCondition(
+ () => homeModeEl.itemCount == 5,
+ "addon option is added again to the menulist when enabled"
+ );
+ await secondAddon.enable();
+ await promise;
+
+ promise = TestUtils.waitForCondition(
+ () => homeModeEl.itemCount == 3,
+ "addon options are no longer an option in the menu list after disabling all addons"
+ );
+ await secondAddon.disable();
+ await addon.disable();
+ await promise;
+
+ is(homeModeEl.value, "0", "addon option is not selected in the menu list");
+
+ levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
+ secondAddon.id,
+ HOMEPAGE_OVERRIDE_KEY,
+ PREF_SETTING_TYPE
+ );
+ is(
+ levelOfControl,
+ "controllable_by_this_extension",
+ "getLevelOfControl returns controllable_by_this_extension."
+ );
+ setting = await ExtensionPreferencesManager.getSetting(HOMEPAGE_OVERRIDE_KEY);
+ ok(!setting.value, "The setting value is back to default.");
+
+ // The homepage elements are reset to their original state.
+ is(homepagePref(), originalHomepagePref, "homepage is set back to default");
+ is(homeModeEl.disabled, false, "The homepage menulist is enabled");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await extension.unload();
+ await secondExtension.unload();
+});
+
+add_task(async function testPrefLockedHomepage() {
+ const ADDON_ID = "@set_homepage";
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+ is(
+ gBrowser.currentURI.spec,
+ "about:preferences#home",
+ "#home should be in the URI for about:preferences"
+ );
+
+ let homePagePref = "browser.startup.homepage";
+ let buttonPrefs = [
+ "pref.browser.homepage.disable_button.current_page",
+ "pref.browser.homepage.disable_button.bookmark_page",
+ "pref.browser.homepage.disable_button.restore_default",
+ ];
+ let homeModeEl = doc.getElementById("homeMode");
+ let homePageInput = doc.getElementById("homePageUrl");
+ let prefs = Services.prefs.getDefaultBranch(null);
+ let mutationOpts = { attributes: true, attributeFilter: ["disabled"] };
+
+ // Helper functions.
+ let getButton = pref =>
+ doc.querySelector(`.homepage-button[preference="${pref}"`);
+ let waitForAllMutations = () =>
+ Promise.all(
+ buttonPrefs
+ .map(pref => waitForMutation(getButton(pref), mutationOpts))
+ .concat([
+ waitForMutation(homeModeEl, mutationOpts),
+ waitForMutation(homePageInput, mutationOpts),
+ ])
+ );
+ let getHomepage = () =>
+ Services.prefs.getCharPref("browser.startup.homepage");
+
+ let originalHomepage = getHomepage();
+ let extensionHomepage = "https://developer.mozilla.org/";
+ let lockedHomepage = "http://www.yahoo.com";
+
+ let lockPrefs = () => {
+ buttonPrefs.forEach(pref => {
+ prefs.setBoolPref(pref, true);
+ prefs.lockPref(pref);
+ });
+ // Do the homepage last since that's the only pref that triggers a UI update.
+ prefs.setCharPref(homePagePref, lockedHomepage);
+ prefs.lockPref(homePagePref);
+ };
+ let unlockPrefs = () => {
+ buttonPrefs.forEach(pref => {
+ prefs.unlockPref(pref);
+ prefs.setBoolPref(pref, false);
+ });
+ // Do the homepage last since that's the only pref that triggers a UI update.
+ prefs.unlockPref(homePagePref);
+ prefs.setCharPref(homePagePref, originalHomepage);
+ };
+
+ // Lock or unlock prefs then wait for all mutations to finish.
+ // Expects a bool indicating if we should lock or unlock.
+ let waitForLockMutations = lock => {
+ let mutationsDone = waitForAllMutations();
+ if (lock) {
+ lockPrefs();
+ } else {
+ unlockPrefs();
+ }
+ return mutationsDone;
+ };
+
+ ok(
+ originalHomepage != extensionHomepage,
+ "The extension will change the homepage"
+ );
+
+ // Install an extension that sets the homepage to MDN.
+ let promise = TestUtils.waitForPrefChange(HOMEPAGE_URL_PREF);
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ version: "1.0",
+ name: "set_homepage",
+ browser_specific_settings: {
+ gecko: {
+ id: ADDON_ID,
+ },
+ },
+ chrome_settings_overrides: { homepage: "https://developer.mozilla.org/" },
+ },
+ });
+ await extension.startup();
+ await promise;
+
+ // Check that everything is still disabled, homepage didn't change.
+ is(
+ getHomepage(),
+ extensionHomepage,
+ "The reported homepage is set by the extension"
+ );
+ is(
+ homePageInput.value,
+ extensionHomepage,
+ "The homepage is set by the extension"
+ );
+
+ // Lock all of the prefs, wait for the UI to update.
+ await waitForLockMutations(true);
+
+ // Check that everything is now disabled.
+ is(getHomepage(), lockedHomepage, "The reported homepage is set by the pref");
+ is(homePageInput.value, lockedHomepage, "The homepage is set by the pref");
+ is(
+ homePageInput.disabled,
+ true,
+ "The homepage is disabed when the pref is locked"
+ );
+
+ buttonPrefs.forEach(pref => {
+ is(
+ getButton(pref).disabled,
+ true,
+ `The ${pref} button is disabled when locked`
+ );
+ });
+
+ let levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
+ ADDON_ID,
+ HOMEPAGE_OVERRIDE_KEY,
+ PREF_SETTING_TYPE
+ );
+ is(
+ levelOfControl,
+ "not_controllable",
+ "getLevelOfControl returns not_controllable, the pref is locked."
+ );
+
+ // Verify that the UI is selecting the extension's Id in the menulist
+ let unlockedPromise = TestUtils.waitForCondition(
+ () => homeModeEl.value == ADDON_ID,
+ "Homepage menulist value is equal to the extension ID"
+ );
+ // Unlock the prefs, wait for the UI to update.
+ unlockPrefs();
+ await unlockedPromise;
+
+ is(
+ homeModeEl.disabled,
+ false,
+ "the home select element is not disabled when the pref is not locked"
+ );
+ is(
+ homePageInput.disabled,
+ false,
+ "The homepage is enabled when the pref is unlocked"
+ );
+ is(
+ getHomepage(),
+ extensionHomepage,
+ "The homepage is reset to extension page"
+ );
+
+ levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
+ ADDON_ID,
+ HOMEPAGE_OVERRIDE_KEY,
+ PREF_SETTING_TYPE
+ );
+ is(
+ levelOfControl,
+ "controlled_by_this_extension",
+ "getLevelOfControl returns controlled_by_this_extension after prefs are unlocked."
+ );
+ let setting = await ExtensionPreferencesManager.getSetting(
+ HOMEPAGE_OVERRIDE_KEY
+ );
+ is(
+ setting.value,
+ extensionHomepage,
+ "The setting value is equal to the extensionHomepage."
+ );
+
+ // Uninstall the add-on.
+ promise = TestUtils.waitForPrefChange(HOMEPAGE_URL_PREF);
+ await extension.unload();
+ await promise;
+
+ setting = await ExtensionPreferencesManager.getSetting(HOMEPAGE_OVERRIDE_KEY);
+ ok(!setting, "The setting is gone after the addon is uninstalled.");
+
+ // Check that everything is now enabled again.
+ is(
+ getHomepage(),
+ originalHomepage,
+ "The reported homepage is reset to original value"
+ );
+ is(homePageInput.value, "", "The homepage is empty");
+ is(
+ homePageInput.disabled,
+ false,
+ "The homepage is enabled after clearing lock"
+ );
+ is(
+ homeModeEl.disabled,
+ false,
+ "Homepage menulist is enabled after clearing lock"
+ );
+ buttonPrefs.forEach(pref => {
+ is(
+ getButton(pref).disabled,
+ false,
+ `The ${pref} button is enabled when unlocked`
+ );
+ });
+
+ // Lock the prefs without an extension.
+ await waitForLockMutations(true);
+
+ // Check that everything is now disabled.
+ is(getHomepage(), lockedHomepage, "The reported homepage is set by the pref");
+ is(homePageInput.value, lockedHomepage, "The homepage is set by the pref");
+ is(
+ homePageInput.disabled,
+ true,
+ "The homepage is disabed when the pref is locked"
+ );
+ is(
+ homeModeEl.disabled,
+ true,
+ "Homepage menulist is disabled when pref is locked"
+ );
+ buttonPrefs.forEach(pref => {
+ is(
+ getButton(pref).disabled,
+ true,
+ `The ${pref} button is disabled when locked`
+ );
+ });
+
+ // Unlock the prefs without an extension.
+ await waitForLockMutations(false);
+
+ // Check that everything is enabled again.
+ is(
+ getHomepage(),
+ originalHomepage,
+ "The homepage is reset to the original value"
+ );
+ is(homePageInput.value, "", "The homepage is clear after being unlocked");
+ is(
+ homePageInput.disabled,
+ false,
+ "The homepage is enabled after clearing lock"
+ );
+ is(
+ homeModeEl.disabled,
+ false,
+ "Homepage menulist is enabled after clearing lock"
+ );
+ buttonPrefs.forEach(pref => {
+ is(
+ getButton(pref).disabled,
+ false,
+ `The ${pref} button is enabled when unlocked`
+ );
+ });
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function testExtensionControlledNewTab() {
+ const ADDON_ID = "@set_newtab";
+ const SECOND_ADDON_ID = "@second_set_newtab";
+ const DEFAULT_NEWTAB = "about:newtab";
+ const NEWTAB_CONTROLLED_PREF = "browser.newtab.extensionControlled";
+
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+ is(
+ gBrowser.currentURI.spec,
+ "about:preferences#home",
+ "#home should be in the URI for about:preferences"
+ );
+
+ let doc = gBrowser.contentDocument;
+ let newTabMenuList = doc.getElementById("newTabMode");
+ // The new tab page is set to the default.
+ is(AboutNewTab.newTabURL, DEFAULT_NEWTAB, "new tab is set to default");
+
+ let promise = TestUtils.waitForCondition(
+ () => newTabMenuList.itemCount == 3,
+ "addon option is added as an option in the menu list"
+ );
+ // Install an extension that will set the new tab page.
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ version: "1.0",
+ name: "set_newtab",
+ browser_specific_settings: {
+ gecko: {
+ id: ADDON_ID,
+ },
+ },
+ chrome_url_overrides: { newtab: "/newtab.html" },
+ },
+ });
+ await extension.startup();
+
+ await promise;
+ let addon = await AddonManager.getAddonByID(ADDON_ID);
+
+ is(newTabMenuList.value, ADDON_ID, "New tab menulist is set to the add-on");
+
+ let levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
+ addon.id,
+ NEW_TAB_KEY,
+ URL_OVERRIDES_TYPE
+ );
+ is(
+ levelOfControl,
+ "controlled_by_this_extension",
+ "getLevelOfControl returns controlled_by_this_extension."
+ );
+ let setting = ExtensionSettingsStore.getSetting(
+ URL_OVERRIDES_TYPE,
+ NEW_TAB_KEY
+ );
+ ok(
+ setting.value.startsWith("moz-extension") &&
+ setting.value.endsWith("newtab.html"),
+ "The url_overrides is set by this extension"
+ );
+
+ promise = TestUtils.waitForPrefChange(NEWTAB_CONTROLLED_PREF);
+ // Set the menu to the default value
+ newTabMenuList.value = "0";
+ newTabMenuList.dispatchEvent(new Event("command"));
+ await promise;
+ let newTabPref = Services.prefs.getBoolPref(NEWTAB_CONTROLLED_PREF, false);
+ is(newTabPref, false, "the new tab is not controlled");
+
+ levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
+ addon.id,
+ NEW_TAB_KEY,
+ URL_OVERRIDES_TYPE
+ );
+ is(
+ levelOfControl,
+ "not_controllable",
+ "getLevelOfControl returns not_controllable."
+ );
+ setting = ExtensionSettingsStore.getSetting(URL_OVERRIDES_TYPE, NEW_TAB_KEY);
+ ok(!setting.value, "The url_overrides is not set by this extension");
+
+ promise = TestUtils.waitForPrefChange(NEWTAB_CONTROLLED_PREF);
+ // Set the menu to a the addon value
+ newTabMenuList.value = ADDON_ID;
+ newTabMenuList.dispatchEvent(new Event("command"));
+ await promise;
+ newTabPref = Services.prefs.getBoolPref(NEWTAB_CONTROLLED_PREF, false);
+ is(newTabPref, true, "the new tab is controlled");
+
+ // Add a second extension, ensure it is added to the menulist and selected.
+ promise = TestUtils.waitForCondition(
+ () => newTabMenuList.itemCount == 4,
+ "addon option is added as an option in the menu list"
+ );
+ let secondExtension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ version: "1.0",
+ name: "second_set_newtab",
+ browser_specific_settings: {
+ gecko: {
+ id: SECOND_ADDON_ID,
+ },
+ },
+ chrome_url_overrides: { newtab: "/newtab2.html" },
+ },
+ });
+ await secondExtension.startup();
+ await promise;
+ let secondAddon = await AddonManager.getAddonByID(SECOND_ADDON_ID);
+ is(
+ newTabMenuList.value,
+ SECOND_ADDON_ID,
+ "New tab menulist is set to the add-on"
+ );
+
+ levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
+ secondAddon.id,
+ NEW_TAB_KEY,
+ URL_OVERRIDES_TYPE
+ );
+ is(
+ levelOfControl,
+ "controlled_by_this_extension",
+ "getLevelOfControl returns controlled_by_this_extension."
+ );
+ setting = ExtensionSettingsStore.getSetting(URL_OVERRIDES_TYPE, NEW_TAB_KEY);
+ ok(
+ setting.value.startsWith("moz-extension") &&
+ setting.value.endsWith("newtab2.html"),
+ "The url_overrides is set by the second extension"
+ );
+
+ promise = TestUtils.waitForCondition(
+ () => newTabMenuList.itemCount == 3,
+ "addon option is no longer an option in the menu list after disable, even if it was not selected"
+ );
+ await addon.disable();
+ await promise;
+
+ // Ensure that re-enabling an addon adds it back to the menulist
+ promise = TestUtils.waitForCondition(
+ () => newTabMenuList.itemCount == 4,
+ "addon option is added again to the menulist when enabled"
+ );
+ await addon.enable();
+ await promise;
+
+ promise = TestUtils.waitForCondition(
+ () => newTabMenuList.itemCount == 3,
+ "addon option is no longer an option in the menu list after disable"
+ );
+ await secondAddon.disable();
+ await promise;
+
+ promise = TestUtils.waitForCondition(
+ () => newTabMenuList.itemCount == 4,
+ "addon option is added again to the menulist when enabled"
+ );
+ await secondAddon.enable();
+ await promise;
+
+ promise = TestUtils.waitForCondition(
+ () => newTabMenuList.itemCount == 2,
+ "addon options are all removed after disabling all"
+ );
+ await addon.disable();
+ await secondAddon.disable();
+ await promise;
+ is(
+ AboutNewTab.newTabURL,
+ DEFAULT_NEWTAB,
+ "new tab page is set back to default"
+ );
+
+ // Cleanup the tabs and add-on.
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await extension.unload();
+ await secondExtension.unload();
+});
+
+add_task(async function testExtensionControlledWebNotificationsPermission() {
+ let manifest = {
+ manifest_version: 2,
+ name: "TestExtension",
+ version: "1.0",
+ description: "Testing WebNotificationsDisable",
+ browser_specific_settings: { gecko: { id: "@web_notifications_disable" } },
+ permissions: ["browserSettings"],
+ browser_action: {
+ default_title: "Testing",
+ },
+ };
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await openNotificationsPermissionDialog();
+
+ let doc = sitePermissionsDialog.document;
+ let extensionControlledContent = doc.getElementById(
+ "browserNotificationsPermissionExtensionContent"
+ );
+
+ // Test that extension content is initially hidden.
+ ok(
+ extensionControlledContent.hidden,
+ "Extension content is initially hidden"
+ );
+
+ // Install an extension that will disable web notifications permission.
+ let messageShown = waitForMessageShown(
+ "browserNotificationsPermissionExtensionContent",
+ doc
+ );
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest,
+ useAddonManager: "permanent",
+ background() {
+ browser.browserSettings.webNotificationsDisabled.set({ value: true });
+ browser.test.sendMessage("load-extension");
+ },
+ });
+ await extension.startup();
+ await extension.awaitMessage("load-extension");
+ await messageShown;
+
+ let controlledDesc = extensionControlledContent.querySelector("description");
+ Assert.deepEqual(
+ doc.l10n.getAttributes(controlledDesc),
+ {
+ id: "extension-controlling-web-notifications",
+ args: {
+ name: "TestExtension",
+ },
+ },
+ "The user is notified that an extension is controlling the web notifications permission"
+ );
+ is(
+ extensionControlledContent.hidden,
+ false,
+ "The extension controlled row is not hidden"
+ );
+
+ // Disable the extension.
+ doc.getElementById("disableNotificationsPermissionExtension").click();
+
+ // Verify the user is notified how to enable the extension.
+ await waitForEnableMessage(extensionControlledContent.id, doc);
+ is(
+ doc.l10n.getAttributes(controlledDesc.querySelector("label")).id,
+ "extension-controlled-enable",
+ "The user is notified of how to enable the extension again"
+ );
+
+ // Verify the enable message can be dismissed.
+ let hidden = waitForMessageHidden(extensionControlledContent.id, doc);
+ let dismissButton = controlledDesc.querySelector("image:last-of-type");
+ dismissButton.click();
+ await hidden;
+
+ // Verify that the extension controlled content in hidden again.
+ is(
+ extensionControlledContent.hidden,
+ true,
+ "The extension controlled row is now hidden"
+ );
+
+ await extension.unload();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function testExtensionControlledHomepageUninstalledAddon() {
+ async function checkHomepageEnabled() {
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+ is(
+ gBrowser.currentURI.spec,
+ "about:preferences#home",
+ "#home should be in the URI for about:preferences"
+ );
+
+ // The homepage is enabled.
+ let homepageInput = doc.getElementById("homePageUrl");
+ is(homepageInput.disabled, false, "The homepage input is enabled");
+ is(homepageInput.value, "", "The homepage input is empty");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+
+ await ExtensionSettingsStore.initialize();
+
+ // Verify the setting isn't reported as controlled and the inputs are enabled.
+ is(
+ ExtensionSettingsStore.getSetting("prefs", "homepage_override"),
+ null,
+ "The homepage_override is not set"
+ );
+ await checkHomepageEnabled();
+
+ // Disarm any pending writes before we modify the JSONFile directly.
+ await ExtensionSettingsStore._reloadFile(false);
+
+ // Write out a bad store file.
+ let storeData = {
+ prefs: {
+ homepage_override: {
+ initialValue: "",
+ precedenceList: [
+ {
+ id: "bad@mochi.test",
+ installDate: 1508802672,
+ value: "https://developer.mozilla.org",
+ enabled: true,
+ },
+ ],
+ },
+ },
+ };
+ let jsonFileName = "extension-settings.json";
+ let storePath = PathUtils.join(PathUtils.profileDir, jsonFileName);
+
+ await IOUtils.writeUTF8(storePath, JSON.stringify(storeData));
+
+ // Reload the ExtensionSettingsStore so it will read the file on disk. Don't
+ // finalize the current store since it will overwrite our file.
+ await ExtensionSettingsStore._reloadFile(false);
+
+ // Verify that the setting is reported as set, but the homepage is still enabled
+ // since there is no matching installed extension.
+ is(
+ ExtensionSettingsStore.getSetting("prefs", "homepage_override").value,
+ "https://developer.mozilla.org",
+ "The homepage_override appears to be set"
+ );
+ await checkHomepageEnabled();
+
+ // Remove the bad store file that we used.
+ await IOUtils.remove(storePath);
+
+ // Reload the ExtensionSettingsStore again so it clears the data we added.
+ // Don't finalize the current store since it will write out the bad data.
+ await ExtensionSettingsStore._reloadFile(false);
+
+ is(
+ ExtensionSettingsStore.getSetting("prefs", "homepage_override"),
+ null,
+ "The ExtensionSettingsStore is left empty."
+ );
+});
+
+add_task(async function testExtensionControlledTrackingProtection() {
+ const TP_PREF = "privacy.trackingprotection.enabled";
+ const TP_DEFAULT = false;
+ const EXTENSION_ID = "@set_tp";
+ const CONTROLLED_LABEL_ID =
+ "contentBlockingTrackingProtectionExtensionContentLabel";
+ const CONTROLLED_BUTTON_ID =
+ "contentBlockingDisableTrackingProtectionExtension";
+
+ let tpEnabledPref = () => Services.prefs.getBoolPref(TP_PREF);
+
+ await SpecialPowers.pushPrefEnv({ set: [[TP_PREF, TP_DEFAULT]] });
+
+ function background() {
+ browser.privacy.websites.trackingProtectionMode.set({ value: "always" });
+ }
+
+ function verifyState(isControlled) {
+ is(tpEnabledPref(), isControlled, "TP pref is set to the expected value.");
+
+ let controlledLabel = doc.getElementById(CONTROLLED_LABEL_ID);
+ let controlledButton = doc.getElementById(CONTROLLED_BUTTON_ID);
+
+ is(
+ controlledLabel.hidden,
+ !isControlled,
+ "The extension controlled row's visibility is as expected."
+ );
+ is(
+ controlledButton.hidden,
+ !isControlled,
+ "The disable extension button's visibility is as expected."
+ );
+ if (isControlled) {
+ let controlledDesc = controlledLabel.querySelector("description");
+ Assert.deepEqual(
+ doc.l10n.getAttributes(controlledDesc),
+ {
+ id: "extension-controlling-websites-content-blocking-all-trackers",
+ args: {
+ name: "set_tp",
+ },
+ },
+ "The user is notified that an extension is controlling TP."
+ );
+ }
+
+ is(
+ doc.getElementById("trackingProtectionMenu").disabled,
+ isControlled,
+ "TP control is enabled."
+ );
+ }
+
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ let doc = gBrowser.contentDocument;
+
+ is(
+ gBrowser.currentURI.spec,
+ "about:preferences#privacy",
+ "#privacy should be in the URI for about:preferences"
+ );
+
+ verifyState(false);
+
+ // Install an extension that sets Tracking Protection.
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ name: "set_tp",
+ browser_specific_settings: { gecko: { id: EXTENSION_ID } },
+ permissions: ["privacy"],
+ },
+ background,
+ });
+
+ let messageShown = waitForMessageShown(CONTROLLED_LABEL_ID);
+ await extension.startup();
+ await messageShown;
+ let addon = await AddonManager.getAddonByID(EXTENSION_ID);
+
+ verifyState(true);
+
+ await disableExtensionViaClick(
+ CONTROLLED_LABEL_ID,
+ CONTROLLED_BUTTON_ID,
+ doc
+ );
+
+ verifyState(false);
+
+ // Enable the extension so we get the UNINSTALL event, which is needed by
+ // ExtensionPreferencesManager to clean up properly.
+ // TODO: BUG 1408226
+ await reEnableExtension(addon, CONTROLLED_LABEL_ID);
+
+ await extension.unload();
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function testExtensionControlledPasswordManager() {
+ const PASSWORD_MANAGER_ENABLED_PREF = "signon.rememberSignons";
+ const PASSWORD_MANAGER_ENABLED_DEFAULT = true;
+ const CONTROLLED_BUTTON_ID = "disablePasswordManagerExtension";
+ const CONTROLLED_LABEL_ID = "passwordManagerExtensionContent";
+ const EXTENSION_ID = "@remember_signons";
+ let manifest = {
+ manifest_version: 2,
+ name: "testPasswordManagerExtension",
+ version: "1.0",
+ description: "Testing rememberSignons",
+ browser_specific_settings: { gecko: { id: EXTENSION_ID } },
+ permissions: ["privacy"],
+ browser_action: {
+ default_title: "Testing rememberSignons",
+ },
+ };
+
+ let passwordManagerEnabledPref = () =>
+ Services.prefs.getBoolPref(PASSWORD_MANAGER_ENABLED_PREF);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [[PASSWORD_MANAGER_ENABLED_PREF, PASSWORD_MANAGER_ENABLED_DEFAULT]],
+ });
+ is(
+ passwordManagerEnabledPref(),
+ true,
+ "Password manager is enabled by default."
+ );
+
+ function verifyState(isControlled) {
+ is(
+ passwordManagerEnabledPref(),
+ !isControlled,
+ "Password manager pref is set to the expected value."
+ );
+ let controlledLabel =
+ gBrowser.contentDocument.getElementById(CONTROLLED_LABEL_ID);
+ let controlledButton =
+ gBrowser.contentDocument.getElementById(CONTROLLED_BUTTON_ID);
+ is(
+ controlledLabel.hidden,
+ !isControlled,
+ "The extension's controlled row visibility is as expected."
+ );
+ is(
+ controlledButton.hidden,
+ !isControlled,
+ "The extension's controlled button visibility is as expected."
+ );
+ if (isControlled) {
+ let controlledDesc = controlledLabel.querySelector("description");
+ Assert.deepEqual(
+ gBrowser.contentDocument.l10n.getAttributes(controlledDesc),
+ {
+ id: "extension-controlling-password-saving",
+ args: {
+ name: "testPasswordManagerExtension",
+ },
+ },
+ "The user is notified that an extension is controlling the remember signons pref."
+ );
+ }
+ }
+
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+
+ info("Verify that no extension is controlling the password manager pref.");
+ verifyState(false);
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest,
+ useAddonManager: "permanent",
+ background() {
+ browser.privacy.services.passwordSavingEnabled.set({ value: false });
+ },
+ });
+ let messageShown = waitForMessageShown(CONTROLLED_LABEL_ID);
+ await extension.startup();
+ await messageShown;
+
+ info(
+ "Verify that the test extension is controlling the password manager pref."
+ );
+ verifyState(true);
+
+ info("Verify that the extension shows as controlled when loaded again.");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ verifyState(true);
+
+ await disableExtensionViaClick(
+ CONTROLLED_LABEL_ID,
+ CONTROLLED_BUTTON_ID,
+ gBrowser.contentDocument
+ );
+
+ info(
+ "Verify that disabling the test extension removes the lock on the password manager pref."
+ );
+ verifyState(false);
+
+ await extension.unload();
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function testExtensionControlledProxyConfig() {
+ const proxySvc = Ci.nsIProtocolProxyService;
+ const PROXY_DEFAULT = proxySvc.PROXYCONFIG_SYSTEM;
+ const EXTENSION_ID = "@set_proxy";
+ const CONTROLLED_SECTION_ID = "proxyExtensionContent";
+ const CONTROLLED_BUTTON_ID = "disableProxyExtension";
+ const CONNECTION_SETTINGS_DESC_ID = "connectionSettingsDescription";
+ const PANEL_URL =
+ "chrome://browser/content/preferences/dialogs/connection.xhtml";
+
+ await SpecialPowers.pushPrefEnv({ set: [[PROXY_PREF, PROXY_DEFAULT]] });
+
+ function background() {
+ browser.proxy.settings.set({ value: { proxyType: "none" } });
+ }
+
+ function expectedConnectionSettingsMessage(doc, isControlled) {
+ return isControlled
+ ? "extension-controlling-proxy-config"
+ : "network-proxy-connection-description";
+ }
+
+ function connectionSettingsMessagePromise(doc, isControlled) {
+ return waitForMessageContent(
+ CONNECTION_SETTINGS_DESC_ID,
+ expectedConnectionSettingsMessage(doc, isControlled),
+ doc
+ );
+ }
+
+ function verifyProxyState(doc, isControlled) {
+ let isPanel = doc.getElementById(CONTROLLED_BUTTON_ID);
+ is(
+ proxyType === proxySvc.PROXYCONFIG_DIRECT,
+ isControlled,
+ "Proxy pref is set to the expected value."
+ );
+
+ if (isPanel) {
+ let controlledSection = doc.getElementById(CONTROLLED_SECTION_ID);
+
+ is(
+ controlledSection.hidden,
+ !isControlled,
+ "The extension controlled row's visibility is as expected."
+ );
+ if (isPanel) {
+ is(
+ doc.getElementById(CONTROLLED_BUTTON_ID).hidden,
+ !isControlled,
+ "The disable extension button's visibility is as expected."
+ );
+ }
+ if (isControlled) {
+ let controlledDesc = controlledSection.querySelector("description");
+ Assert.deepEqual(
+ doc.l10n.getAttributes(controlledDesc),
+ {
+ id: "extension-controlling-proxy-config",
+ args: {
+ name: "set_proxy",
+ },
+ },
+ "The user is notified that an extension is controlling proxy settings."
+ );
+ }
+ function getProxyControls() {
+ let controlGroup = doc.getElementById("networkProxyType");
+ let manualControlContainer = controlGroup.querySelector("#proxy-grid");
+ return {
+ manualControls: [
+ ...manualControlContainer.querySelectorAll(
+ "label[data-l10n-id]:not([control=networkProxyNone])"
+ ),
+ ...manualControlContainer.querySelectorAll("input"),
+ ...manualControlContainer.querySelectorAll("checkbox"),
+ ...doc.querySelectorAll("#networkProxySOCKSVersion > radio"),
+ ],
+ pacControls: [doc.getElementById("networkProxyAutoconfigURL")],
+ otherControls: [
+ doc.querySelector("label[control=networkProxyNone]"),
+ doc.getElementById("networkProxyNone"),
+ ...controlGroup.querySelectorAll(":scope > radio"),
+ ...doc.querySelectorAll("#ConnectionsDialogPane > checkbox"),
+ ],
+ };
+ }
+ let controlState = isControlled ? "disabled" : "enabled";
+ let controls = getProxyControls();
+ for (let element of controls.manualControls) {
+ let disabled =
+ isControlled || proxyType !== proxySvc.PROXYCONFIG_MANUAL;
+ is(
+ element.disabled,
+ disabled,
+ `Manual proxy controls should be ${controlState} - control: ${element.outerHTML}.`
+ );
+ }
+ for (let element of controls.pacControls) {
+ let disabled = isControlled || proxyType !== proxySvc.PROXYCONFIG_PAC;
+ is(
+ element.disabled,
+ disabled,
+ `PAC proxy controls should be ${controlState} - control: ${element.outerHTML}.`
+ );
+ }
+ for (let element of controls.otherControls) {
+ is(
+ element.disabled,
+ isControlled,
+ `Other proxy controls should be ${controlState} - control: ${element.outerHTML}.`
+ );
+ }
+ } else {
+ let elem = doc.getElementById(CONNECTION_SETTINGS_DESC_ID);
+ is(
+ doc.l10n.getAttributes(elem).id,
+ expectedConnectionSettingsMessage(doc, isControlled),
+ "The connection settings description is as expected."
+ );
+ }
+ }
+
+ async function reEnableProxyExtension(addon) {
+ let messageChanged = connectionSettingsMessagePromise(mainDoc, true);
+ await addon.enable();
+ await messageChanged;
+ }
+
+ async function openProxyPanel() {
+ let panel = await openAndLoadSubDialog(PANEL_URL);
+ let closingPromise = BrowserTestUtils.waitForEvent(
+ panel.document.getElementById("ConnectionsDialog"),
+ "dialogclosing"
+ );
+ ok(panel, "Proxy panel opened.");
+ return { panel, closingPromise };
+ }
+
+ async function closeProxyPanel(panelObj) {
+ let dialog = panelObj.panel.document.getElementById("ConnectionsDialog");
+ dialog.cancelDialog();
+ let panelClosingEvent = await panelObj.closingPromise;
+ ok(panelClosingEvent, "Proxy panel closed.");
+ }
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ let mainDoc = gBrowser.contentDocument;
+
+ is(
+ gBrowser.currentURI.spec,
+ "about:preferences#general",
+ "#general should be in the URI for about:preferences"
+ );
+
+ verifyProxyState(mainDoc, false);
+
+ // Open the connections panel.
+ let panelObj = await openProxyPanel();
+ let panelDoc = panelObj.panel.document;
+
+ verifyProxyState(panelDoc, false);
+
+ await closeProxyPanel(panelObj);
+
+ verifyProxyState(mainDoc, false);
+
+ // Install an extension that controls proxy settings. The extension needs
+ // incognitoOverride because controlling the proxy.settings requires private
+ // browsing access.
+ let extension = ExtensionTestUtils.loadExtension({
+ incognitoOverride: "spanning",
+ useAddonManager: "permanent",
+ manifest: {
+ name: "set_proxy",
+ browser_specific_settings: { gecko: { id: EXTENSION_ID } },
+ permissions: ["proxy"],
+ },
+ background,
+ });
+
+ let messageChanged = connectionSettingsMessagePromise(mainDoc, true);
+ await extension.startup();
+ await messageChanged;
+ let addon = await AddonManager.getAddonByID(EXTENSION_ID);
+
+ verifyProxyState(mainDoc, true);
+ messageChanged = connectionSettingsMessagePromise(mainDoc, false);
+
+ panelObj = await openProxyPanel();
+ panelDoc = panelObj.panel.document;
+
+ verifyProxyState(panelDoc, true);
+
+ await disableExtensionViaClick(
+ CONTROLLED_SECTION_ID,
+ CONTROLLED_BUTTON_ID,
+ panelDoc
+ );
+
+ verifyProxyState(panelDoc, false);
+
+ await closeProxyPanel(panelObj);
+ await messageChanged;
+
+ verifyProxyState(mainDoc, false);
+
+ await reEnableProxyExtension(addon);
+
+ verifyProxyState(mainDoc, true);
+ messageChanged = connectionSettingsMessagePromise(mainDoc, false);
+
+ panelObj = await openProxyPanel();
+ panelDoc = panelObj.panel.document;
+
+ verifyProxyState(panelDoc, true);
+
+ await disableExtensionViaClick(
+ CONTROLLED_SECTION_ID,
+ CONTROLLED_BUTTON_ID,
+ panelDoc
+ );
+
+ verifyProxyState(panelDoc, false);
+
+ await closeProxyPanel(panelObj);
+ await messageChanged;
+
+ verifyProxyState(mainDoc, false);
+
+ // Enable the extension so we get the UNINSTALL event, which is needed by
+ // ExtensionPreferencesManager to clean up properly.
+ // TODO: BUG 1408226
+ await reEnableProxyExtension(addon);
+
+ await extension.unload();
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Test that the newtab menu selection is correct when loading about:preferences
+add_task(async function testMenuSyncFromPrefs() {
+ const DEFAULT_NEWTAB = "about:newtab";
+
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+ is(
+ gBrowser.currentURI.spec,
+ "about:preferences#home",
+ "#home should be in the URI for about:preferences"
+ );
+
+ let doc = gBrowser.contentDocument;
+ let newTabMenuList = doc.getElementById("newTabMode");
+ // The new tab page is set to the default.
+ is(AboutNewTab.newTabURL, DEFAULT_NEWTAB, "new tab is set to default");
+
+ is(newTabMenuList.value, "0", "New tab menulist is set to the default");
+
+ newTabMenuList.value = "1";
+ newTabMenuList.dispatchEvent(new Event("command"));
+ is(newTabMenuList.value, "1", "New tab menulist is set to blank");
+
+ gBrowser.reloadTab(gBrowser.selectedTab);
+
+ await TestUtils.waitForCondition(
+ () => gBrowser.contentDocument.getElementById("newTabMode"),
+ "wait until element exists in new contentDoc"
+ );
+
+ is(
+ gBrowser.contentDocument.getElementById("newTabMode").value,
+ "1",
+ "New tab menulist is still set to blank"
+ );
+
+ // Cleanup
+ newTabMenuList.value = "0";
+ newTabMenuList.dispatchEvent(new Event("command"));
+ is(AboutNewTab.newTabURL, DEFAULT_NEWTAB, "new tab is set to default");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_filetype_dialog.js b/browser/components/preferences/tests/browser_filetype_dialog.js
new file mode 100644
index 0000000000..0e5ac036c4
--- /dev/null
+++ b/browser/components/preferences/tests/browser_filetype_dialog.js
@@ -0,0 +1,189 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+SimpleTest.requestCompleteLog();
+const { HandlerServiceTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/HandlerServiceTestUtils.sys.mjs"
+);
+
+let gHandlerService = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
+ Ci.nsIHandlerService
+);
+
+let gOldMailHandlers = [];
+let gDummyHandlers = [];
+let gOriginalPreferredMailHandler;
+let gOriginalPreferredPDFHandler;
+
+registerCleanupFunction(function () {
+ function removeDummyHandlers(handlers) {
+ // Remove any of the dummy handlers we created.
+ for (let i = handlers.Count() - 1; i >= 0; i--) {
+ try {
+ if (
+ gDummyHandlers.some(
+ h =>
+ h.uriTemplate ==
+ handlers.queryElementAt(i, Ci.nsIWebHandlerApp).uriTemplate
+ )
+ ) {
+ handlers.removeElementAt(i);
+ }
+ } catch (ex) {
+ /* ignore non-web-app handlers */
+ }
+ }
+ }
+ // Re-add the original protocol handlers:
+ let mailHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("mailto");
+ let mailHandlers = mailHandlerInfo.possibleApplicationHandlers;
+ for (let h of gOldMailHandlers) {
+ mailHandlers.appendElement(h);
+ }
+ removeDummyHandlers(mailHandlers);
+ mailHandlerInfo.preferredApplicationHandler = gOriginalPreferredMailHandler;
+ gHandlerService.store(mailHandlerInfo);
+
+ let pdfHandlerInfo =
+ HandlerServiceTestUtils.getHandlerInfo("application/pdf");
+ pdfHandlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally;
+ pdfHandlerInfo.preferredApplicationHandler = gOriginalPreferredPDFHandler;
+ let handlers = pdfHandlerInfo.possibleApplicationHandlers;
+ for (let i = handlers.Count() - 1; i >= 0; i--) {
+ let app = handlers.queryElementAt(i, Ci.nsIHandlerApp);
+ if (app.name == "Foopydoopydoo") {
+ handlers.removeElementAt(i);
+ }
+ }
+ gHandlerService.store(pdfHandlerInfo);
+
+ gBrowser.removeCurrentTab();
+});
+
+function scrubMailtoHandlers(handlerInfo) {
+ // Remove extant web handlers because they have icons that
+ // we fetch from the web, which isn't allowed in tests.
+ let handlers = handlerInfo.possibleApplicationHandlers;
+ for (let i = handlers.Count() - 1; i >= 0; i--) {
+ try {
+ let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
+ gOldMailHandlers.push(handler);
+ // If we get here, this is a web handler app. Remove it:
+ handlers.removeElementAt(i);
+ } catch (ex) {}
+ }
+}
+
+("use strict");
+
+add_setup(async function () {
+ // Create our dummy handlers
+ let handler1 = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(
+ Ci.nsIWebHandlerApp
+ );
+ handler1.name = "Handler 1";
+ handler1.uriTemplate = "https://example.com/first/%s";
+
+ let handler2 = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(
+ Ci.nsIWebHandlerApp
+ );
+ handler2.name = "Handler 2";
+ handler2.uriTemplate = "http://example.org/second/%s";
+ gDummyHandlers.push(handler1, handler2);
+
+ function substituteWebHandlers(handlerInfo) {
+ // Append the dummy handlers to replace them:
+ let handlers = handlerInfo.possibleApplicationHandlers;
+ handlers.appendElement(handler1);
+ handlers.appendElement(handler2);
+ gHandlerService.store(handlerInfo);
+ }
+ // Set up our mailto handler test infrastructure.
+ let mailtoHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("mailto");
+ scrubMailtoHandlers(mailtoHandlerInfo);
+ gOriginalPreferredMailHandler = mailtoHandlerInfo.preferredApplicationHandler;
+ substituteWebHandlers(mailtoHandlerInfo);
+
+ // Now add a pdf handler:
+ let pdfHandlerInfo =
+ HandlerServiceTestUtils.getHandlerInfo("application/pdf");
+ // PDF doesn't have built-in web handlers, so no need to scrub.
+ gOriginalPreferredPDFHandler = pdfHandlerInfo.preferredApplicationHandler;
+ let handlers = pdfHandlerInfo.possibleApplicationHandlers;
+ let appHandler = Cc[
+ "@mozilla.org/uriloader/local-handler-app;1"
+ ].createInstance(Ci.nsILocalHandlerApp);
+ appHandler.name = "Foopydoopydoo";
+ appHandler.executable = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ appHandler.executable.append("dummy.exe");
+ // Prefs are picky and want this to exist and be executable (bug 1626009):
+ if (!appHandler.executable.exists()) {
+ appHandler.executable.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o777);
+ }
+
+ handlers.appendElement(appHandler);
+
+ pdfHandlerInfo.preferredApplicationHandler = appHandler;
+ pdfHandlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+ gHandlerService.store(pdfHandlerInfo);
+
+ await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+ info("Preferences page opened on the general pane.");
+
+ await gBrowser.selectedBrowser.contentWindow.promiseLoadHandlersList;
+ info("Apps list loaded.");
+});
+
+add_task(async function dialogShowsCorrectContent() {
+ let win = gBrowser.selectedBrowser.contentWindow;
+
+ let container = win.document.getElementById("handlersView");
+
+ // First, find the PDF item.
+ let pdfItem = container.querySelector("richlistitem[type='application/pdf']");
+ Assert.ok(pdfItem, "pdfItem is present in handlersView.");
+ pdfItem.scrollIntoView({ block: "center" });
+ pdfItem.closest("richlistbox").selectItem(pdfItem);
+
+ // Open its menu
+ let list = pdfItem.querySelector(".actionsMenu");
+ let popup = list.menupopup;
+ let popupShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(list, {}, win);
+ await popupShown;
+
+ // Then open the dialog
+ const promiseDialogLoaded = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/applicationManager.xhtml"
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ popup.querySelector(".manage-app-item"),
+ {},
+ win
+ );
+ let dialogWin = await promiseDialogLoaded;
+
+ // Then verify that the description is correct.
+ let desc = dialogWin.document.getElementById("appDescription");
+ let descL10n = dialogWin.document.l10n.getAttributes(desc);
+ is(descL10n.id, "app-manager-handle-file", "Should have right string");
+ let stringBundle = Services.strings.createBundle(
+ "chrome://mozapps/locale/downloads/unknownContentType.properties"
+ );
+ is(
+ descL10n.args.type,
+ stringBundle.GetStringFromName("pdfExtHandlerDescription"),
+ "Should have PDF string bits."
+ );
+
+ // And that there's one item in the list, with the correct name:
+ let appList = dialogWin.document.getElementById("appList");
+ is(appList.itemCount, 1, "Should have 1 item in the list");
+ is(
+ appList.selectedItem.querySelector("label").getAttribute("value"),
+ "Foopydoopydoo",
+ "Should have the right executable label"
+ );
+
+ dialogWin.close();
+});
diff --git a/browser/components/preferences/tests/browser_fluent.js b/browser/components/preferences/tests/browser_fluent.js
new file mode 100644
index 0000000000..db2daecc4f
--- /dev/null
+++ b/browser/components/preferences/tests/browser_fluent.js
@@ -0,0 +1,40 @@
+function whenMainPaneLoadedFinished() {
+ return new Promise(function (resolve, reject) {
+ const topic = "main-pane-loaded";
+ Services.obs.addObserver(function observer(aSubject) {
+ Services.obs.removeObserver(observer, topic);
+ resolve();
+ }, topic);
+ });
+}
+
+// Temporary test for an experimental new localization API.
+// See bug 1402069 for details.
+add_task(async function () {
+ // The string is used only when `browserTabsRemoteAutostart` is true
+ if (!Services.appinfo.browserTabsRemoteAutostart) {
+ ok(true, "fake test to avoid harness complaining");
+ return;
+ }
+
+ await Promise.all([
+ openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true }),
+ whenMainPaneLoadedFinished(),
+ ]);
+
+ let doc = gBrowser.contentDocument;
+ await doc.l10n.ready;
+
+ let [msg] = await doc.l10n.formatMessages([{ id: "category-general" }]);
+
+ let elem = doc.querySelector(`#category-general`);
+
+ Assert.deepEqual(msg, {
+ value: null,
+ attributes: [
+ { name: "tooltiptext", value: elem.getAttribute("tooltiptext") },
+ ],
+ });
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_homepage_default.js b/browser/components/preferences/tests/browser_homepage_default.js
new file mode 100644
index 0000000000..e3fcdcec5e
--- /dev/null
+++ b/browser/components/preferences/tests/browser_homepage_default.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function default_homepage_test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.startup.page", 1]],
+ });
+ let defaults = Services.prefs.getDefaultBranch("");
+ // Simulate a homepage set via policy or a distribution.
+ defaults.setStringPref("browser.startup.homepage", "https://example.com");
+
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+
+ let doc = gBrowser.contentDocument;
+ let homeMode = doc.getElementById("homeMode");
+ Assert.equal(homeMode.value, 2, "homeMode should be 2 (Custom URL)");
+
+ let homePageUrl = doc.getElementById("homePageUrl");
+ Assert.equal(
+ homePageUrl.value,
+ "https://example.com",
+ "homePageUrl should be example.com"
+ );
+
+ registerCleanupFunction(async () => {
+ defaults.setStringPref("browser.startup.homepage", "about:home");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+});
diff --git a/browser/components/preferences/tests/browser_homepages_filter_aboutpreferences.js b/browser/components/preferences/tests/browser_homepages_filter_aboutpreferences.js
new file mode 100644
index 0000000000..f54d0edaaa
--- /dev/null
+++ b/browser/components/preferences/tests/browser_homepages_filter_aboutpreferences.js
@@ -0,0 +1,33 @@
+add_task(async function testSetHomepageUseCurrent() {
+ is(
+ gBrowser.currentURI.spec,
+ "about:blank",
+ "Test starts with about:blank open"
+ );
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+ let doc = gBrowser.contentDocument;
+ is(
+ gBrowser.currentURI.spec,
+ "about:preferences#home",
+ "#home should be in the URI for about:preferences"
+ );
+ let oldHomepage = HomePage.get();
+
+ let useCurrent = doc.getElementById("useCurrentBtn");
+ useCurrent.click();
+
+ is(gBrowser.tabs.length, 3, "Three tabs should be open");
+ await TestUtils.waitForCondition(
+ () => HomePage.get() == "about:blank|about:home"
+ );
+ is(
+ HomePage.get(),
+ "about:blank|about:home",
+ "about:blank and about:home should be the only homepages set"
+ );
+
+ HomePage.safeSet(oldHomepage);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_homepages_use_bookmark.js b/browser/components/preferences/tests/browser_homepages_use_bookmark.js
new file mode 100644
index 0000000000..572783481d
--- /dev/null
+++ b/browser/components/preferences/tests/browser_homepages_use_bookmark.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URL1 = "http://example.com/1";
+const TEST_URL2 = "http://example.com/2";
+
+add_setup(async function () {
+ let oldHomepagePref = Services.prefs.getCharPref("browser.startup.homepage");
+
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+
+ Assert.equal(
+ gBrowser.currentURI.spec,
+ "about:preferences#home",
+ "#home should be in the URI for about:preferences"
+ );
+
+ registerCleanupFunction(async () => {
+ Services.prefs.setCharPref("browser.startup.homepage", oldHomepagePref);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await PlacesUtils.bookmarks.eraseEverything();
+ });
+});
+
+add_task(async function testSetHomepageFromBookmark() {
+ let bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "TestHomepage",
+ url: TEST_URL1,
+ });
+
+ let doc = gBrowser.contentDocument;
+ // Select the custom URLs option.
+ doc.getElementById("homeMode").value = 2;
+
+ let promiseSubDialogLoaded = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/selectBookmark.xhtml"
+ );
+ doc.getElementById("useBookmarkBtn").click();
+
+ let dialog = await promiseSubDialogLoaded;
+ dialog.document.getElementById("bookmarks").selectItems([bm.guid]);
+ dialog.document
+ .getElementById("selectBookmarkDialog")
+ .getButton("accept")
+ .click();
+
+ await TestUtils.waitForCondition(() => HomePage.get() == TEST_URL1);
+
+ Assert.equal(
+ HomePage.get(),
+ TEST_URL1,
+ "Should have set the homepage to the same as the bookmark."
+ );
+});
+
+add_task(async function testSetHomepageFromTopLevelFolder() {
+ // Insert a second item into the menu folder
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "TestHomepage",
+ url: TEST_URL2,
+ });
+
+ let doc = gBrowser.contentDocument;
+ // Select the custom URLs option.
+ doc.getElementById("homeMode").value = 2;
+
+ let promiseSubDialogLoaded = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/selectBookmark.xhtml"
+ );
+ doc.getElementById("useBookmarkBtn").click();
+
+ let dialog = await promiseSubDialogLoaded;
+ dialog.document
+ .getElementById("bookmarks")
+ .selectItems([PlacesUtils.bookmarks.menuGuid]);
+ dialog.document
+ .getElementById("selectBookmarkDialog")
+ .getButton("accept")
+ .click();
+
+ await TestUtils.waitForCondition(
+ () => HomePage.get() == `${TEST_URL1}|${TEST_URL2}`
+ );
+
+ Assert.equal(
+ HomePage.get(),
+ `${TEST_URL1}|${TEST_URL2}`,
+ "Should have set the homepage to the same as the bookmark."
+ );
+});
diff --git a/browser/components/preferences/tests/browser_hometab_restore_defaults.js b/browser/components/preferences/tests/browser_hometab_restore_defaults.js
new file mode 100644
index 0000000000..55a9a974b2
--- /dev/null
+++ b/browser/components/preferences/tests/browser_hometab_restore_defaults.js
@@ -0,0 +1,220 @@
+add_task(async function testRestoreDefaultsBtn_visible() {
+ const before = SpecialPowers.Services.prefs.getStringPref(
+ "browser.newtabpage.activity-stream.feeds.section.topstories.options",
+ ""
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Hide Pocket prefs so we don't trigger network requests when we reset all preferences
+ [
+ "browser.newtabpage.activity-stream.feeds.section.topstories.options",
+ JSON.stringify(Object.assign({}, JSON.parse(before), { hidden: true })),
+ ],
+ [
+ "browser.newtabpage.activity-stream.discoverystream.endpointSpocsClear",
+ "",
+ ],
+ // Set a user pref to false to force the Restore Defaults button to be visible
+ ["browser.newtabpage.activity-stream.feeds.topsites", false],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:preferences#home",
+ false
+ );
+ let browser = tab.linkedBrowser;
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ SpecialPowers.spawn(
+ browser,
+ [],
+ () =>
+ content.document.getElementById("restoreDefaultHomePageBtn") !== null
+ ),
+ "Wait for the button to be added to the page"
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ SpecialPowers.spawn(
+ browser,
+ [],
+ () =>
+ content.document.querySelector(
+ "[data-subcategory='topsites'] checkbox"
+ ) !== null
+ ),
+ "Wait for the preference checkbox to load"
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ SpecialPowers.spawn(
+ browser,
+ [],
+ () =>
+ content.document.getElementById("restoreDefaultHomePageBtn")
+ .hidden === false
+ ),
+ "Should show the Restore Defaults btn because pref is changed"
+ );
+
+ await SpecialPowers.spawn(browser, [], () =>
+ content.document.getElementById("restoreDefaultHomePageBtn").click()
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ SpecialPowers.spawn(
+ browser,
+ [],
+ () =>
+ content.document.querySelector(
+ "[data-subcategory='topsites'] checkbox"
+ ).checked
+ ),
+ "Should have checked preference"
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ SpecialPowers.spawn(
+ browser,
+ [],
+ () =>
+ content.document.getElementById("restoreDefaultHomePageBtn").style
+ .visibility === "hidden"
+ ),
+ "Should not show the Restore Defaults btn if prefs were reset"
+ );
+
+ const topsitesPref = await SpecialPowers.Services.prefs.getBoolPref(
+ "browser.newtabpage.activity-stream.feeds.topsites"
+ );
+ Assert.ok(topsitesPref, "Topsites pref should have the default value");
+
+ await SpecialPowers.popPrefEnv();
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testRestoreDefaultsBtn_hidden() {
+ const before = SpecialPowers.Services.prefs.getStringPref(
+ "browser.newtabpage.activity-stream.feeds.section.topstories.options",
+ ""
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Hide Pocket pref so we don't trigger network requests when we reset all preferences
+ [
+ "browser.newtabpage.activity-stream.feeds.section.topstories.options",
+ JSON.stringify(Object.assign({}, JSON.parse(before), { hidden: true })),
+ ],
+ [
+ "browser.newtabpage.activity-stream.discoverystream.endpointSpocsClear",
+ "",
+ ],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:preferences#home",
+ false
+ );
+ let browser = tab.linkedBrowser;
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ SpecialPowers.spawn(
+ browser,
+ [],
+ () =>
+ content.document.getElementById("restoreDefaultHomePageBtn") !== null
+ ),
+ "Wait for the button to be added to the page"
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ SpecialPowers.spawn(
+ browser,
+ [],
+ () =>
+ content.document.querySelector(
+ "[data-subcategory='topsites'] checkbox"
+ ) !== null
+ ),
+ "Wait for the preference checkbox to load"
+ );
+
+ const btnDefault = await SpecialPowers.spawn(
+ browser,
+ [],
+ () =>
+ content.document.getElementById("restoreDefaultHomePageBtn").style
+ .visibility
+ );
+ Assert.equal(
+ btnDefault,
+ "hidden",
+ "When no prefs are changed button should not show up"
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ SpecialPowers.spawn(
+ browser,
+ [],
+ () =>
+ content.document.querySelector(
+ "[data-subcategory='topsites'] checkbox"
+ ).checked
+ ),
+ "Should have checked preference"
+ );
+
+ // Uncheck a pref
+ await SpecialPowers.spawn(browser, [], () =>
+ content.document
+ .querySelector("[data-subcategory='topsites'] checkbox")
+ .click()
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ SpecialPowers.spawn(
+ browser,
+ [],
+ () =>
+ !content.document.querySelector(
+ "[data-subcategory='topsites'] checkbox"
+ ).checked
+ ),
+ "Should have unchecked preference"
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ SpecialPowers.spawn(
+ browser,
+ [],
+ () =>
+ content.document.getElementById("restoreDefaultHomePageBtn").style
+ .visibility === ""
+ ),
+ "Should show the Restore Defaults btn if prefs were changed"
+ );
+
+ // Reset the pref
+ await SpecialPowers.Services.prefs.clearUserPref(
+ "browser.newtabpage.activity-stream.feeds.topsites"
+ );
+
+ await SpecialPowers.popPrefEnv();
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/preferences/tests/browser_https_only_exceptions.js b/browser/components/preferences/tests/browser_https_only_exceptions.js
new file mode 100644
index 0000000000..ad7cd34571
--- /dev/null
+++ b/browser/components/preferences/tests/browser_https_only_exceptions.js
@@ -0,0 +1,279 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * First Test
+ * Checks if buttons are disabled/enabled and visible/hidden correctly.
+ */
+add_task(async function testButtons() {
+ // Let's make sure HTTPS-Only Mode is off.
+ await setHttpsOnlyPref("off");
+
+ // Open the privacy-pane in about:preferences
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+
+ // Get button-element to open the exceptions-dialog
+ const exceptionButton = gBrowser.contentDocument.getElementById(
+ "httpsOnlyExceptionButton"
+ );
+
+ is(
+ exceptionButton.disabled,
+ true,
+ "HTTPS-Only exception button should be disabled when HTTPS-Only Mode is disabled."
+ );
+
+ await setHttpsOnlyPref("private");
+ is(
+ exceptionButton.disabled,
+ true,
+ "HTTPS-Only exception button should be disabled when HTTPS-Only Mode is only enabled in private browsing."
+ );
+
+ await setHttpsOnlyPref("everywhere");
+ is(
+ exceptionButton.disabled,
+ false,
+ "HTTPS-Only exception button should be enabled when HTTPS-Only Mode enabled everywhere."
+ );
+
+ // Now that the button is clickable, we open the dialog
+ // to check if the correct buttons are visible
+ let promiseSubDialogLoaded = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/permissions.xhtml"
+ );
+ exceptionButton.doCommand();
+
+ let win = await promiseSubDialogLoaded;
+
+ const dialogDoc = win.document;
+
+ is(
+ dialogDoc.getElementById("btnBlock").hidden,
+ true,
+ "Block button should not be visible in HTTPS-Only Dialog."
+ );
+ is(
+ dialogDoc.getElementById("btnCookieSession").hidden,
+ true,
+ "Cookie specific allow button should not be visible in HTTPS-Only Dialog."
+ );
+ is(
+ dialogDoc.getElementById("btnAllow").hidden,
+ true,
+ "Allow button should not be visible in HTTPS-Only Dialog."
+ );
+ is(
+ dialogDoc.getElementById("btnHttpsOnlyOff").hidden,
+ false,
+ "HTTPS-Only off button should be visible in HTTPS-Only Dialog."
+ );
+ is(
+ dialogDoc.getElementById("btnHttpsOnlyOffTmp").hidden,
+ false,
+ "HTTPS-Only temporary off button should be visible in HTTPS-Only Dialog."
+ );
+
+ // Reset prefs and close the tab
+ await SpecialPowers.flushPrefEnv();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Second Test
+ * Checks permissions are added and removed correctly.
+ *
+ * Each test opens a new dialog, performs an action (second argument),
+ * then closes the dialog and checks if the changes were made (third argument).
+ */
+add_task(async function checkDialogFunctionality() {
+ // Enable HTTPS-Only Mode for every window, so the exceptions dialog is accessible.
+ await setHttpsOnlyPref("everywhere");
+
+ // Open the privacy-pane in about:preferences
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ const preferencesDoc = gBrowser.contentDocument;
+
+ // Test if we can add permanent exceptions
+ await runTest(
+ preferencesDoc,
+ elements => {
+ assertListContents(elements, []);
+
+ elements.url.value = "test.com";
+ elements.btnAllow.doCommand();
+
+ assertListContents(elements, [
+ ["http://test.com", elements.allowL10nId],
+ ["https://test.com", elements.allowL10nId],
+ ]);
+ },
+ () => [
+ {
+ type: "https-only-load-insecure",
+ origin: "http://test.com",
+ data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expireType: Ci.nsIPermissionManager.EXPIRE_NEVER,
+ },
+ {
+ type: "https-only-load-insecure",
+ origin: "https://test.com",
+ data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expireType: Ci.nsIPermissionManager.EXPIRE_NEVER,
+ },
+ ]
+ );
+
+ // Test if items are retained, and if temporary exceptions are added correctly
+ await runTest(
+ preferencesDoc,
+ elements => {
+ assertListContents(elements, [
+ ["http://test.com", elements.allowL10nId],
+ ["https://test.com", elements.allowL10nId],
+ ]);
+
+ elements.url.value = "1.1.1.1:8080";
+ elements.btnAllowSession.doCommand();
+
+ assertListContents(elements, [
+ ["http://test.com", elements.allowL10nId],
+ ["https://test.com", elements.allowL10nId],
+ ["http://1.1.1.1:8080", elements.allowSessionL10nId],
+ ["https://1.1.1.1:8080", elements.allowSessionL10nId],
+ ]);
+ },
+ () => [
+ {
+ type: "https-only-load-insecure",
+ origin: "http://1.1.1.1:8080",
+ data: "added",
+ capability: Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION,
+ expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
+ },
+ {
+ type: "https-only-load-insecure",
+ origin: "https://1.1.1.1:8080",
+ data: "added",
+ capability: Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION,
+ expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
+ },
+ ]
+ );
+
+ await runTest(
+ preferencesDoc,
+ elements => {
+ while (elements.richlistbox.itemCount) {
+ elements.richlistbox.selectedIndex = 0;
+ elements.btnRemove.doCommand();
+ }
+ assertListContents(elements, []);
+ },
+ elements => {
+ let richlistItems = elements.richlistbox.getElementsByAttribute(
+ "origin",
+ "*"
+ );
+ let observances = [];
+ for (let item of richlistItems) {
+ observances.push({
+ type: "https-only-load-insecure",
+ origin: item.getAttribute("origin"),
+ data: "deleted",
+ });
+ }
+ return observances;
+ }
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Changes HTTPS-Only Mode pref
+ * @param {string} state "everywhere", "private", "off"
+ */
+async function setHttpsOnlyPref(state) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.security.https_only_mode", state === "everywhere"],
+ ["dom.security.https_only_mode_pbm", state === "private"],
+ ],
+ });
+}
+
+/**
+ * Opens new exceptions dialog, runs test function
+ * @param {HTMLElement} preferencesDoc document of about:preferences tab
+ * @param {function} test function to call when dialog is open
+ * @param {Array} observances permission changes to observe (order is important)
+ */
+async function runTest(preferencesDoc, test, observancesFn) {
+ // Click on exception-button and wait for dialog to open
+ let promiseSubDialogLoaded = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/permissions.xhtml"
+ );
+ preferencesDoc.getElementById("httpsOnlyExceptionButton").doCommand();
+
+ let win = await promiseSubDialogLoaded;
+
+ // Create a bunch of references to UI-elements for the test-function
+ const doc = win.document;
+ let elements = {
+ richlistbox: doc.getElementById("permissionsBox"),
+ url: doc.getElementById("url"),
+ btnAllow: doc.getElementById("btnHttpsOnlyOff"),
+ btnAllowSession: doc.getElementById("btnHttpsOnlyOffTmp"),
+ btnRemove: doc.getElementById("removePermission"),
+ allowL10nId: win.gPermissionManager._getCapabilityL10nId(
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ ),
+ allowSessionL10nId: win.gPermissionManager._getCapabilityL10nId(
+ Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION
+ ),
+ };
+
+ // Some observances need to be computed based on the current state.
+ const observances = observancesFn(elements);
+
+ // Run test function
+ await test(elements);
+
+ // Click on "Save changes" and wait for permission changes.
+ let btnApplyChanges = doc.querySelector("dialog").getButton("accept");
+ let observeAllPromise = createObserveAllPromise(observances);
+
+ btnApplyChanges.doCommand();
+ await observeAllPromise;
+}
+
+function assertListContents(elements, expected) {
+ is(
+ elements.richlistbox.itemCount,
+ expected.length,
+ "Richlistbox should match the expected amount of exceptions."
+ );
+
+ for (let i = 0; i < expected.length; i++) {
+ let website = expected[i][0];
+ let listItem = elements.richlistbox.getElementsByAttribute(
+ "origin",
+ website
+ );
+ is(listItem.length, 1, "Each origin should be unique");
+ is(
+ listItem[0]
+ .querySelector(".website-capability-value")
+ .getAttribute("data-l10n-id"),
+ expected[i][1],
+ "List item capability should match expected l10n-id"
+ );
+ }
+}
diff --git a/browser/components/preferences/tests/browser_https_only_section.js b/browser/components/preferences/tests/browser_https_only_section.js
new file mode 100644
index 0000000000..a43448adf5
--- /dev/null
+++ b/browser/components/preferences/tests/browser_https_only_section.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Bug 1671122 - Fixed bug where second click on HTTPS-Only Mode enable-checkbox disables it again.
+// https://bugzilla.mozilla.org/bug/1671122
+"use strict";
+
+const HTTPS_ONLY_ENABLED = "enabled";
+const HTTPS_ONLY_PBM_ONLY = "privateOnly";
+const HTTPS_ONLY_DISABLED = "disabled";
+
+add_task(async function httpsOnlyRadioGroupIsWorking() {
+ // Make sure HTTPS-Only mode is only enabled for PBM
+
+ registerCleanupFunction(async function () {
+ Services.prefs.clearUserPref("dom.security.https_only_mode");
+ Services.prefs.clearUserPref("dom.security.https_only_mode_pbm");
+ });
+
+ await SpecialPowers.setBoolPref("dom.security.https_only_mode", false);
+ await SpecialPowers.setBoolPref("dom.security.https_only_mode_pbm", true);
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ const doc = gBrowser.selectedBrowser.contentDocument;
+ const radioGroup = doc.getElementById("httpsOnlyRadioGroup");
+ const enableAllRadio = doc.getElementById("httpsOnlyRadioEnabled");
+ const enablePbmRadio = doc.getElementById("httpsOnlyRadioEnabledPBM");
+ const disableRadio = doc.getElementById("httpsOnlyRadioDisabled");
+
+ // Check if UI
+ check(radioGroup, HTTPS_ONLY_PBM_ONLY);
+
+ // Check if UI updated on pref-change
+ await SpecialPowers.setBoolPref("dom.security.https_only_mode_pbm", false);
+ check(radioGroup, HTTPS_ONLY_DISABLED);
+
+ // Check if prefs change if clicked on radio button
+ enableAllRadio.click();
+ check(radioGroup, HTTPS_ONLY_ENABLED);
+
+ // Check if prefs stay the same if clicked on same
+ // radio button again (see bug 1671122)
+ enableAllRadio.click();
+ check(radioGroup, HTTPS_ONLY_ENABLED);
+
+ // Check if prefs are set correctly for PBM-only mode.
+ enablePbmRadio.click();
+ check(radioGroup, HTTPS_ONLY_PBM_ONLY);
+
+ // Check if prefs are set correctly when disabled again.
+ disableRadio.click();
+ check(radioGroup, HTTPS_ONLY_DISABLED);
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+function check(radioGroupElement, expectedValue) {
+ is(
+ radioGroupElement.value,
+ expectedValue,
+ "Radio Group value should match expected value"
+ );
+ is(
+ SpecialPowers.getBoolPref("dom.security.https_only_mode"),
+ expectedValue === HTTPS_ONLY_ENABLED,
+ "HTTPS-Only pref should match expected value."
+ );
+ is(
+ SpecialPowers.getBoolPref("dom.security.https_only_mode_pbm"),
+ expectedValue === HTTPS_ONLY_PBM_ONLY,
+ "HTTPS-Only PBM pref should match expected value."
+ );
+}
diff --git a/browser/components/preferences/tests/browser_ignore_invalid_capability.js b/browser/components/preferences/tests/browser_ignore_invalid_capability.js
new file mode 100644
index 0000000000..35b2679ffc
--- /dev/null
+++ b/browser/components/preferences/tests/browser_ignore_invalid_capability.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function testInvalidCapabilityIgnored() {
+ info(
+ "Test to make sure that invalid combinations of type and capability are ignored \
+ so the cookieExceptions management popup does not crash"
+ );
+ PermissionTestUtils.add(
+ "https://mozilla.org",
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_ALLOW
+ );
+
+ // This is an invalid combination of type & capability and should be ignored
+ PermissionTestUtils.add(
+ "https://foobar.org",
+ "cookie",
+ Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ let doc = gBrowser.contentDocument;
+ let promiseSubDialogLoaded = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/permissions.xhtml"
+ );
+ doc.getElementById("cookieExceptions").doCommand();
+
+ let win = await promiseSubDialogLoaded;
+ doc = win.document;
+
+ is(
+ doc.getElementById("permissionsBox").itemCount,
+ 1,
+ "We only display the permission that is valid for the type cookie"
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_languages_subdialog.js b/browser/components/preferences/tests/browser_languages_subdialog.js
new file mode 100644
index 0000000000..e85ce44ca3
--- /dev/null
+++ b/browser/components/preferences/tests/browser_languages_subdialog.js
@@ -0,0 +1,139 @@
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+ const contentDocument = gBrowser.contentDocument;
+ let dialogOverlay = content.gSubDialog._preloadDialog._overlay;
+
+ async function languagesSubdialogOpened() {
+ const promiseSubDialogLoaded = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/languages.xhtml"
+ );
+ contentDocument.getElementById("chooseLanguage").click();
+ const win = await promiseSubDialogLoaded;
+ dialogOverlay = content.gSubDialog._topDialog._overlay;
+ ok(!BrowserTestUtils.is_hidden(dialogOverlay), "The dialog is visible.");
+ return win;
+ }
+
+ function acceptLanguagesSubdialog(win) {
+ const button = win.document.querySelector("dialog").getButton("accept");
+ button.doCommand();
+ }
+
+ ok(BrowserTestUtils.is_hidden(dialogOverlay), "The dialog is invisible.");
+ let win = await languagesSubdialogOpened();
+ ok(
+ win.document.getElementById("spoofEnglish").hidden,
+ "The 'Request English' checkbox is hidden."
+ );
+ acceptLanguagesSubdialog(win);
+ ok(BrowserTestUtils.is_hidden(dialogOverlay), "The dialog is invisible.");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["intl.accept_languages", "en-US,en-XX,foo"]],
+ });
+ win = await languagesSubdialogOpened();
+ let activeLanguages = win.document.getElementById("activeLanguages").children;
+ ok(
+ activeLanguages[0].id == "en-us",
+ "The ID for 'en-US' locale code is correctly set."
+ );
+ ok(
+ activeLanguages[0].firstChild.value == "English (United States) [en-us]",
+ "The name for known 'en-US' locale code is correctly resolved."
+ );
+ ok(
+ activeLanguages[1].id == "en-xx",
+ "The ID for 'en-XX' locale code is correctly set."
+ );
+ ok(
+ activeLanguages[1].firstChild.value == "English [en-xx]",
+ "The name for unknown 'en-XX' locale code is resolved using 'en'."
+ );
+ ok(
+ activeLanguages[2].firstChild.value == " [foo]",
+ "The name for unknown 'foo' locale code is empty."
+ );
+ acceptLanguagesSubdialog(win);
+ await SpecialPowers.popPrefEnv();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ ["privacy.spoof_english", 0],
+ ],
+ });
+
+ win = await languagesSubdialogOpened();
+ ok(
+ !win.document.getElementById("spoofEnglish").hidden,
+ "The 'Request English' checkbox isn't hidden."
+ );
+ ok(
+ !win.document.getElementById("spoofEnglish").checked,
+ "The 'Request English' checkbox isn't checked."
+ );
+ is(
+ win.Preferences.get("privacy.spoof_english").value,
+ 0,
+ "The privacy.spoof_english pref is set to 0."
+ );
+
+ win.document.getElementById("spoofEnglish").checked = true;
+ win.document.getElementById("spoofEnglish").doCommand();
+ ok(
+ win.document.getElementById("spoofEnglish").checked,
+ "The 'Request English' checkbox is checked."
+ );
+ is(
+ win.Preferences.get("privacy.spoof_english").value,
+ 2,
+ "The privacy.spoof_english pref is set to 2."
+ );
+ acceptLanguagesSubdialog(win);
+
+ win = await languagesSubdialogOpened();
+ ok(
+ !win.document.getElementById("spoofEnglish").hidden,
+ "The 'Request English' checkbox isn't hidden."
+ );
+ ok(
+ win.document.getElementById("spoofEnglish").checked,
+ "The 'Request English' checkbox is checked."
+ );
+ is(
+ win.Preferences.get("privacy.spoof_english").value,
+ 2,
+ "The privacy.spoof_english pref is set to 2."
+ );
+
+ win.document.getElementById("spoofEnglish").checked = false;
+ win.document.getElementById("spoofEnglish").doCommand();
+ ok(
+ !win.document.getElementById("spoofEnglish").checked,
+ "The 'Request English' checkbox isn't checked."
+ );
+ is(
+ win.Preferences.get("privacy.spoof_english").value,
+ 1,
+ "The privacy.spoof_english pref is set to 1."
+ );
+ acceptLanguagesSubdialog(win);
+
+ win = await languagesSubdialogOpened();
+ ok(
+ !win.document.getElementById("spoofEnglish").hidden,
+ "The 'Request English' checkbox isn't hidden."
+ );
+ ok(
+ !win.document.getElementById("spoofEnglish").checked,
+ "The 'Request English' checkbox isn't checked."
+ );
+ is(
+ win.Preferences.get("privacy.spoof_english").value,
+ 1,
+ "The privacy.spoof_english pref is set to 1."
+ );
+ acceptLanguagesSubdialog(win);
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_layersacceleration.js b/browser/components/preferences/tests/browser_layersacceleration.js
new file mode 100644
index 0000000000..982a32c94d
--- /dev/null
+++ b/browser/components/preferences/tests/browser_layersacceleration.js
@@ -0,0 +1,36 @@
+add_task(async function () {
+ // We must temporarily disable `Once` StaticPrefs check for the duration of
+ // this test (see bug 1556131). We must do so in a separate operation as
+ // pushPrefEnv doesn't set the preferences in the order one could expect.
+ await SpecialPowers.pushPrefEnv({
+ set: [["preferences.force-disable.check.once.policy", true]],
+ });
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["gfx.direct2d.disabled", false],
+ ["layers.acceleration.disabled", false],
+ ],
+ });
+
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ let checkbox = doc.querySelector("#allowHWAccel");
+ is(
+ !checkbox.checked,
+ Services.prefs.getBoolPref("layers.acceleration.disabled"),
+ "checkbox should represent inverted pref value before clicking on checkbox"
+ );
+
+ checkbox.click();
+
+ is(
+ !checkbox.checked,
+ Services.prefs.getBoolPref("layers.acceleration.disabled"),
+ "checkbox should represent inverted pref value after clicking on checkbox"
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_localSearchShortcuts.js b/browser/components/preferences/tests/browser_localSearchShortcuts.js
new file mode 100644
index 0000000000..0b8e170cc1
--- /dev/null
+++ b/browser/components/preferences/tests/browser_localSearchShortcuts.js
@@ -0,0 +1,309 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Checks the local shortcut rows in the engines list of the search pane.
+ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
+ UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
+});
+
+let gTree;
+
+add_setup(async function () {
+ let prefs = await openPreferencesViaOpenPreferencesAPI("search", {
+ leaveOpen: true,
+ });
+ registerCleanupFunction(() => {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+
+ Assert.equal(
+ prefs.selectedPane,
+ "paneSearch",
+ "Sanity check: Search pane is selected by default"
+ );
+
+ gTree = gBrowser.contentDocument.querySelector("#engineList");
+ gTree.scrollIntoView();
+ gTree.focus();
+});
+
+// The rows should be visible and checked by default.
+add_task(async function visible() {
+ await checkRowVisibility(true);
+ await forEachLocalShortcutRow(async (row, shortcut) => {
+ Assert.equal(
+ gTree.view.getCellValue(row, gTree.columns.getNamedColumn("engineShown")),
+ "true",
+ "Row is checked initially"
+ );
+ });
+});
+
+// Toggling the browser.urlbar.shortcuts.* prefs should toggle the corresponding
+// checkboxes in the rows.
+add_task(async function syncFromPrefs() {
+ let col = gTree.columns.getNamedColumn("engineShown");
+ await forEachLocalShortcutRow(async (row, shortcut) => {
+ Assert.equal(
+ gTree.view.getCellValue(row, col),
+ "true",
+ "Row is checked initially"
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [[getUrlbarPrefName(shortcut.pref), false]],
+ });
+ Assert.equal(
+ gTree.view.getCellValue(row, col),
+ "false",
+ "Row is unchecked after disabling pref"
+ );
+ await SpecialPowers.popPrefEnv();
+ Assert.equal(
+ gTree.view.getCellValue(row, col),
+ "true",
+ "Row is checked after re-enabling pref"
+ );
+ });
+});
+
+// Pressing the space key while a row is selected should toggle its checkbox
+// and pref.
+add_task(async function syncToPrefs_spaceKey() {
+ let col = gTree.columns.getNamedColumn("engineShown");
+ await forEachLocalShortcutRow(async (row, shortcut) => {
+ Assert.ok(
+ UrlbarPrefs.get(shortcut.pref),
+ "Sanity check: Pref is enabled initially"
+ );
+ Assert.equal(
+ gTree.view.getCellValue(row, col),
+ "true",
+ "Row is checked initially"
+ );
+ gTree.view.selection.select(row);
+ EventUtils.synthesizeKey(" ", {}, gTree.ownerGlobal);
+ Assert.ok(
+ !UrlbarPrefs.get(shortcut.pref),
+ "Pref is disabled after pressing space key"
+ );
+ Assert.equal(
+ gTree.view.getCellValue(row, col),
+ "false",
+ "Row is unchecked after pressing space key"
+ );
+ Services.prefs.clearUserPref(getUrlbarPrefName(shortcut.pref));
+ });
+});
+
+// Clicking the checkbox in a local shortcut row should toggle the checkbox and
+// pref.
+add_task(async function syncToPrefs_click() {
+ let col = gTree.columns.getNamedColumn("engineShown");
+ await forEachLocalShortcutRow(async (row, shortcut) => {
+ Assert.ok(
+ UrlbarPrefs.get(shortcut.pref),
+ "Sanity check: Pref is enabled initially"
+ );
+ Assert.equal(
+ gTree.view.getCellValue(row, col),
+ "true",
+ "Row is checked initially"
+ );
+
+ let rect = gTree.getCoordsForCellItem(row, col, "cell");
+ let x = rect.x + rect.width / 2;
+ let y = rect.y + rect.height / 2;
+ EventUtils.synthesizeMouse(gTree.body, x, y, {}, gTree.ownerGlobal);
+
+ Assert.ok(
+ !UrlbarPrefs.get(shortcut.pref),
+ "Pref is disabled after clicking checkbox"
+ );
+ Assert.equal(
+ gTree.view.getCellValue(row, col),
+ "false",
+ "Row is unchecked after clicking checkbox"
+ );
+ Services.prefs.clearUserPref(getUrlbarPrefName(shortcut.pref));
+ });
+});
+
+// The keyword column should not be editable according to isEditable().
+add_task(async function keywordNotEditable_isEditable() {
+ await forEachLocalShortcutRow(async (row, shortcut) => {
+ Assert.ok(
+ !gTree.view.isEditable(
+ row,
+ gTree.columns.getNamedColumn("engineKeyword")
+ ),
+ "Keyword column is not editable"
+ );
+ });
+});
+
+// Pressing the enter key while a row is selected shouldn't allow the keyword to
+// be edited.
+add_task(async function keywordNotEditable_enterKey() {
+ let col = gTree.columns.getNamedColumn("engineKeyword");
+ await forEachLocalShortcutRow(async (row, shortcut) => {
+ Assert.ok(
+ shortcut.restrict,
+ "Sanity check: Shortcut restriction char is non-empty"
+ );
+ Assert.equal(
+ gTree.view.getCellText(row, col),
+ shortcut.restrict,
+ "Sanity check: Keyword column has correct restriction char initially"
+ );
+
+ gTree.view.selection.select(row);
+ EventUtils.synthesizeKey("KEY_Enter", {}, gTree.ownerGlobal);
+ EventUtils.sendString("newkeyword");
+ EventUtils.synthesizeKey("KEY_Enter", {}, gTree.ownerGlobal);
+
+ // Wait a moment to allow for any possible asynchronicity.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 500));
+
+ Assert.equal(
+ gTree.view.getCellText(row, col),
+ shortcut.restrict,
+ "Keyword column is still restriction char"
+ );
+ });
+});
+
+// Double-clicking the keyword column shouldn't allow the keyword to be edited.
+add_task(async function keywordNotEditable_click() {
+ let col = gTree.columns.getNamedColumn("engineKeyword");
+ await forEachLocalShortcutRow(async (row, shortcut) => {
+ Assert.ok(
+ shortcut.restrict,
+ "Sanity check: Shortcut restriction char is non-empty"
+ );
+ Assert.equal(
+ gTree.view.getCellText(row, col),
+ shortcut.restrict,
+ "Sanity check: Keyword column has correct restriction char initially"
+ );
+
+ let rect = gTree.getCoordsForCellItem(row, col, "text");
+ let x = rect.x + rect.width / 2;
+ let y = rect.y + rect.height / 2;
+
+ let promise = BrowserTestUtils.waitForEvent(gTree, "dblclick");
+
+ // Click once to select the row.
+ EventUtils.synthesizeMouse(
+ gTree.body,
+ x,
+ y,
+ { clickCount: 1 },
+ gTree.ownerGlobal
+ );
+
+ // Now double-click the keyword column.
+ EventUtils.synthesizeMouse(
+ gTree.body,
+ x,
+ y,
+ { clickCount: 2 },
+ gTree.ownerGlobal
+ );
+
+ await promise;
+
+ EventUtils.sendString("newkeyword");
+ EventUtils.synthesizeKey("KEY_Enter", {}, gTree.ownerGlobal);
+
+ // Wait a moment to allow for any possible asynchronicity.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 500));
+
+ Assert.equal(
+ gTree.view.getCellText(row, col),
+ shortcut.restrict,
+ "Keyword column is still restriction char"
+ );
+ });
+});
+
+/**
+ * Asserts that the engine and local shortcut rows are present in the tree.
+ */
+async function checkRowVisibility() {
+ let engines = await Services.search.getVisibleEngines();
+
+ Assert.equal(
+ gTree.view.rowCount,
+ engines.length + UrlbarUtils.LOCAL_SEARCH_MODES.length,
+ "Expected number of tree rows"
+ );
+
+ // Check the engine rows.
+ for (let row = 0; row < engines.length; row++) {
+ let engine = engines[row];
+ let text = gTree.view.getCellText(
+ row,
+ gTree.columns.getNamedColumn("engineName")
+ );
+ Assert.equal(
+ text,
+ engine.name,
+ `Sanity check: Tree row ${row} has expected engine name`
+ );
+ }
+
+ // Check the shortcut rows.
+ await forEachLocalShortcutRow(async (row, shortcut) => {
+ let text = gTree.view.getCellText(
+ row,
+ gTree.columns.getNamedColumn("engineName")
+ );
+ let name = UrlbarUtils.getResultSourceName(shortcut.source);
+ let l10nName = await gTree.ownerDocument.l10n.formatValue(
+ `urlbar-search-mode-${name}`
+ );
+ Assert.ok(l10nName, "Sanity check: l10n name is non-empty");
+ Assert.equal(text, l10nName, `Tree row ${row} has expected shortcut name`);
+ });
+}
+
+/**
+ * Calls a callback for each local shortcut row in the tree.
+ *
+ * @param {function} callback
+ * Called for each local shortcut row like: callback(rowIndex, shortcutObject)
+ */
+async function forEachLocalShortcutRow(callback) {
+ let engines = await Services.search.getVisibleEngines();
+ for (let i = 0; i < UrlbarUtils.LOCAL_SEARCH_MODES.length; i++) {
+ let shortcut = UrlbarUtils.LOCAL_SEARCH_MODES[i];
+ let row = engines.length + i;
+ // These tests assume LOCAL_SEARCH_MODES are enabled, this can be removed
+ // when we enable QuickActions. We cant just enable the pref in browser.ini
+ // as this test calls clearUserPref.
+ if (shortcut.pref == "shortcuts.quickactions") {
+ continue;
+ }
+ await callback(row, shortcut);
+ }
+}
+
+/**
+ * Prepends the `browser.urlbar.` branch to the given relative pref.
+ *
+ * @param {string} relativePref
+ * A pref name relative to the `browser.urlbar.`.
+ * @returns {string}
+ * The full pref name with `browser.urlbar.` prepended.
+ */
+function getUrlbarPrefName(relativePref) {
+ return `browser.urlbar.${relativePref}`;
+}
diff --git a/browser/components/preferences/tests/browser_moreFromMozilla.js b/browser/components/preferences/tests/browser_moreFromMozilla.js
new file mode 100644
index 0000000000..0c9b6e2b88
--- /dev/null
+++ b/browser/components/preferences/tests/browser_moreFromMozilla.js
@@ -0,0 +1,380 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { EnterprisePolicyTesting } = ChromeUtils.importESModule(
+ "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
+);
+
+let { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+async function clearPolicies() {
+ // Ensure no active policies are set
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson("");
+}
+
+// The Relay promo is only shown if the default FxA instance is detected, and
+// tests override it to a dummy address, so we need to make the dummy address
+// appear like it's the default (using the actual default instance might cause a
+// remote connection, crashing the test harness).
+add_setup(mockDefaultFxAInstance);
+
+add_task(async function testDefaultUIWithoutTemplatePref() {
+ await clearPolicies();
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ let doc = gBrowser.contentDocument;
+ let tab = gBrowser.selectedTab;
+
+ let moreFromMozillaCategory = doc.getElementById(
+ "category-more-from-mozilla"
+ );
+ ok(moreFromMozillaCategory, "The category exists");
+ ok(!moreFromMozillaCategory.hidden, "The category is not hidden");
+
+ moreFromMozillaCategory.click();
+
+ let productCards = doc.querySelectorAll(".mozilla-product-item.simple");
+ Assert.ok(productCards, "Default UI uses simple template");
+ Assert.equal(productCards.length, 3, "3 product cards displayed");
+
+ const expectedUrl = "https://www.mozilla.org/firefox/browsers/mobile/";
+ let tabOpened = BrowserTestUtils.waitForNewTab(gBrowser, url =>
+ url.startsWith(expectedUrl)
+ );
+ let mobileLink = doc.getElementById("default-fxMobile");
+ mobileLink.click();
+ let openedTab = await tabOpened;
+ Assert.ok(gBrowser.selectedBrowser.documentURI.spec.startsWith(expectedUrl));
+
+ let searchParams = new URL(gBrowser.selectedBrowser.documentURI.spec)
+ .searchParams;
+ Assert.equal(
+ searchParams.get("utm_source"),
+ "about-prefs",
+ "expected utm_source sent"
+ );
+ Assert.equal(
+ searchParams.get("utm_campaign"),
+ "morefrommozilla",
+ "utm_campaign set"
+ );
+ Assert.equal(
+ searchParams.get("utm_medium"),
+ "firefox-desktop",
+ "utm_medium set"
+ );
+ Assert.equal(
+ searchParams.get("utm_content"),
+ "default-global",
+ "default utm_content set"
+ );
+ Assert.ok(
+ !searchParams.has("entrypoint_variation"),
+ "entrypoint_variation should not be set"
+ );
+ Assert.ok(
+ !searchParams.has("entrypoint_experiment"),
+ "entrypoint_experiment should not be set"
+ );
+ BrowserTestUtils.removeTab(openedTab);
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testDefaulEmailClick() {
+ await clearPolicies();
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ let doc = gBrowser.contentDocument;
+ let tab = gBrowser.selectedTab;
+
+ let moreFromMozillaCategory = doc.getElementById(
+ "category-more-from-mozilla"
+ );
+ moreFromMozillaCategory.click();
+
+ const expectedUrl = "https://www.mozilla.org/firefox/mobile/get-app/?v=mfm";
+ let sendEmailLink = doc.getElementById("default-qr-code-send-email");
+
+ Assert.ok(
+ sendEmailLink.href.startsWith(expectedUrl),
+ `Expected URL ${sendEmailLink.href}`
+ );
+
+ let searchParams = new URL(sendEmailLink.href).searchParams;
+ Assert.equal(searchParams.get("v"), "mfm", "expected send email param set");
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Test that we don't show moreFromMozilla pane when it's disabled.
+ */
+add_task(async function testwhenPrefDisabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.moreFromMozilla", false]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ let doc = gBrowser.contentDocument;
+
+ let moreFromMozillaCategory = doc.getElementById(
+ "category-more-from-mozilla"
+ );
+ ok(moreFromMozillaCategory, "The category exists");
+ ok(moreFromMozillaCategory.hidden, "The category is hidden");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_aboutpreferences_event_telemetry() {
+ Services.telemetry.clearEvents();
+ Services.telemetry.setEventRecordingEnabled("aboutpreferences", true);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.moreFromMozilla", true]],
+ });
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let moreFromMozillaCategory = doc.getElementById(
+ "category-more-from-mozilla"
+ );
+
+ let clickedPromise = BrowserTestUtils.waitForEvent(
+ moreFromMozillaCategory,
+ "click"
+ );
+ moreFromMozillaCategory.click();
+ await clickedPromise;
+
+ TelemetryTestUtils.assertEvents(
+ [["aboutpreferences", "show", "initial", "paneGeneral"]],
+ { category: "aboutpreferences", method: "show", object: "initial" },
+ { clear: false }
+ );
+ TelemetryTestUtils.assertEvents(
+ [["aboutpreferences", "show", "click", "paneMoreFromMozilla"]],
+ { category: "aboutpreferences", method: "show", object: "click" },
+ { clear: false }
+ );
+ TelemetryTestUtils.assertNumberOfEvents(2);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_aboutpreferences_simple_template() {
+ await clearPolicies();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.preferences.moreFromMozilla", true],
+ ["browser.preferences.moreFromMozilla.template", "simple"],
+ ],
+ });
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let moreFromMozillaCategory = doc.getElementById(
+ "category-more-from-mozilla"
+ );
+
+ moreFromMozillaCategory.click();
+
+ let productCards = doc.querySelectorAll(".mozilla-product-item");
+ Assert.ok(productCards, "The product cards from simple template found");
+ Assert.equal(productCards.length, 3, "3 product cards displayed");
+
+ let qrCodeButtons = doc.querySelectorAll('.qr-code-box[hidden="false"]');
+ Assert.equal(qrCodeButtons.length, 1, "1 qr-code box displayed");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_aboutpreferences_clickBtnVPN() {
+ await clearPolicies();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.preferences.moreFromMozilla", true],
+ ["browser.preferences.moreFromMozilla.template", "simple"],
+ ],
+ });
+ await openPreferencesViaOpenPreferencesAPI("paneMoreFromMozilla", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let tab = gBrowser.selectedTab;
+
+ let productCards = doc.querySelectorAll(".mozilla-product-item.simple");
+ Assert.ok(productCards, "Simple template loaded");
+
+ const expectedUrl = "https://www.mozilla.org/products/vpn/";
+ let tabOpened = BrowserTestUtils.waitForNewTab(gBrowser, url =>
+ url.startsWith(expectedUrl)
+ );
+
+ let vpnButton = doc.getElementById("simple-mozillaVPN");
+ vpnButton.click();
+
+ let openedTab = await tabOpened;
+ Assert.ok(gBrowser.selectedBrowser.documentURI.spec.startsWith(expectedUrl));
+
+ let searchParams = new URL(gBrowser.selectedBrowser.documentURI.spec)
+ .searchParams;
+ Assert.equal(
+ searchParams.get("utm_source"),
+ "about-prefs",
+ "expected utm_source sent"
+ );
+ Assert.equal(
+ searchParams.get("utm_campaign"),
+ "morefrommozilla",
+ "utm_campaign set"
+ );
+ Assert.equal(
+ searchParams.get("utm_medium"),
+ "firefox-desktop",
+ "utm_medium set"
+ );
+ Assert.equal(
+ searchParams.get("utm_content"),
+ "fxvt-113-a-global",
+ "utm_content set"
+ );
+ Assert.equal(
+ searchParams.get("entrypoint_experiment"),
+ "morefrommozilla-experiment-1846",
+ "entrypoint_experiment set"
+ );
+ Assert.equal(
+ searchParams.get("entrypoint_variation"),
+ "treatment-simple",
+ "entrypoint_variation set"
+ );
+ BrowserTestUtils.removeTab(openedTab);
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_aboutpreferences_clickBtnMobile() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.preferences.moreFromMozilla", true],
+ ["browser.preferences.moreFromMozilla.template", "simple"],
+ ],
+ });
+ await openPreferencesViaOpenPreferencesAPI("paneMoreFromMozilla", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let tab = gBrowser.selectedTab;
+
+ let productCards = doc.querySelectorAll("vbox.simple");
+ Assert.ok(productCards, "Simple template loaded");
+
+ const expectedUrl = "https://www.mozilla.org/firefox/browsers/mobile/";
+
+ let mobileUrl = new URL(doc.getElementById("simple-fxMobile").href);
+
+ Assert.ok(mobileUrl.href.startsWith(expectedUrl));
+
+ let searchParams = mobileUrl.searchParams;
+ Assert.equal(
+ searchParams.get("utm_source"),
+ "about-prefs",
+ "expected utm_source sent"
+ );
+ Assert.equal(
+ searchParams.get("utm_campaign"),
+ "morefrommozilla",
+ "utm_campaign set"
+ );
+ Assert.equal(
+ searchParams.get("utm_medium"),
+ "firefox-desktop",
+ "utm_medium set"
+ );
+ Assert.equal(
+ searchParams.get("utm_content"),
+ "fxvt-113-a-global",
+ "default-global",
+ "utm_content set"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_aboutpreferences_search() {
+ await clearPolicies();
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.moreFromMozilla", true]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI(null, {
+ leaveOpen: true,
+ });
+
+ await runSearchInput("Relay");
+
+ let doc = gBrowser.contentDocument;
+ let tab = gBrowser.selectedTab;
+
+ let productCards = doc.querySelectorAll(".mozilla-product-item");
+ Assert.equal(productCards.length, 3, "All products in the group are found");
+ let [mobile, vpn, relay] = productCards;
+ Assert.ok(BrowserTestUtils.is_hidden(mobile), "Mobile hidden");
+ Assert.ok(BrowserTestUtils.is_hidden(vpn), "VPN hidden");
+ Assert.ok(BrowserTestUtils.is_visible(relay), "Relay shown");
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_aboutpreferences_clickBtnRelay() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.moreFromMozilla", true]],
+ });
+ await openPreferencesViaOpenPreferencesAPI("paneMoreFromMozilla", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let tab = gBrowser.selectedTab;
+
+ let expectedUrl = new URL("https://relay.firefox.com");
+ expectedUrl.searchParams.set("utm_source", "about-prefs");
+ expectedUrl.searchParams.set("utm_campaign", "morefrommozilla");
+ expectedUrl.searchParams.set("utm_medium", "firefox-desktop");
+ expectedUrl.searchParams.set("utm_content", "fxvt-113-a-global");
+ expectedUrl.searchParams.set(
+ "entrypoint_experiment",
+ "morefrommozilla-experiment-1846"
+ );
+ expectedUrl.searchParams.set("entrypoint_variation", "treatment-simple");
+
+ let tabOpened = BrowserTestUtils.waitForDocLoadAndStopIt(
+ expectedUrl.toString(),
+ gBrowser,
+ channel => {
+ Assert.equal(
+ channel.originalURI.spec,
+ expectedUrl.toString(),
+ "URL matched"
+ );
+ return true;
+ }
+ );
+ doc.getElementById("simple-firefoxRelay").click();
+
+ await tabOpened;
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/preferences/tests/browser_moreFromMozilla_locales.js b/browser/components/preferences/tests/browser_moreFromMozilla_locales.js
new file mode 100644
index 0000000000..404e22b3ea
--- /dev/null
+++ b/browser/components/preferences/tests/browser_moreFromMozilla_locales.js
@@ -0,0 +1,331 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { EnterprisePolicyTesting } = ChromeUtils.importESModule(
+ "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
+);
+
+let { Region } = ChromeUtils.importESModule(
+ "resource://gre/modules/Region.sys.mjs"
+);
+
+const initialHomeRegion = Region._home;
+const initialCurrentRegion = Region._current;
+
+// Helper to run tests for specific regions
+async function setupRegions(home, current) {
+ Region._setHomeRegion(home || "");
+ Region._setCurrentRegion(current || "");
+}
+
+function setLocale(language) {
+ Services.locale.availableLocales = [language];
+ Services.locale.requestedLocales = [language];
+}
+
+async function clearPolicies() {
+ // Ensure no active policies are set
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson("");
+}
+
+async function getPromoCards() {
+ await openPreferencesViaOpenPreferencesAPI("paneMoreFromMozilla", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let vpnPromoCard = doc.getElementById("mozilla-vpn");
+ let mobileCard = doc.getElementById("firefox-mobile");
+ let relayPromoCard = doc.getElementById("firefox-relay");
+
+ return {
+ vpnPromoCard,
+ mobileCard,
+ relayPromoCard,
+ };
+}
+
+let mockFxA, unmockFxA;
+
+// The Relay promo is only shown if the default FxA instance is detected, and
+// tests override it to a dummy address, so we need to make the dummy address
+// appear like it's the default (using the actual default instance might cause a
+// remote connection, crashing the test harness).
+add_setup(async function () {
+ let { mock, unmock } = await mockDefaultFxAInstance();
+ mockFxA = mock;
+ unmockFxA = unmock;
+});
+
+add_task(async function test_VPN_promo_enabled() {
+ await clearPolicies();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.preferences.moreFromMozilla", true],
+ ["browser.vpn_promo.enabled", true],
+ ],
+ });
+
+ let { vpnPromoCard, mobileCard } = await getPromoCards();
+
+ ok(vpnPromoCard, "The VPN promo is visible");
+ ok(mobileCard, "The Mobile promo is visible");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_VPN_promo_disabled() {
+ await clearPolicies();
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.vpn_promo.enabled", false]],
+ });
+
+ let { vpnPromoCard, mobileCard } = await getPromoCards();
+
+ ok(!vpnPromoCard, "The VPN promo is not visible");
+ ok(mobileCard, "The Mobile promo is visible");
+
+ Services.prefs.clearUserPref("browser.vpn_promo.enabled");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_VPN_promo_in_disallowed_home_region() {
+ await clearPolicies();
+ const disallowedRegion = "SY";
+
+ setupRegions(disallowedRegion);
+
+ // Promo should not show in disallowed regions even when vpn_promo pref is enabled
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.vpn_promo.enabled", true]],
+ });
+
+ let { vpnPromoCard, mobileCard } = await getPromoCards();
+
+ ok(!vpnPromoCard, "The VPN promo is not visible");
+ ok(mobileCard, "The Mobile promo is visible");
+
+ setupRegions(initialHomeRegion, initialCurrentRegion); // revert changes to regions
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_VPN_promo_in_illegal_home_region() {
+ await clearPolicies();
+ const illegalRegion = "CN";
+
+ setupRegions(illegalRegion);
+
+ // Promo should not show in illegal regions even if the list of disallowed regions is somehow altered (though changing this preference is blocked)
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.vpn_promo.disallowedRegions", "SY, CU"]],
+ });
+
+ let { vpnPromoCard, mobileCard } = await getPromoCards();
+
+ ok(!vpnPromoCard, "The VPN promo is not visible");
+ ok(mobileCard, "The Mobile promo is visible");
+
+ setupRegions(initialHomeRegion, initialCurrentRegion); // revert changes to regions
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_VPN_promo_in_disallowed_current_region() {
+ await clearPolicies();
+ const allowedRegion = "US";
+ const disallowedRegion = "SY";
+
+ setupRegions(allowedRegion, disallowedRegion);
+
+ // Promo should not show in disallowed regions even when vpn_promo pref is enabled
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.vpn_promo.enabled", true]],
+ });
+
+ let { vpnPromoCard, mobileCard } = await getPromoCards();
+
+ ok(!vpnPromoCard, "The VPN promo is not visible");
+ ok(mobileCard, "The Mobile promo is visible");
+
+ setupRegions(initialHomeRegion, initialCurrentRegion); // revert changes to regions
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_VPN_promo_in_illegal_current_region() {
+ await clearPolicies();
+ const allowedRegion = "US";
+ const illegalRegion = "CN";
+
+ setupRegions(allowedRegion, illegalRegion);
+
+ // Promo should not show in illegal regions even if the list of disallowed regions is somehow altered (though changing this preference is blocked)
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.vpn_promo.disallowedRegions", "SY, CU"]],
+ });
+
+ let { vpnPromoCard, mobileCard } = await getPromoCards();
+
+ ok(!vpnPromoCard, "The VPN promo is not visible");
+ ok(mobileCard, "The Mobile promo is visible");
+
+ setupRegions(initialHomeRegion, initialCurrentRegion); // revert changes to regions
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_aboutpreferences_partnerCNRepack() {
+ let defaultBranch = Services.prefs.getDefaultBranch(null);
+ defaultBranch.setCharPref("distribution.id", "MozillaOnline");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.preferences.moreFromMozilla", true],
+ ["browser.preferences.moreFromMozilla.template", "simple"],
+ ],
+ });
+ await openPreferencesViaOpenPreferencesAPI("paneMoreFromMozilla", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let tab = gBrowser.selectedTab;
+
+ let productCards = doc.querySelectorAll("vbox.simple");
+ Assert.ok(productCards, "Simple template loaded");
+
+ const expectedUrl = "https://www.firefox.com.cn/browsers/mobile/";
+
+ let link = doc.getElementById("simple-fxMobile");
+ Assert.ok(link.getAttribute("href").startsWith(expectedUrl));
+
+ defaultBranch.setCharPref("distribution.id", "");
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_send_to_device_email_link_for_supported_locale() {
+ // Email is supported for Brazilian Portuguese
+ const supportedLocale = "pt-BR";
+ const initialLocale = Services.locale.appLocaleAsBCP47;
+ setLocale(supportedLocale);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.moreFromMozilla.template", "simple"]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("paneMoreFromMozilla", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let emailLink = doc.getElementById("simple-qr-code-send-email");
+
+ ok(!BrowserTestUtils.is_hidden(emailLink), "Email link should be visible");
+
+ await SpecialPowers.popPrefEnv();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ setLocale(initialLocale); // revert changes to language
+});
+
+add_task(
+ async function test_send_to_device_email_link_for_unsupported_locale() {
+ // Email is not supported for Afrikaans
+ const unsupportedLocale = "af";
+ const initialLocale = Services.locale.appLocaleAsBCP47;
+ setLocale(unsupportedLocale);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.moreFromMozilla.template", "simple"]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("paneMoreFromMozilla", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let emailLink = doc.getElementById("simple-qr-code-send-email");
+
+ ok(BrowserTestUtils.is_hidden(emailLink), "Email link should be hidden");
+
+ await SpecialPowers.popPrefEnv();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ setLocale(initialLocale); // revert changes to language
+ }
+);
+
+add_task(
+ async function test_VPN_promo_in_unsupported_current_region_with_supported_home_region() {
+ await clearPolicies();
+ const supportedRegion = "US";
+ const unsupportedRegion = "LY";
+
+ setupRegions(supportedRegion, unsupportedRegion);
+
+ let { vpnPromoCard, mobileCard } = await getPromoCards();
+
+ ok(vpnPromoCard, "The VPN promo is visible");
+ ok(mobileCard, "The Mobile promo is visible");
+
+ setupRegions(initialHomeRegion, initialCurrentRegion); // revert changes to regions
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+);
+
+add_task(
+ async function test_VPN_promo_in_supported_current_region_with_unsupported_home_region() {
+ await clearPolicies();
+ const supportedRegion = "US";
+ const unsupportedRegion = "LY";
+
+ setupRegions(unsupportedRegion, supportedRegion);
+
+ let { vpnPromoCard, mobileCard } = await getPromoCards();
+
+ ok(vpnPromoCard, "The VPN promo is visible");
+ ok(mobileCard, "The Mobile promo is visible");
+
+ setupRegions(initialHomeRegion, initialCurrentRegion); // revert changes to regions
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+);
+
+add_task(async function test_VPN_promo_with_active_enterprise_policy() {
+ // set up an arbitrary enterprise policy
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson({
+ policies: {
+ EnableTrackingProtection: {
+ Value: true,
+ },
+ },
+ });
+
+ let { vpnPromoCard, mobileCard } = await getPromoCards();
+ ok(!vpnPromoCard, "The VPN promo is not visible");
+ ok(mobileCard, "The Mobile promo is visible");
+
+ setupRegions(initialHomeRegion, initialCurrentRegion); // revert changes to regions
+ await clearPolicies();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_relay_promo_with_supported_fxa_server() {
+ await clearPolicies();
+
+ let { relayPromoCard } = await getPromoCards();
+ ok(relayPromoCard, "The Relay promo is visible");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_relay_promo_with_unsupported_fxa_server() {
+ await clearPolicies();
+ // Set the default pref value to something other than the current value so it
+ // will appear to be user-set and treated as invalid (actually setting the
+ // pref would cause a remote connection and crash the test harness)
+ unmockFxA();
+
+ let { relayPromoCard } = await getPromoCards();
+ ok(!relayPromoCard, "The Relay promo is not visible");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ mockFxA();
+});
diff --git a/browser/components/preferences/tests/browser_newtab_menu.js b/browser/components/preferences/tests/browser_newtab_menu.js
new file mode 100644
index 0000000000..774c9dd756
--- /dev/null
+++ b/browser/components/preferences/tests/browser_newtab_menu.js
@@ -0,0 +1,38 @@
+add_task(async function newtabPreloaded() {
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+
+ const { contentDocument: doc, contentWindow } = gBrowser;
+ function dispatchMenuItemCommand(menuItem) {
+ const cmdEvent = doc.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent(
+ "command",
+ true,
+ true,
+ contentWindow,
+ 0,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null,
+ 0
+ );
+ menuItem.dispatchEvent(cmdEvent);
+ }
+
+ const menuHome = doc.querySelector(`#newTabMode menuitem[value="0"]`);
+ const menuBlank = doc.querySelector(`#newTabMode menuitem[value="1"]`);
+ ok(menuHome.selected, "The first item, Home (default), is selected.");
+ ok(NewTabPagePreloading.enabled, "Default Home allows preloading.");
+
+ dispatchMenuItemCommand(menuBlank);
+ ok(menuBlank.selected, "The second item, Blank, is selected.");
+ ok(!NewTabPagePreloading.enabled, "Non-Home prevents preloading.");
+
+ dispatchMenuItemCommand(menuHome);
+ ok(menuHome.selected, "The first item, Home, is selected again.");
+ ok(NewTabPagePreloading.enabled, "Default Home allows preloading again.");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_notifications_do_not_disturb.js b/browser/components/preferences/tests/browser_notifications_do_not_disturb.js
new file mode 100644
index 0000000000..afc31b9041
--- /dev/null
+++ b/browser/components/preferences/tests/browser_notifications_do_not_disturb.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+registerCleanupFunction(function () {
+ while (gBrowser.tabs[1]) {
+ gBrowser.removeTab(gBrowser.tabs[1]);
+ }
+});
+
+add_task(async function () {
+ let prefs = await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "panePrivacy", "Privacy pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ let notificationsDoNotDisturbBox = doc.getElementById(
+ "notificationsDoNotDisturbBox"
+ );
+ if (notificationsDoNotDisturbBox.hidden) {
+ todo(false, "Do not disturb is not available on this platform");
+ return;
+ }
+
+ let alertService;
+ try {
+ alertService = Cc["@mozilla.org/alerts-service;1"]
+ .getService(Ci.nsIAlertsService)
+ .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+ } catch (ex) {
+ ok(true, "Do not disturb is not available on this platform: " + ex.message);
+ return;
+ }
+
+ let checkbox = doc.getElementById("notificationsDoNotDisturb");
+ ok(!checkbox.checked, "Checkbox should not be checked by default");
+ ok(
+ !alertService.manualDoNotDisturb,
+ "Do not disturb should be off by default"
+ );
+
+ let checkboxChanged = BrowserTestUtils.waitForEvent(checkbox, "command");
+ checkbox.click();
+ await checkboxChanged;
+ ok(
+ alertService.manualDoNotDisturb,
+ "Do not disturb should be enabled when checked"
+ );
+
+ checkboxChanged = BrowserTestUtils.waitForEvent(checkbox, "command");
+ checkbox.click();
+ await checkboxChanged;
+ ok(
+ !alertService.manualDoNotDisturb,
+ "Do not disturb should be disabled when unchecked"
+ );
+});
diff --git a/browser/components/preferences/tests/browser_open_download_preferences.js b/browser/components/preferences/tests/browser_open_download_preferences.js
new file mode 100644
index 0000000000..794d2ebb05
--- /dev/null
+++ b/browser/components/preferences/tests/browser_open_download_preferences.js
@@ -0,0 +1,288 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { HandlerServiceTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/HandlerServiceTestUtils.sys.mjs"
+);
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+async function selectPdfCategoryItem() {
+ await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+ info("Preferences page opened on the general pane.");
+
+ await gBrowser.selectedBrowser.contentWindow.promiseLoadHandlersList;
+ info("Apps list loaded.");
+
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let container = win.document.getElementById("handlersView");
+ let pdfCategory = container.querySelector(
+ "richlistitem[type='application/pdf']"
+ );
+
+ pdfCategory.closest("richlistbox").selectItem(pdfCategory);
+ Assert.ok(pdfCategory.selected, "Should be able to select our item.");
+
+ return pdfCategory;
+}
+
+async function selectItemInPopup(item, list) {
+ let popup = list.menupopup;
+ let popupShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ // Synthesizing the mouse on the .actionsMenu menulist somehow just selects
+ // the top row. Probably something to do with the multiple layers of anon
+ // content - workaround by using the `.open` setter instead.
+ list.open = true;
+ await popupShown;
+ let popupHidden = BrowserTestUtils.waitForEvent(popup, "popuphidden");
+
+ item.click();
+ popup.hidePopup();
+ await popupHidden;
+ return item;
+}
+
+function downloadHadFinished(publicList) {
+ return new Promise(resolve => {
+ publicList.addView({
+ onDownloadChanged(download) {
+ if (download.succeeded || download.error) {
+ publicList.removeView(this);
+ resolve(download);
+ }
+ },
+ });
+ });
+}
+
+async function removeTheFile(download) {
+ Assert.ok(
+ await IOUtils.exists(download.target.path),
+ "The file should have been downloaded."
+ );
+
+ try {
+ info("removing " + download.target.path);
+ if (Services.appinfo.OS === "WINNT") {
+ // We need to make the file writable to delete it on Windows.
+ await IOUtils.setPermissions(download.target.path, 0o600);
+ }
+ await IOUtils.remove(download.target.path);
+ } catch (ex) {
+ info("The file " + download.target.path + " is not removed, " + ex);
+ }
+}
+
+add_task(async function alwaysAskPreferenceWorks() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.download.always_ask_before_handling_new_types", false],
+ ["browser.download.useDownloadDir", true],
+ ],
+ });
+
+ let pdfCategory = await selectPdfCategoryItem();
+ let list = pdfCategory.querySelector(".actionsMenu");
+
+ let alwaysAskItem = list.querySelector(
+ `menuitem[action='${Ci.nsIHandlerInfo.alwaysAsk}']`
+ );
+
+ await selectItemInPopup(alwaysAskItem, list);
+ Assert.equal(
+ list.selectedItem,
+ alwaysAskItem,
+ "Should have selected 'always ask' for pdf"
+ );
+ let alwaysAskBeforeHandling = HandlerServiceTestUtils.getHandlerInfo(
+ pdfCategory.getAttribute("type")
+ ).alwaysAskBeforeHandling;
+ Assert.ok(
+ alwaysAskBeforeHandling,
+ "Should have turned on 'always asking before handling'"
+ );
+
+ let domWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
+ let loadingTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: TEST_PATH + "empty_pdf_file.pdf",
+ waitForLoad: false,
+ waitForStateStop: true,
+ });
+
+ let domWindow = await domWindowPromise;
+ let dialog = domWindow.document.querySelector("#unknownContentType");
+ let button = dialog.getButton("cancel");
+
+ await TestUtils.waitForCondition(
+ () => !button.disabled,
+ "Wait for Cancel button to get enabled"
+ );
+ Assert.ok(dialog, "Dialog should be shown");
+ dialog.cancelDialog();
+ BrowserTestUtils.removeTab(loadingTab);
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function handleInternallyPreferenceWorks() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.download.always_ask_before_handling_new_types", false],
+ ["browser.download.useDownloadDir", true],
+ ],
+ });
+
+ let pdfCategory = await selectPdfCategoryItem();
+ let list = pdfCategory.querySelector(".actionsMenu");
+
+ let handleInternallyItem = list.querySelector(
+ `menuitem[action='${Ci.nsIHandlerInfo.handleInternally}']`
+ );
+
+ await selectItemInPopup(handleInternallyItem, list);
+ Assert.equal(
+ list.selectedItem,
+ handleInternallyItem,
+ "Should have selected 'handle internally' for pdf"
+ );
+
+ let loadingTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: TEST_PATH + "empty_pdf_file.pdf",
+ waitForLoad: false,
+ waitForStateStop: true,
+ });
+
+ await ContentTask.spawn(loadingTab.linkedBrowser, null, async () => {
+ await ContentTaskUtils.waitForCondition(
+ () => content.document.readyState == "complete"
+ );
+ Assert.ok(
+ content.document.querySelector("div#viewer"),
+ "document content has viewer UI"
+ );
+ });
+
+ BrowserTestUtils.removeTab(loadingTab);
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function saveToDiskPreferenceWorks() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.download.always_ask_before_handling_new_types", false],
+ ["browser.download.useDownloadDir", true],
+ ],
+ });
+
+ let pdfCategory = await selectPdfCategoryItem();
+ let list = pdfCategory.querySelector(".actionsMenu");
+
+ let saveToDiskItem = list.querySelector(
+ `menuitem[action='${Ci.nsIHandlerInfo.saveToDisk}']`
+ );
+
+ await selectItemInPopup(saveToDiskItem, list);
+ Assert.equal(
+ list.selectedItem,
+ saveToDiskItem,
+ "Should have selected 'save to disk' for pdf"
+ );
+
+ let publicList = await Downloads.getList(Downloads.PUBLIC);
+ registerCleanupFunction(async () => {
+ await publicList.removeFinished();
+ });
+
+ let downloadFinishedPromise = downloadHadFinished(publicList);
+
+ let loadingTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: TEST_PATH + "empty_pdf_file.pdf",
+ waitForLoad: false,
+ waitForStateStop: true,
+ });
+
+ let download = await downloadFinishedPromise;
+ BrowserTestUtils.removeTab(loadingTab);
+
+ await removeTheFile(download);
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function useSystemDefaultPreferenceWorks() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.download.always_ask_before_handling_new_types", false],
+ ["browser.download.useDownloadDir", true],
+ ],
+ });
+
+ let pdfCategory = await selectPdfCategoryItem();
+ let list = pdfCategory.querySelector(".actionsMenu");
+
+ let useSystemDefaultItem = list.querySelector(
+ `menuitem[action='${Ci.nsIHandlerInfo.useSystemDefault}']`
+ );
+
+ // Whether there's a "use default" item depends on the OS, there might not be a system default viewer.
+ if (!useSystemDefaultItem) {
+ info(
+ "No 'Use default' item, so no testing for setting 'use system default' preference"
+ );
+ gBrowser.removeCurrentTab();
+ return;
+ }
+
+ await selectItemInPopup(useSystemDefaultItem, list);
+ Assert.equal(
+ list.selectedItem,
+ useSystemDefaultItem,
+ "Should have selected 'use system default' for pdf"
+ );
+
+ let oldLaunchFile = DownloadIntegration.launchFile;
+
+ let waitForLaunchFileCalled = new Promise(resolve => {
+ DownloadIntegration.launchFile = () => {
+ ok(true, "The file should be launched with an external application");
+ resolve();
+ };
+ });
+
+ let publicList = await Downloads.getList(Downloads.PUBLIC);
+ registerCleanupFunction(async () => {
+ await publicList.removeFinished();
+ });
+
+ let downloadFinishedPromise = downloadHadFinished(publicList);
+
+ let loadingTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: TEST_PATH + "empty_pdf_file.pdf",
+ waitForLoad: false,
+ waitForStateStop: true,
+ });
+
+ info("Downloading had finished");
+ let download = await downloadFinishedPromise;
+
+ info("Waiting until DownloadIntegration.launchFile is called");
+ await waitForLaunchFileCalled;
+
+ DownloadIntegration.launchFile = oldLaunchFile;
+
+ await removeTheFile(download);
+
+ BrowserTestUtils.removeTab(loadingTab);
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_open_migration_wizard.js b/browser/components/preferences/tests/browser_open_migration_wizard.js
new file mode 100644
index 0000000000..c2c18b35ef
--- /dev/null
+++ b/browser/components/preferences/tests/browser_open_migration_wizard.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests the "Import Data" button in the "Import Browser Data" section of
+ * the General pane of about:preferences launches the Migration Wizard.
+ */
+add_task(async function test_open_migration_wizard() {
+ const BUTTON_ID = "data-migration";
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#general" },
+ async function (browser) {
+ let button = browser.contentDocument.getElementById(BUTTON_ID);
+
+ // First, we'll test the legacy Migration Wizard.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.migrate.content-modal.enabled", false]],
+ });
+
+ let migrationWizardWindow = BrowserTestUtils.domWindowOpenedAndLoaded(
+ null,
+ win => {
+ let type = win.document.documentElement.getAttribute("windowtype");
+ if (type == "Browser:MigrationWizard") {
+ Assert.ok(true, "Saw legacy Migration Wizard window open.");
+ return true;
+ }
+
+ return false;
+ }
+ );
+
+ button.click();
+ let win = await migrationWizardWindow;
+ await BrowserTestUtils.closeWindow(win);
+
+ // Next, we'll test the new Migration Wizard.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.migrate.content-modal.enabled", true]],
+ });
+
+ let wizardReady = BrowserTestUtils.waitForEvent(
+ browser.contentWindow,
+ "MigrationWizard:Ready"
+ );
+ button.click();
+ await wizardReady;
+ Assert.ok(true, "Saw the new Migration Wizard dialog open.");
+ }
+ );
+});
diff --git a/browser/components/preferences/tests/browser_password_management.js b/browser/components/preferences/tests/browser_password_management.js
new file mode 100644
index 0000000000..d84c88dc08
--- /dev/null
+++ b/browser/components/preferences/tests/browser_password_management.js
@@ -0,0 +1,43 @@
+"use strict";
+
+const { LoginTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/LoginTestUtils.sys.mjs"
+);
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+var passwordsDialog;
+
+add_task(async function test_openPasswordManagement() {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let tabOpenPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:logins");
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
+ let doc = content.document;
+
+ let savePasswordCheckBox = doc.getElementById("savePasswords");
+ Assert.ok(
+ !savePasswordCheckBox.checked,
+ "Save Password CheckBox should be unchecked by default"
+ );
+
+ let showPasswordsButton = doc.getElementById("showPasswords");
+ showPasswordsButton.click();
+ });
+
+ let tab = await tabOpenPromise;
+ ok(tab, "Tab opened");
+
+ // check telemetry events while we are in here
+ await LoginTestUtils.telemetry.waitForEventCount(1);
+ TelemetryTestUtils.assertEvents(
+ [["pwmgr", "open_management", "preferences"]],
+ { category: "pwmgr", method: "open_management" },
+ { clear: true, process: "content" }
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_pdf_disabled.js b/browser/components/preferences/tests/browser_pdf_disabled.js
new file mode 100644
index 0000000000..5b814b39e9
--- /dev/null
+++ b/browser/components/preferences/tests/browser_pdf_disabled.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test checks that pdf always appears in the applications list even
+// both a customized handler doesn't exist and when the internal viewer is
+// not enabled.
+add_task(async function pdfIsAlwaysPresent() {
+ // Try again with the pdf viewer enabled and disabled.
+ for (let test of ["enabled", "disabled"]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["pdfjs.disabled", test == "disabled"]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+
+ let win = gBrowser.selectedBrowser.contentWindow;
+
+ let container = win.document.getElementById("handlersView");
+
+ // First, find the PDF item.
+ let pdfItem = container.querySelector(
+ "richlistitem[type='application/pdf']"
+ );
+ Assert.ok(pdfItem, "pdfItem is present in handlersView when " + test);
+ if (pdfItem) {
+ pdfItem.scrollIntoView({ block: "center" });
+ pdfItem.closest("richlistbox").selectItem(pdfItem);
+
+ // Open its menu
+ let list = pdfItem.querySelector(".actionsMenu");
+ let popup = list.menupopup;
+ let popupShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(list, {}, win);
+ await popupShown;
+
+ let handleInternallyItem = list.querySelector(
+ `menuitem[action='${Ci.nsIHandlerInfo.handleInternally}']`
+ );
+
+ is(
+ test == "enabled",
+ !!handleInternallyItem,
+ "handle internally is present when " + test
+ );
+ }
+
+ gBrowser.removeCurrentTab();
+ }
+});
diff --git a/browser/components/preferences/tests/browser_performance.js b/browser/components/preferences/tests/browser_performance.js
new file mode 100644
index 0000000000..bd7ff70c51
--- /dev/null
+++ b/browser/components/preferences/tests/browser_performance.js
@@ -0,0 +1,300 @@
+const DEFAULT_HW_ACCEL_PREF = Services.prefs
+ .getDefaultBranch(null)
+ .getBoolPref("layers.acceleration.disabled");
+const DEFAULT_PROCESS_COUNT = Services.prefs
+ .getDefaultBranch(null)
+ .getIntPref("dom.ipc.processCount");
+
+add_task(async function () {
+ // We must temporarily disable `Once` StaticPrefs check for the duration of
+ // this test (see bug 1556131). We must do so in a separate operation as
+ // pushPrefEnv doesn't set the preferences in the order one could expect.
+ await SpecialPowers.pushPrefEnv({
+ set: [["preferences.force-disable.check.once.policy", true]],
+ });
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["layers.acceleration.disabled", DEFAULT_HW_ACCEL_PREF],
+ ["dom.ipc.processCount", DEFAULT_PROCESS_COUNT],
+ ["browser.preferences.defaultPerformanceSettings.enabled", true],
+ ],
+ });
+});
+
+add_task(async function () {
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ let useRecommendedPerformanceSettings = doc.querySelector(
+ "#useRecommendedPerformanceSettings"
+ );
+
+ is(
+ Services.prefs.getBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled"
+ ),
+ true,
+ "pref value should be true before clicking on checkbox"
+ );
+ ok(
+ useRecommendedPerformanceSettings.checked,
+ "checkbox should be checked before clicking on checkbox"
+ );
+
+ useRecommendedPerformanceSettings.click();
+
+ let performanceSettings = doc.querySelector("#performanceSettings");
+ is(
+ performanceSettings.hidden,
+ false,
+ "performance settings section is shown"
+ );
+
+ is(
+ Services.prefs.getBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled"
+ ),
+ false,
+ "pref value should be false after clicking on checkbox"
+ );
+ ok(
+ !useRecommendedPerformanceSettings.checked,
+ "checkbox should not be checked after clicking on checkbox"
+ );
+
+ let allowHWAccel = doc.querySelector("#allowHWAccel");
+ let allowHWAccelPref = Services.prefs.getBoolPref(
+ "layers.acceleration.disabled"
+ );
+ is(
+ allowHWAccelPref,
+ DEFAULT_HW_ACCEL_PREF,
+ "pref value should be the default value before clicking on checkbox"
+ );
+ is(
+ allowHWAccel.checked,
+ !DEFAULT_HW_ACCEL_PREF,
+ "checkbox should show the invert of the default value"
+ );
+
+ let contentProcessCount = doc.querySelector("#contentProcessCount");
+ is(
+ contentProcessCount.disabled,
+ false,
+ "process count control should be enabled"
+ );
+ is(
+ Services.prefs.getIntPref("dom.ipc.processCount"),
+ DEFAULT_PROCESS_COUNT,
+ "default pref value should be default value"
+ );
+ is(
+ contentProcessCount.selectedItem.value,
+ "" + DEFAULT_PROCESS_COUNT,
+ "selected item should be the default one"
+ );
+
+ allowHWAccel.click();
+ allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
+ is(
+ allowHWAccelPref,
+ !DEFAULT_HW_ACCEL_PREF,
+ "pref value should be opposite of the default value after clicking on checkbox"
+ );
+ is(
+ allowHWAccel.checked,
+ !allowHWAccelPref,
+ "checkbox should show the invert of the current value"
+ );
+
+ contentProcessCount.value = 7;
+ contentProcessCount.doCommand();
+ is(
+ Services.prefs.getIntPref("dom.ipc.processCount"),
+ 7,
+ "pref value should be 7"
+ );
+ is(contentProcessCount.selectedItem.value, "7", "selected item should be 7");
+
+ allowHWAccel.click();
+ allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
+ is(
+ allowHWAccelPref,
+ DEFAULT_HW_ACCEL_PREF,
+ "pref value should be the default value after clicking on checkbox"
+ );
+ is(
+ allowHWAccel.checked,
+ !allowHWAccelPref,
+ "checkbox should show the invert of the current value"
+ );
+
+ contentProcessCount.value = DEFAULT_PROCESS_COUNT;
+ contentProcessCount.doCommand();
+ is(
+ Services.prefs.getIntPref("dom.ipc.processCount"),
+ DEFAULT_PROCESS_COUNT,
+ "pref value should be default value"
+ );
+ is(
+ contentProcessCount.selectedItem.value,
+ "" + DEFAULT_PROCESS_COUNT,
+ "selected item should be default one"
+ );
+
+ is(
+ performanceSettings.hidden,
+ false,
+ "performance settings section should be still shown"
+ );
+
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ true
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function () {
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ let useRecommendedPerformanceSettings = doc.querySelector(
+ "#useRecommendedPerformanceSettings"
+ );
+ let allowHWAccel = doc.querySelector("#allowHWAccel");
+ let contentProcessCount = doc.querySelector("#contentProcessCount");
+ let performanceSettings = doc.querySelector("#performanceSettings");
+
+ useRecommendedPerformanceSettings.click();
+ allowHWAccel.click();
+ contentProcessCount.value = 7;
+ contentProcessCount.doCommand();
+ useRecommendedPerformanceSettings.click();
+
+ is(
+ Services.prefs.getBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled"
+ ),
+ true,
+ "pref value should be true before clicking on checkbox"
+ );
+ ok(
+ useRecommendedPerformanceSettings.checked,
+ "checkbox should be checked before clicking on checkbox"
+ );
+ is(
+ performanceSettings.hidden,
+ true,
+ "performance settings section should be still shown"
+ );
+
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ true
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function () {
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ let performanceSettings = doc.querySelector("#performanceSettings");
+
+ is(
+ performanceSettings.hidden,
+ true,
+ "performance settings section should not be shown"
+ );
+
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ false
+ );
+
+ is(
+ performanceSettings.hidden,
+ false,
+ "performance settings section should be shown"
+ );
+
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ true
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function () {
+ Services.prefs.setIntPref("dom.ipc.processCount", 7);
+
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+
+ let performanceSettings = doc.querySelector("#performanceSettings");
+ is(
+ performanceSettings.hidden,
+ false,
+ "performance settings section should be shown"
+ );
+
+ let contentProcessCount = doc.querySelector("#contentProcessCount");
+ is(
+ Services.prefs.getIntPref("dom.ipc.processCount"),
+ 7,
+ "pref value should be 7"
+ );
+ is(contentProcessCount.selectedItem.value, "7", "selected item should be 7");
+
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ true
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function () {
+ Services.prefs.setBoolPref("layers.acceleration.disabled", true);
+
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+
+ let performanceSettings = doc.querySelector("#performanceSettings");
+ is(
+ performanceSettings.hidden,
+ false,
+ "performance settings section should be shown"
+ );
+
+ let allowHWAccel = doc.querySelector("#allowHWAccel");
+ is(
+ Services.prefs.getBoolPref("layers.acceleration.disabled"),
+ true,
+ "pref value is false"
+ );
+ ok(!allowHWAccel.checked, "checkbox should not be checked");
+
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ true
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_performance_content_process_limit.js b/browser/components/preferences/tests/browser_performance_content_process_limit.js
new file mode 100644
index 0000000000..7ac5c354bd
--- /dev/null
+++ b/browser/components/preferences/tests/browser_performance_content_process_limit.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.defaultPerformanceSettings.enabled", false]],
+ });
+
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+
+ let limitContentProcess = doc.querySelector("#limitContentProcess");
+ is(
+ limitContentProcess.hidden,
+ Services.appinfo.fissionAutostart,
+ "Limit Content Process should be hidden if fission is enabled and shown if it is not."
+ );
+
+ let contentProcessCount = doc.querySelector("#contentProcessCount");
+ is(
+ contentProcessCount.hidden,
+ Services.appinfo.fissionAutostart,
+ "Limit Content Count should be hidden if fission is enabled and shown if it is not."
+ );
+
+ let contentProcessCountEnabledDescription = doc.querySelector(
+ "#contentProcessCountEnabledDescription"
+ );
+ is(
+ contentProcessCountEnabledDescription.hidden,
+ Services.appinfo.fissionAutostart,
+ "Limit Content Process Enabled Description should be hidden if fission is enabled and shown if it is not."
+ );
+
+ let contentProcessCountDisabledDescription = doc.querySelector(
+ "#contentProcessCountDisabledDescription"
+ );
+ is(
+ contentProcessCountDisabledDescription.hidden,
+ Services.appinfo.fissionAutostart ||
+ Services.appinfo.browserTabsRemoteAutostart,
+ "Limit Content Process Disabled Description should be shown if e10s is disabled, and hidden otherwise."
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_performance_e10srollout.js b/browser/components/preferences/tests/browser_performance_e10srollout.js
new file mode 100644
index 0000000000..1c2c57b6e7
--- /dev/null
+++ b/browser/components/preferences/tests/browser_performance_e10srollout.js
@@ -0,0 +1,164 @@
+const DEFAULT_HW_ACCEL_PREF = Services.prefs
+ .getDefaultBranch(null)
+ .getBoolPref("layers.acceleration.disabled");
+const DEFAULT_PROCESS_COUNT = Services.prefs
+ .getDefaultBranch(null)
+ .getIntPref("dom.ipc.processCount");
+
+add_task(async function () {
+ // We must temporarily disable `Once` StaticPrefs check for the duration of
+ // this test (see bug 1556131). We must do so in a separate operation as
+ // pushPrefEnv doesn't set the preferences in the order one could expect.
+ await SpecialPowers.pushPrefEnv({
+ set: [["preferences.force-disable.check.once.policy", true]],
+ });
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["layers.acceleration.disabled", DEFAULT_HW_ACCEL_PREF],
+ ["dom.ipc.processCount", DEFAULT_PROCESS_COUNT],
+ ["browser.preferences.defaultPerformanceSettings.enabled", true],
+ ],
+ });
+});
+
+add_task(async function testPrefsAreDefault() {
+ Services.prefs.setIntPref("dom.ipc.processCount", DEFAULT_PROCESS_COUNT);
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ true
+ );
+
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ let useRecommendedPerformanceSettings = doc.querySelector(
+ "#useRecommendedPerformanceSettings"
+ );
+
+ is(
+ Services.prefs.getBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled"
+ ),
+ true,
+ "pref value should be true before clicking on checkbox"
+ );
+ ok(
+ useRecommendedPerformanceSettings.checked,
+ "checkbox should be checked before clicking on checkbox"
+ );
+
+ useRecommendedPerformanceSettings.click();
+
+ let performanceSettings = doc.querySelector("#performanceSettings");
+ is(
+ performanceSettings.hidden,
+ false,
+ "performance settings section is shown"
+ );
+
+ is(
+ Services.prefs.getBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled"
+ ),
+ false,
+ "pref value should be false after clicking on checkbox"
+ );
+ ok(
+ !useRecommendedPerformanceSettings.checked,
+ "checkbox should not be checked after clicking on checkbox"
+ );
+
+ let contentProcessCount = doc.querySelector("#contentProcessCount");
+ is(
+ contentProcessCount.disabled,
+ false,
+ "process count control should be enabled"
+ );
+ is(
+ Services.prefs.getIntPref("dom.ipc.processCount"),
+ DEFAULT_PROCESS_COUNT,
+ "default pref should be default value"
+ );
+ is(
+ contentProcessCount.selectedItem.value,
+ "" + DEFAULT_PROCESS_COUNT,
+ "selected item should be the default one"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ Services.prefs.clearUserPref("dom.ipc.processCount");
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ true
+ );
+});
+
+add_task(async function testPrefsSetByUser() {
+ const kNewCount = DEFAULT_PROCESS_COUNT - 2;
+
+ Services.prefs.setIntPref("dom.ipc.processCount", kNewCount);
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ false
+ );
+
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ let performanceSettings = doc.querySelector("#performanceSettings");
+ is(
+ performanceSettings.hidden,
+ false,
+ "performance settings section is shown"
+ );
+
+ let contentProcessCount = doc.querySelector("#contentProcessCount");
+ is(
+ contentProcessCount.disabled,
+ false,
+ "process count control should be enabled"
+ );
+ is(
+ Services.prefs.getIntPref("dom.ipc.processCount"),
+ kNewCount,
+ "process count should be the set value"
+ );
+ is(
+ contentProcessCount.selectedItem.value,
+ "" + kNewCount,
+ "selected item should be the set one"
+ );
+
+ let useRecommendedPerformanceSettings = doc.querySelector(
+ "#useRecommendedPerformanceSettings"
+ );
+ useRecommendedPerformanceSettings.click();
+
+ is(
+ Services.prefs.getBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled"
+ ),
+ true,
+ "pref value should be true after clicking on checkbox"
+ );
+ is(
+ Services.prefs.getIntPref("dom.ipc.processCount"),
+ DEFAULT_PROCESS_COUNT,
+ "process count should be default value"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ Services.prefs.clearUserPref("dom.ipc.processCount");
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ true
+ );
+});
diff --git a/browser/components/preferences/tests/browser_performance_non_e10s.js b/browser/components/preferences/tests/browser_performance_non_e10s.js
new file mode 100644
index 0000000000..169afcaaa6
--- /dev/null
+++ b/browser/components/preferences/tests/browser_performance_non_e10s.js
@@ -0,0 +1,210 @@
+const DEFAULT_HW_ACCEL_PREF = Services.prefs
+ .getDefaultBranch(null)
+ .getBoolPref("layers.acceleration.disabled");
+const DEFAULT_PROCESS_COUNT = Services.prefs
+ .getDefaultBranch(null)
+ .getIntPref("dom.ipc.processCount");
+
+add_task(async function () {
+ // We must temporarily disable `Once` StaticPrefs check for the duration of
+ // this test (see bug 1556131). We must do so in a separate operation as
+ // pushPrefEnv doesn't set the preferences in the order one could expect.
+ await SpecialPowers.pushPrefEnv({
+ set: [["preferences.force-disable.check.once.policy", true]],
+ });
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["layers.acceleration.disabled", DEFAULT_HW_ACCEL_PREF],
+ ["dom.ipc.processCount", DEFAULT_PROCESS_COUNT],
+ ["browser.preferences.defaultPerformanceSettings.enabled", true],
+ ],
+ });
+});
+
+add_task(async function () {
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ let useRecommendedPerformanceSettings = doc.querySelector(
+ "#useRecommendedPerformanceSettings"
+ );
+
+ is(
+ Services.prefs.getBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled"
+ ),
+ true,
+ "pref value should be true before clicking on checkbox"
+ );
+ ok(
+ useRecommendedPerformanceSettings.checked,
+ "checkbox should be checked before clicking on checkbox"
+ );
+
+ useRecommendedPerformanceSettings.click();
+
+ let performanceSettings = doc.querySelector("#performanceSettings");
+ is(
+ performanceSettings.hidden,
+ false,
+ "performance settings section is shown"
+ );
+
+ is(
+ Services.prefs.getBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled"
+ ),
+ false,
+ "pref value should be false after clicking on checkbox"
+ );
+ ok(
+ !useRecommendedPerformanceSettings.checked,
+ "checkbox should not be checked after clicking on checkbox"
+ );
+
+ let allowHWAccel = doc.querySelector("#allowHWAccel");
+ let allowHWAccelPref = Services.prefs.getBoolPref(
+ "layers.acceleration.disabled"
+ );
+ is(
+ allowHWAccelPref,
+ DEFAULT_HW_ACCEL_PREF,
+ "pref value should be the default value before clicking on checkbox"
+ );
+ is(
+ allowHWAccel.checked,
+ !DEFAULT_HW_ACCEL_PREF,
+ "checkbox should show the invert of the default value"
+ );
+
+ let contentProcessCount = doc.querySelector("#contentProcessCount");
+ is(
+ contentProcessCount.disabled,
+ true,
+ "process count control should be disabled"
+ );
+
+ let contentProcessCountEnabledDescription = doc.querySelector(
+ "#contentProcessCountEnabledDescription"
+ );
+ is(
+ contentProcessCountEnabledDescription.hidden,
+ true,
+ "process count enabled description should be hidden"
+ );
+
+ let contentProcessCountDisabledDescription = doc.querySelector(
+ "#contentProcessCountDisabledDescription"
+ );
+ is(
+ contentProcessCountDisabledDescription.hidden,
+ false,
+ "process count enabled description should be shown"
+ );
+
+ allowHWAccel.click();
+ allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
+ is(
+ allowHWAccelPref,
+ !DEFAULT_HW_ACCEL_PREF,
+ "pref value should be opposite of the default value after clicking on checkbox"
+ );
+ is(
+ allowHWAccel.checked,
+ !allowHWAccelPref,
+ "checkbox should show the invert of the current value"
+ );
+
+ allowHWAccel.click();
+ allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
+ is(
+ allowHWAccelPref,
+ DEFAULT_HW_ACCEL_PREF,
+ "pref value should be the default value after clicking on checkbox"
+ );
+ is(
+ allowHWAccel.checked,
+ !allowHWAccelPref,
+ "checkbox should show the invert of the current value"
+ );
+
+ is(
+ performanceSettings.hidden,
+ false,
+ "performance settings section should be still shown"
+ );
+
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ true
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function () {
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ let performanceSettings = doc.querySelector("#performanceSettings");
+
+ is(
+ performanceSettings.hidden,
+ true,
+ "performance settings section should not be shown"
+ );
+
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ false
+ );
+
+ is(
+ performanceSettings.hidden,
+ false,
+ "performance settings section should be shown"
+ );
+
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ true
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function () {
+ Services.prefs.setBoolPref("layers.acceleration.disabled", true);
+
+ let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+ let doc = gBrowser.contentDocument;
+
+ let performanceSettings = doc.querySelector("#performanceSettings");
+ is(
+ performanceSettings.hidden,
+ false,
+ "performance settings section should be shown"
+ );
+
+ let allowHWAccel = doc.querySelector("#allowHWAccel");
+ is(
+ Services.prefs.getBoolPref("layers.acceleration.disabled"),
+ true,
+ "pref value is false"
+ );
+ ok(!allowHWAccel.checked, "checkbox should not be checked");
+
+ Services.prefs.setBoolPref(
+ "browser.preferences.defaultPerformanceSettings.enabled",
+ true
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_permissions_checkPermissionsWereAdded.js b/browser/components/preferences/tests/browser_permissions_checkPermissionsWereAdded.js
new file mode 100644
index 0000000000..829f897b72
--- /dev/null
+++ b/browser/components/preferences/tests/browser_permissions_checkPermissionsWereAdded.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PERMISSIONS_URL =
+ "chrome://browser/content/preferences/dialogs/permissions.xhtml";
+
+const _checkAndOpenCookiesDialog = async doc => {
+ let cookieExceptionsButton = doc.getElementById("cookieExceptions");
+ ok(cookieExceptionsButton, "cookieExceptionsButton found");
+ let dialogPromise = promiseLoadSubDialog(PERMISSIONS_URL);
+ cookieExceptionsButton.click();
+ let dialog = await dialogPromise;
+ return dialog;
+};
+
+const _checkCookiesDialog = (dialog, buttonIds) => {
+ ok(dialog, "dialog loaded");
+ let urlLabel = dialog.document.getElementById("urlLabel");
+ ok(!urlLabel.hidden, "urlLabel should be visible");
+ let url = dialog.document.getElementById("url");
+ ok(!url.hidden, "url should be visible");
+ for (let buttonId of buttonIds) {
+ let buttonDialog = dialog.document.getElementById(buttonId);
+ ok(buttonDialog, "blockButtonDialog found");
+ is(
+ buttonDialog.hasAttribute("disabled"),
+ true,
+ "If the user hasn't added an url the button shouldn't be clickable"
+ );
+ }
+ return dialog;
+};
+
+const _addWebsiteAddressToPermissionBox = (
+ websiteAddress,
+ dialog,
+ buttonId
+) => {
+ let url = dialog.document.getElementById("url");
+ let buttonDialog = dialog.document.getElementById(buttonId);
+ url.value = websiteAddress;
+ url.dispatchEvent(new Event("input", { bubbles: true }));
+ is(
+ buttonDialog.hasAttribute("disabled"),
+ false,
+ "When the user add an url the button should be clickable"
+ );
+ buttonDialog.click();
+ let permissionsBox = dialog.document.getElementById("permissionsBox");
+ let children = permissionsBox.getElementsByAttribute("origin", "*");
+ is(!children.length, false, "Website added in url should be in the list");
+};
+
+const _checkIfPermissionsWereAdded = (dialog, expectedResult) => {
+ let permissionsBox = dialog.document.getElementById("permissionsBox");
+ for (let website of expectedResult) {
+ let elements = permissionsBox.getElementsByAttribute("origin", website);
+ is(elements.length, 1, "It should find only one coincidence");
+ }
+};
+
+const _removesAllSitesInPermissionBox = dialog => {
+ let removeAllWebsitesButton = dialog.document.getElementById(
+ "removeAllPermissions"
+ );
+ ok(removeAllWebsitesButton, "removeAllWebsitesButton found");
+ is(
+ removeAllWebsitesButton.hasAttribute("disabled"),
+ false,
+ "There should be websites in the list"
+ );
+ removeAllWebsitesButton.click();
+};
+
+add_task(async function checkCookiePermissions() {
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = win.document;
+ let buttonIds = ["btnBlock", "btnCookieSession", "btnAllow"];
+
+ let dialog = await _checkAndOpenCookiesDialog(doc);
+ _checkCookiesDialog(dialog, buttonIds);
+
+ let tests = [
+ {
+ inputWebsite: "google.com",
+ expectedResult: ["http://google.com", "https://google.com"],
+ },
+ {
+ inputWebsite: "https://google.com",
+ expectedResult: ["https://google.com"],
+ },
+ {
+ inputWebsite: "http://",
+ expectedResult: ["http://http", "https://http"],
+ },
+ {
+ inputWebsite: "s3.eu-central-1.amazonaws.com",
+ expectedResult: [
+ "http://s3.eu-central-1.amazonaws.com",
+ "https://s3.eu-central-1.amazonaws.com",
+ ],
+ },
+ {
+ inputWebsite: "file://",
+ expectedResult: ["file:///"],
+ },
+ {
+ inputWebsite: "about:config",
+ expectedResult: ["about:config"],
+ },
+ ];
+
+ for (let buttonId of buttonIds) {
+ for (let test of tests) {
+ _addWebsiteAddressToPermissionBox(test.inputWebsite, dialog, buttonId);
+ _checkIfPermissionsWereAdded(dialog, test.expectedResult);
+ _removesAllSitesInPermissionBox(dialog);
+ }
+ }
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_permissions_dialog.js b/browser/components/preferences/tests/browser_permissions_dialog.js
new file mode 100644
index 0000000000..3a5e0f95c2
--- /dev/null
+++ b/browser/components/preferences/tests/browser_permissions_dialog.js
@@ -0,0 +1,642 @@
+"use strict";
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const PERMISSIONS_URL =
+ "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml";
+const URL = "http://www.example.com";
+const URI = Services.io.newURI(URL);
+var sitePermissionsDialog;
+let settingsButtonMap = {
+ "desktop-notification": "notificationSettingsButton",
+ speaker: "speakerSettingsButton",
+};
+
+function checkMenulistPermissionItem(origin, state) {
+ let doc = sitePermissionsDialog.document;
+
+ let label = doc.getElementsByTagName("label")[3];
+ Assert.equal(label.value, origin);
+
+ let menulist = doc.getElementsByTagName("menulist")[0];
+ Assert.equal(menulist.value, state);
+}
+
+async function openPermissionsDialog(permissionType) {
+ let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [settingsButtonMap[permissionType]],
+ function (settingsButtonId) {
+ let doc = content.document;
+ let settingsButton = doc.getElementById(settingsButtonId);
+ settingsButton.click();
+ }
+ );
+
+ sitePermissionsDialog = await dialogOpened;
+ await sitePermissionsDialog.document.mozSubdialogReady;
+}
+
+add_task(async function openSitePermissionsDialog() {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await openPermissionsDialog("desktop-notification");
+});
+
+add_task(async function addPermission() {
+ let doc = sitePermissionsDialog.document;
+ let richlistbox = doc.getElementById("permissionsBox");
+
+ // First item in the richlistbox contains column headers.
+ Assert.equal(
+ richlistbox.itemCount,
+ 0,
+ "Number of permission items is 0 initially"
+ );
+
+ // Add notification permission for a website.
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+
+ // Observe the added permission changes in the dialog UI.
+ Assert.equal(richlistbox.itemCount, 1);
+ checkMenulistPermissionItem(URL, Services.perms.ALLOW_ACTION);
+
+ PermissionTestUtils.remove(URI, "desktop-notification");
+});
+
+add_task(async function addPermissionPrivateBrowsing() {
+ let privateBrowsingPrincipal =
+ Services.scriptSecurityManager.createContentPrincipal(URI, {
+ privateBrowsingId: 1,
+ });
+ let doc = sitePermissionsDialog.document;
+ let richlistbox = doc.getElementById("permissionsBox");
+
+ Assert.equal(
+ richlistbox.itemCount,
+ 0,
+ "Number of permission items is 0 initially"
+ );
+
+ // Add a session permission for private browsing.
+ PermissionTestUtils.add(
+ privateBrowsingPrincipal,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION,
+ Services.perms.EXPIRE_SESSION
+ );
+
+ // The permission should not show in the dialog UI.
+ Assert.equal(richlistbox.itemCount, 0);
+
+ PermissionTestUtils.remove(privateBrowsingPrincipal, "desktop-notification");
+
+ // Add a permanent permission for private browsing
+ // The permission manager will store it as EXPIRE_SESSION
+ PermissionTestUtils.add(
+ privateBrowsingPrincipal,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+
+ // The permission should not show in the dialog UI.
+ Assert.equal(richlistbox.itemCount, 0);
+
+ PermissionTestUtils.remove(privateBrowsingPrincipal, "desktop-notification");
+});
+
+add_task(async function observePermissionChange() {
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+
+ // Change the permission.
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.DENY_ACTION
+ );
+
+ checkMenulistPermissionItem(URL, Services.perms.DENY_ACTION);
+
+ PermissionTestUtils.remove(URI, "desktop-notification");
+});
+
+add_task(async function observePermissionDelete() {
+ let doc = sitePermissionsDialog.document;
+ let richlistbox = doc.getElementById("permissionsBox");
+
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+
+ Assert.equal(
+ richlistbox.itemCount,
+ 1,
+ "The box contains one permission item initially"
+ );
+
+ PermissionTestUtils.remove(URI, "desktop-notification");
+
+ Assert.equal(richlistbox.itemCount, 0);
+});
+
+add_task(async function onPermissionChange() {
+ let doc = sitePermissionsDialog.document;
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+
+ // Change the permission state in the UI.
+ doc.getElementsByAttribute("value", SitePermissions.BLOCK)[0].click();
+
+ Assert.equal(
+ PermissionTestUtils.getPermissionObject(URI, "desktop-notification")
+ .capability,
+ Services.perms.ALLOW_ACTION,
+ "Permission state does not change before saving changes"
+ );
+
+ doc.querySelector("dialog").getButton("accept").click();
+
+ await TestUtils.waitForCondition(
+ () =>
+ PermissionTestUtils.getPermissionObject(URI, "desktop-notification")
+ .capability == Services.perms.DENY_ACTION
+ );
+
+ PermissionTestUtils.remove(URI, "desktop-notification");
+});
+
+add_task(async function onPermissionDelete() {
+ await openPermissionsDialog("desktop-notification");
+
+ let doc = sitePermissionsDialog.document;
+ let richlistbox = doc.getElementById("permissionsBox");
+
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+
+ richlistbox.selectItem(richlistbox.getItemAtIndex(0));
+ doc.getElementById("removePermission").click();
+
+ await TestUtils.waitForCondition(() => richlistbox.itemCount == 0);
+
+ Assert.equal(
+ PermissionTestUtils.getPermissionObject(URI, "desktop-notification")
+ .capability,
+ Services.perms.ALLOW_ACTION,
+ "Permission is not deleted before saving changes"
+ );
+
+ doc.querySelector("dialog").getButton("accept").click();
+
+ await TestUtils.waitForCondition(
+ () =>
+ PermissionTestUtils.getPermissionObject(URI, "desktop-notification") ==
+ null
+ );
+});
+
+add_task(async function onAllPermissionsDelete() {
+ await openPermissionsDialog("desktop-notification");
+
+ let doc = sitePermissionsDialog.document;
+ let richlistbox = doc.getElementById("permissionsBox");
+
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+ let u = Services.io.newURI("http://www.test.com");
+ PermissionTestUtils.add(
+ u,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+
+ doc.getElementById("removeAllPermissions").click();
+ await TestUtils.waitForCondition(() => richlistbox.itemCount == 0);
+
+ Assert.equal(
+ PermissionTestUtils.getPermissionObject(URI, "desktop-notification")
+ .capability,
+ Services.perms.ALLOW_ACTION
+ );
+ Assert.equal(
+ PermissionTestUtils.getPermissionObject(u, "desktop-notification")
+ .capability,
+ Services.perms.ALLOW_ACTION,
+ "Permissions are not deleted before saving changes"
+ );
+
+ doc.querySelector("dialog").getButton("accept").click();
+
+ await TestUtils.waitForCondition(
+ () =>
+ PermissionTestUtils.getPermissionObject(URI, "desktop-notification") ==
+ null &&
+ PermissionTestUtils.getPermissionObject(u, "desktop-notification") == null
+ );
+});
+
+add_task(async function onPermissionChangeAndDelete() {
+ await openPermissionsDialog("desktop-notification");
+
+ let doc = sitePermissionsDialog.document;
+ let richlistbox = doc.getElementById("permissionsBox");
+
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+
+ // Change the permission state in the UI.
+ doc.getElementsByAttribute("value", SitePermissions.BLOCK)[0].click();
+
+ // Remove that permission by clicking the "Remove" button.
+ richlistbox.selectItem(richlistbox.getItemAtIndex(0));
+ doc.getElementById("removePermission").click();
+
+ await TestUtils.waitForCondition(() => richlistbox.itemCount == 0);
+
+ doc.querySelector("dialog").getButton("accept").click();
+
+ await TestUtils.waitForCondition(
+ () =>
+ PermissionTestUtils.getPermissionObject(URI, "desktop-notification") ==
+ null
+ );
+});
+
+add_task(async function onPermissionChangeCancel() {
+ await openPermissionsDialog("desktop-notification");
+
+ let doc = sitePermissionsDialog.document;
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+
+ // Change the permission state in the UI.
+ doc.getElementsByAttribute("value", SitePermissions.BLOCK)[0].click();
+
+ doc.querySelector("dialog").getButton("cancel").click();
+
+ Assert.equal(
+ PermissionTestUtils.getPermissionObject(URI, "desktop-notification")
+ .capability,
+ Services.perms.ALLOW_ACTION,
+ "Permission state does not change on clicking cancel"
+ );
+
+ PermissionTestUtils.remove(URI, "desktop-notification");
+});
+
+add_task(async function onPermissionDeleteCancel() {
+ await openPermissionsDialog("desktop-notification");
+
+ let doc = sitePermissionsDialog.document;
+ let richlistbox = doc.getElementById("permissionsBox");
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+
+ // Remove that permission by clicking the "Remove" button.
+ richlistbox.selectItem(richlistbox.getItemAtIndex(0));
+ doc.getElementById("removePermission").click();
+
+ await TestUtils.waitForCondition(() => richlistbox.itemCount == 0);
+
+ doc.querySelector("dialog").getButton("cancel").click();
+
+ Assert.equal(
+ PermissionTestUtils.getPermissionObject(URI, "desktop-notification")
+ .capability,
+ Services.perms.ALLOW_ACTION,
+ "Permission state does not change on clicking cancel"
+ );
+
+ PermissionTestUtils.remove(URI, "desktop-notification");
+});
+
+add_task(async function onSearch() {
+ await openPermissionsDialog("desktop-notification");
+ let doc = sitePermissionsDialog.document;
+ let richlistbox = doc.getElementById("permissionsBox");
+ let searchBox = doc.getElementById("searchBox");
+
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+ searchBox.value = "www.example.com";
+
+ let u = Services.io.newURI("http://www.test.com");
+ PermissionTestUtils.add(
+ u,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+
+ Assert.equal(
+ doc.getElementsByAttribute("origin", "http://www.test.com")[0],
+ null
+ );
+ Assert.equal(
+ doc.getElementsByAttribute("origin", "http://www.example.com")[0],
+ richlistbox.getItemAtIndex(0)
+ );
+
+ PermissionTestUtils.remove(URI, "desktop-notification");
+ PermissionTestUtils.remove(u, "desktop-notification");
+
+ doc.querySelector("dialog").getButton("cancel").click();
+});
+
+add_task(async function onPermissionsSort() {
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+ let u = Services.io.newURI("http://www.test.com");
+ PermissionTestUtils.add(
+ u,
+ "desktop-notification",
+ Services.perms.DENY_ACTION
+ );
+
+ await openPermissionsDialog("desktop-notification");
+ let doc = sitePermissionsDialog.document;
+ let richlistbox = doc.getElementById("permissionsBox");
+
+ // Test default arrangement(Allow followed by Block).
+ Assert.equal(
+ richlistbox.getItemAtIndex(0).getAttribute("origin"),
+ "http://www.example.com"
+ );
+ Assert.equal(
+ richlistbox.getItemAtIndex(1).getAttribute("origin"),
+ "http://www.test.com"
+ );
+
+ doc.getElementById("statusCol").click();
+
+ // Test the rearrangement(Block followed by Allow).
+ Assert.equal(
+ richlistbox.getItemAtIndex(0).getAttribute("origin"),
+ "http://www.test.com"
+ );
+ Assert.equal(
+ richlistbox.getItemAtIndex(1).getAttribute("origin"),
+ "http://www.example.com"
+ );
+
+ doc.getElementById("siteCol").click();
+
+ // Test the rearrangement(Website names arranged in alphabhetical order).
+ Assert.equal(
+ richlistbox.getItemAtIndex(0).getAttribute("origin"),
+ "http://www.example.com"
+ );
+ Assert.equal(
+ richlistbox.getItemAtIndex(1).getAttribute("origin"),
+ "http://www.test.com"
+ );
+
+ doc.getElementById("siteCol").click();
+
+ // Test the rearrangement(Website names arranged in reverse alphabhetical order).
+ Assert.equal(
+ richlistbox.getItemAtIndex(0).getAttribute("origin"),
+ "http://www.test.com"
+ );
+ Assert.equal(
+ richlistbox.getItemAtIndex(1).getAttribute("origin"),
+ "http://www.example.com"
+ );
+
+ PermissionTestUtils.remove(URI, "desktop-notification");
+ PermissionTestUtils.remove(u, "desktop-notification");
+
+ doc.querySelector("dialog").getButton("cancel").click();
+});
+
+add_task(async function onPermissionDisable() {
+ // Enable desktop-notification permission prompts.
+ Services.prefs.setIntPref(
+ "permissions.default.desktop-notification",
+ SitePermissions.UNKNOWN
+ );
+
+ await openPermissionsDialog("desktop-notification");
+ let doc = sitePermissionsDialog.document;
+
+ // Check if the enabled state is reflected in the checkbox.
+ let checkbox = doc.getElementById("permissionsDisableCheckbox");
+ Assert.equal(checkbox.checked, false);
+
+ // Disable permission and click on "Cancel".
+ checkbox.checked = true;
+ doc.querySelector("dialog").getButton("cancel").click();
+
+ // Check that the permission is not disabled yet.
+ let perm = Services.prefs.getIntPref(
+ "permissions.default.desktop-notification"
+ );
+ Assert.equal(perm, SitePermissions.UNKNOWN);
+
+ // Open the dialog once again.
+ await openPermissionsDialog("desktop-notification");
+ doc = sitePermissionsDialog.document;
+
+ // Disable permission and save changes.
+ checkbox = doc.getElementById("permissionsDisableCheckbox");
+ checkbox.checked = true;
+ doc.querySelector("dialog").getButton("accept").click();
+
+ // Check if the permission is now disabled.
+ perm = Services.prefs.getIntPref("permissions.default.desktop-notification");
+ Assert.equal(perm, SitePermissions.BLOCK);
+
+ // Open the dialog once again and check if the disabled state is still reflected in the checkbox.
+ await openPermissionsDialog("desktop-notification");
+ doc = sitePermissionsDialog.document;
+ checkbox = doc.getElementById("permissionsDisableCheckbox");
+ Assert.equal(checkbox.checked, true);
+
+ // Close the dialog and clean up.
+ doc.querySelector("dialog").getButton("cancel").click();
+ Services.prefs.setIntPref(
+ "permissions.default.desktop-notification",
+ SitePermissions.UNKNOWN
+ );
+});
+
+add_task(async function checkDefaultPermissionState() {
+ // Set default permission state to ALLOW.
+ Services.prefs.setIntPref(
+ "permissions.default.desktop-notification",
+ SitePermissions.ALLOW
+ );
+
+ await openPermissionsDialog("desktop-notification");
+ let doc = sitePermissionsDialog.document;
+
+ // Check if the enabled state is reflected in the checkbox.
+ let checkbox = doc.getElementById("permissionsDisableCheckbox");
+ Assert.equal(checkbox.checked, false);
+
+ // Check the checkbox and then uncheck it.
+ checkbox.checked = true;
+ checkbox.checked = false;
+
+ // Save changes.
+ doc.querySelector("dialog").getButton("accept").click();
+
+ // Check if the default permission state is retained (and not automatically set to SitePermissions.UNKNOWN).
+ let state = Services.prefs.getIntPref(
+ "permissions.default.desktop-notification"
+ );
+ Assert.equal(state, SitePermissions.ALLOW);
+
+ // Clean up.
+ Services.prefs.setIntPref(
+ "permissions.default.desktop-notification",
+ SitePermissions.UNKNOWN
+ );
+});
+
+add_task(async function testTabBehaviour() {
+ // Test tab behaviour inside the permissions setting dialog when site permissions are selected.
+ // Only selected items in the richlistbox should be tabable for accessibility reasons.
+
+ // Force tabfocus for all elements on OSX.
+ SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] });
+
+ PermissionTestUtils.add(
+ URI,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+ let u = Services.io.newURI("http://www.test.com");
+ PermissionTestUtils.add(
+ u,
+ "desktop-notification",
+ Services.perms.ALLOW_ACTION
+ );
+
+ await openPermissionsDialog("desktop-notification");
+ let doc = sitePermissionsDialog.document;
+
+ EventUtils.synthesizeKey("KEY_Tab", {}, sitePermissionsDialog);
+ let richlistbox = doc.getElementById("permissionsBox");
+ is(
+ richlistbox,
+ doc.activeElement.closest("#permissionsBox"),
+ "The richlistbox is focused after pressing tab once."
+ );
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, sitePermissionsDialog);
+ EventUtils.synthesizeKey("KEY_Tab", {}, sitePermissionsDialog);
+ let menulist = doc
+ .getElementById("permissionsBox")
+ .itemChildren[1].getElementsByTagName("menulist")[0];
+ is(
+ menulist,
+ doc.activeElement,
+ "The menulist inside the selected richlistitem is focused now"
+ );
+
+ EventUtils.synthesizeKey("KEY_Tab", {}, sitePermissionsDialog);
+ let removeButton = doc.getElementById("removePermission");
+ is(
+ removeButton,
+ doc.activeElement,
+ "The focus moves outside the richlistbox and onto the remove button"
+ );
+
+ PermissionTestUtils.remove(URI, "desktop-notification");
+ PermissionTestUtils.remove(u, "desktop-notification");
+
+ doc.querySelector("dialog").getButton("cancel").click();
+});
+
+add_task(async function addSpeakerPermission() {
+ let enabled = Services.prefs.getBoolPref("media.setsinkid.enabled", false);
+ let speakerRow =
+ gBrowser.contentDocument.getElementById("speakerSettingsRow");
+ Assert.equal(
+ BrowserTestUtils.is_visible(speakerRow),
+ enabled,
+ "speakerRow visible"
+ );
+ if (!enabled) {
+ return;
+ }
+
+ await openPermissionsDialog("speaker");
+ let doc = sitePermissionsDialog.document;
+ let richlistbox = doc.getElementById("permissionsBox");
+ Assert.equal(
+ richlistbox.itemCount,
+ 0,
+ "Number of permission items is 0 initially"
+ );
+ // Add an allow permission for a device.
+ let deviceId = "DEVICE-ID";
+ let devicePermissionId = `speaker^${deviceId}`;
+ PermissionTestUtils.add(URI, devicePermissionId, Services.perms.ALLOW_ACTION);
+
+ // Observe the added permission changes in the dialog UI.
+ Assert.equal(richlistbox.itemCount, 1, "itemCount with allow");
+ checkMenulistPermissionItem(URL, Services.perms.ALLOW_ACTION);
+
+ // Check that an all-device deny permission overrides the device-specific
+ // allow permission.
+ PermissionTestUtils.add(URI, "speaker", Services.perms.DENY_ACTION);
+
+ Assert.equal(richlistbox.itemCount, 1, "itemCount with deny and allow");
+ let richlistitem = richlistbox.itemChildren[0];
+ let siteStatus = richlistitem.querySelector(".website-status");
+ Assert.equal(
+ siteStatus.value,
+ Services.perms.DENY_ACTION,
+ "website status with deny and allow"
+ );
+ // The website status element is not a menulist because all-device allow is
+ // not an option.
+ Assert.equal(siteStatus.tagName, "hbox");
+ Assert.equal(siteStatus.firstElementChild.tagName, "label");
+
+ PermissionTestUtils.remove(URI, devicePermissionId);
+ PermissionTestUtils.remove(URI, "speaker");
+
+ doc.querySelector("dialog").getButton("cancel").click();
+});
+
+add_task(async function removeTab() {
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_permissions_dialog_default_perm.js b/browser/components/preferences/tests/browser_permissions_dialog_default_perm.js
new file mode 100644
index 0000000000..37bde1a275
--- /dev/null
+++ b/browser/components/preferences/tests/browser_permissions_dialog_default_perm.js
@@ -0,0 +1,145 @@
+"use strict";
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const PERMISSIONS_URL =
+ "chrome://browser/content/preferences/dialogs/permissions.xhtml";
+
+let sitePermissionsDialog;
+
+let principal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI("http://www.example.com"),
+ {}
+);
+let pbPrincipal = Services.scriptSecurityManager.principalWithOA(principal, {
+ privateBrowsingId: 1,
+});
+let principalB = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI("https://example.org"),
+ {}
+);
+
+/**
+ * Replaces the default permissions defined in browser/app/permissions with our
+ * own test-only permissions and instructs the permission manager to import
+ * them.
+ */
+async function addDefaultTestPermissions() {
+ // create a file in the temp directory with the defaults.
+ let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ file.append("test_default_permissions");
+
+ await IOUtils.writeUTF8(
+ file.path,
+ `origin\tinstall\t1\t${principal.origin}\norigin\tinstall\t1\t${pbPrincipal.origin}\n`
+ );
+
+ // Change the default permission file path.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["permissions.manager.defaultsUrl", Services.io.newFileURI(file).spec],
+ ],
+ });
+
+ // Call the permission manager to reload default permissions from file.
+ Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk");
+
+ registerCleanupFunction(async () => {
+ // Clean up temporary default permission file.
+ await IOUtils.remove(file.path);
+
+ // Restore non-test default permissions.
+ await SpecialPowers.popPrefEnv();
+ Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk");
+ });
+}
+
+async function openPermissionsDialog() {
+ let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
+ let doc = content.document;
+ let settingsButton = doc.getElementById("addonExceptions");
+ settingsButton.click();
+ });
+
+ sitePermissionsDialog = await dialogOpened;
+ await sitePermissionsDialog.document.mozSubdialogReady;
+}
+
+add_setup(async function () {
+ await addDefaultTestPermissions();
+});
+
+/**
+ * Tests that default (persistent) private browsing permissions can be removed.
+ */
+add_task(async function removeAll() {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await openPermissionsDialog();
+
+ let doc = sitePermissionsDialog.document;
+ let richlistbox = doc.getElementById("permissionsBox");
+
+ // First item in the richlistbox contains column headers.
+ Assert.equal(
+ richlistbox.itemCount,
+ 2,
+ "Should have the two default permission entries initially."
+ );
+
+ info("Adding a new non-default install permission");
+ PermissionTestUtils.add(principalB, "install", Services.perms.ALLOW_ACTION);
+
+ info("Waiting for the permission to appear in the list.");
+ await BrowserTestUtils.waitForMutationCondition(
+ richlistbox,
+ { childList: true },
+ () => richlistbox.itemCount == 3
+ );
+
+ info("Clicking remove all.");
+ doc.getElementById("removeAllPermissions").click();
+
+ info("Waiting for all list items to be cleared.");
+ await BrowserTestUtils.waitForMutationCondition(
+ richlistbox,
+ { childList: true },
+ () => richlistbox.itemCount == 0
+ );
+
+ let dialogClosePromise = BrowserTestUtils.waitForEvent(
+ sitePermissionsDialog,
+ "dialogclosing",
+ true
+ );
+
+ info("Accepting dialog to apply the changes.");
+ doc.querySelector("dialog").getButton("accept").click();
+
+ info("Waiting for dialog to close.");
+ await dialogClosePromise;
+
+ info("Waiting for all permissions to be removed.");
+ await TestUtils.waitForCondition(
+ () =>
+ PermissionTestUtils.getPermissionObject(principal, "install") == null &&
+ PermissionTestUtils.getPermissionObject(pbPrincipal, "install") == null &&
+ PermissionTestUtils.getPermissionObject(principalB, "install") == null
+ );
+
+ info("Opening the permissions dialog again.");
+ await openPermissionsDialog();
+
+ Assert.equal(
+ richlistbox.itemCount,
+ 0,
+ "Permission list should still be empty."
+ );
+
+ // Cleanup
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ Services.perms.removeAll();
+});
diff --git a/browser/components/preferences/tests/browser_permissions_urlFieldHidden.js b/browser/components/preferences/tests/browser_permissions_urlFieldHidden.js
new file mode 100644
index 0000000000..537ee3db72
--- /dev/null
+++ b/browser/components/preferences/tests/browser_permissions_urlFieldHidden.js
@@ -0,0 +1,38 @@
+"use strict";
+
+const PERMISSIONS_URL =
+ "chrome://browser/content/preferences/dialogs/permissions.xhtml";
+
+add_task(async function urlFieldVisibleForPopupPermissions(finish) {
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = win.document;
+ let popupPolicyCheckbox = doc.getElementById("popupPolicy");
+ ok(
+ !popupPolicyCheckbox.checked,
+ "popupPolicyCheckbox should be unchecked by default"
+ );
+ popupPolicyCheckbox.click();
+ let popupPolicyButton = doc.getElementById("popupPolicyButton");
+ ok(popupPolicyButton, "popupPolicyButton found");
+ let dialogPromise = promiseLoadSubDialog(PERMISSIONS_URL);
+ popupPolicyButton.click();
+ let dialog = await dialogPromise;
+ ok(dialog, "dialog loaded");
+
+ let urlLabel = dialog.document.getElementById("urlLabel");
+ ok(
+ !urlLabel.hidden,
+ "urlLabel should be visible when one of block/session/allow visible"
+ );
+ let url = dialog.document.getElementById("url");
+ ok(
+ !url.hidden,
+ "url should be visible when one of block/session/allow visible"
+ );
+
+ popupPolicyCheckbox.click();
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_primaryPassword.js b/browser/components/preferences/tests/browser_primaryPassword.js
new file mode 100644
index 0000000000..4de28a1fdb
--- /dev/null
+++ b/browser/components/preferences/tests/browser_primaryPassword.js
@@ -0,0 +1,130 @@
+const { OSKeyStoreTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/OSKeyStoreTestUtils.sys.mjs"
+);
+const { OSKeyStore } = ChromeUtils.importESModule(
+ "resource://gre/modules/OSKeyStore.sys.mjs"
+);
+
+add_task(async function () {
+ let prefs = await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, "panePrivacy", "Privacy pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ // Fake the subdialog and LoginHelper
+ let win = doc.defaultView;
+ let dialogURL = "";
+ let dialogOpened = false;
+ XPCOMUtils.defineLazyGetter(win, "gSubDialog", () => ({
+ open(aDialogURL, { closingCallback: aCallback }) {
+ dialogOpened = true;
+ dialogURL = aDialogURL;
+ primaryPasswordSet = primaryPasswordNextState;
+ aCallback();
+ },
+ }));
+
+ let primaryPasswordSet = false;
+ win.LoginHelper = {
+ isPrimaryPasswordSet() {
+ return primaryPasswordSet;
+ },
+ };
+
+ let checkbox = doc.querySelector("#useMasterPassword");
+ checkbox.scrollIntoView();
+ ok(
+ !checkbox.checked,
+ "primary password checkbox should be unchecked by default"
+ );
+ let button = doc.getElementById("changeMasterPassword");
+ ok(button.disabled, "primary password button should be disabled by default");
+
+ let primaryPasswordNextState = false;
+ if (OSKeyStoreTestUtils.canTestOSKeyStoreLogin() && OSKeyStore.canReauth()) {
+ let osAuthDialogShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(false);
+ checkbox.click();
+ info("waiting for os auth dialog to appear and get canceled");
+ await osAuthDialogShown;
+ await TestUtils.waitForCondition(
+ () => !checkbox.checked,
+ "wait for checkbox to get unchecked"
+ );
+ ok(!dialogOpened, "the dialog should not have opened");
+ ok(
+ !dialogURL,
+ "the changemp dialog should not have been opened when the os auth dialog is canceled"
+ );
+ ok(
+ !checkbox.checked,
+ "primary password checkbox should be unchecked after canceling os auth dialog"
+ );
+ ok(button.disabled, "button should be disabled after canceling os auth");
+ }
+
+ primaryPasswordNextState = true;
+ if (OSKeyStoreTestUtils.canTestOSKeyStoreLogin() && OSKeyStore.canReauth()) {
+ let osAuthDialogShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
+ checkbox.click();
+ info("waiting for os auth dialog to appear");
+ await osAuthDialogShown;
+ info("waiting for dialogURL to get set");
+ await TestUtils.waitForCondition(
+ () => dialogURL,
+ "wait for open to get called asynchronously"
+ );
+ is(
+ dialogURL,
+ "chrome://mozapps/content/preferences/changemp.xhtml",
+ "clicking on the checkbox should open the primary password dialog"
+ );
+ } else {
+ primaryPasswordSet = true;
+ doc.defaultView.gPrivacyPane._initMasterPasswordUI();
+ await TestUtils.waitForCondition(
+ () => !button.disabled,
+ "waiting for primary password button to get enabled"
+ );
+ }
+ ok(!button.disabled, "primary password button should now be enabled");
+ ok(checkbox.checked, "primary password checkbox should be checked now");
+
+ dialogURL = "";
+ button.doCommand();
+ await TestUtils.waitForCondition(
+ () => dialogURL,
+ "wait for open to get called asynchronously"
+ );
+ is(
+ dialogURL,
+ "chrome://mozapps/content/preferences/changemp.xhtml",
+ "clicking on the button should open the primary password dialog"
+ );
+ ok(!button.disabled, "primary password button should still be enabled");
+ ok(checkbox.checked, "primary password checkbox should be checked still");
+
+ // Confirm that we won't automatically respond to the dialog,
+ // since we don't expect a dialog here, we want the test to fail if one appears.
+ is(
+ Services.prefs.getStringPref(
+ "toolkit.osKeyStore.unofficialBuildOnlyLogin",
+ ""
+ ),
+ "",
+ "Pref should be set to an empty string"
+ );
+
+ primaryPasswordNextState = false;
+ dialogURL = "";
+ checkbox.click();
+ is(
+ dialogURL,
+ "chrome://mozapps/content/preferences/removemp.xhtml",
+ "clicking on the checkbox to uncheck primary password should show the removal dialog"
+ );
+ ok(button.disabled, "primary password button should now be disabled");
+ ok(!checkbox.checked, "primary password checkbox should now be unchecked");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_privacy_cookieBannerHandling.js b/browser/components/preferences/tests/browser_privacy_cookieBannerHandling.js
new file mode 100644
index 0000000000..722fe9a215
--- /dev/null
+++ b/browser/components/preferences/tests/browser_privacy_cookieBannerHandling.js
@@ -0,0 +1,210 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This file tests the Privacy pane's Cookie Banner Handling UI.
+
+"use strict";
+
+const FEATURE_PREF = "cookiebanners.ui.desktop.enabled";
+const MODE_PREF = "cookiebanners.service.mode";
+const PBM_MODE_PREF = "cookiebanners.service.mode.privateBrowsing";
+const DETECT_ONLY_PREF = "cookiebanners.service.detectOnly";
+
+const GROUPBOX_ID = "cookieBannerHandlingGroup";
+const CHECKBOX_ID = "handleCookieBanners";
+
+// Test the section is hidden on page load if the feature pref is disabled.
+add_task(async function test_section_hidden_when_feature_flag_disabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, false],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_DISABLED],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function (browser) {
+ let groupbox = browser.contentDocument.getElementById(GROUPBOX_ID);
+ is_element_hidden(groupbox, "#cookieBannerHandlingGroup is hidden");
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test the section is shown on page load if the feature pref is enabled.
+add_task(async function test_section_shown_when_feature_flag_enabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, true],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_DISABLED],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function (browser) {
+ let groupbox = browser.contentDocument.getElementById(GROUPBOX_ID);
+ is_element_visible(groupbox, "#cookieBannerHandlingGroup is visible");
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test the checkbox is unchecked in DISABLED mode.
+add_task(async function test_checkbox_unchecked_disabled_mode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, true],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_DISABLED],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function (browser) {
+ let checkbox = browser.contentDocument.getElementById(CHECKBOX_ID);
+ ok(!checkbox.checked, "checkbox is not checked in DISABLED mode");
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test the checkbox is unchecked in detect-only mode.
+add_task(async function test_checkbox_unchecked_detect_only_mode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, true],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_REJECT],
+ [DETECT_ONLY_PREF, true],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function (browser) {
+ let checkbox = browser.contentDocument.getElementById(CHECKBOX_ID);
+ ok(!checkbox.checked, "checkbox is not checked in detect-only mode");
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test the checkbox is checked in REJECT_OR_ACCEPT mode.
+add_task(async function test_checkbox_checked_reject_or_accept_mode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, true],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function (browser) {
+ let checkbox = browser.contentDocument.getElementById(CHECKBOX_ID);
+ ok(checkbox.checked, "checkbox is checked in REJECT_OR_ACCEPT mode");
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test the checkbox is checked in REJECT mode.
+add_task(async function test_checkbox_checked_reject_mode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, true],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function (browser) {
+ let checkbox = browser.contentDocument.getElementById(CHECKBOX_ID);
+ ok(checkbox.checked, "checkbox is checked in REJECT mode");
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test that toggling the checkbox toggles the mode pref value as expected,
+// and also disables detect only mode, as expected.
+add_task(async function test_checkbox_modifies_prefs() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, true],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_UNSET],
+ [PBM_MODE_PREF, Ci.nsICookieBannerService.MODE_UNSET],
+ [DETECT_ONLY_PREF, true],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function (browser) {
+ let checkboxSelector = "#" + CHECKBOX_ID;
+ let checkbox = browser.contentDocument.querySelector(checkboxSelector);
+ let section = browser.contentDocument.getElementById(GROUPBOX_ID);
+
+ section.scrollIntoView();
+
+ Assert.ok(
+ !checkbox.checked,
+ "initially, the checkbox should be unchecked"
+ );
+
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ checkboxSelector,
+ {},
+ browser
+ );
+ Assert.ok(checkbox.checked, "checkbox should be checked");
+ Assert.equal(
+ Ci.nsICookieBannerService.MODE_REJECT,
+ Services.prefs.getIntPref(MODE_PREF),
+ "cookie banner handling mode should be set to REJECT mode after checking the checkbox"
+ );
+ Assert.equal(
+ Ci.nsICookieBannerService.MODE_REJECT,
+ Services.prefs.getIntPref(PBM_MODE_PREF),
+ "cookie banner handling mode for PBM should be set to REJECT mode after checking the checkbox"
+ );
+ Assert.equal(
+ false,
+ Services.prefs.getBoolPref(DETECT_ONLY_PREF),
+ "cookie banner handling detect-only mode should be disabled after checking the checkbox"
+ );
+
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ checkboxSelector,
+ {},
+ browser
+ );
+ Assert.ok(!checkbox.checked, "checkbox should be unchecked");
+ Assert.equal(
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ Services.prefs.getIntPref(MODE_PREF),
+ "cookie banner handling mode should be set to DISABLED mode after unchecking the checkbox"
+ );
+ Assert.equal(
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ Services.prefs.getIntPref(PBM_MODE_PREF),
+ "cookie banner handling mode for PBM should be set to DISABLED mode after unchecking the checkbox"
+ );
+ Assert.equal(
+ false,
+ Services.prefs.getBoolPref(DETECT_ONLY_PREF),
+ "cookie banner handling detect-only mode should still be disabled after unchecking the checkbox"
+ );
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/preferences/tests/browser_privacy_dnsoverhttps.js b/browser/components/preferences/tests/browser_privacy_dnsoverhttps.js
new file mode 100644
index 0000000000..48469cfce4
--- /dev/null
+++ b/browser/components/preferences/tests/browser_privacy_dnsoverhttps.js
@@ -0,0 +1,844 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(4);
+
+const { EnterprisePolicyTesting, PoliciesPrefTracker } =
+ ChromeUtils.importESModule(
+ "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
+ );
+
+ChromeUtils.defineESModuleGetters(this, {
+ DoHConfigController: "resource:///modules/DoHConfig.sys.mjs",
+ DoHController: "resource:///modules/DoHController.sys.mjs",
+ DoHTestUtils: "resource://testing-common/DoHTestUtils.sys.mjs",
+});
+
+const TRR_MODE_PREF = "network.trr.mode";
+const TRR_URI_PREF = "network.trr.uri";
+const TRR_CUSTOM_URI_PREF = "network.trr.custom_uri";
+const ROLLOUT_ENABLED_PREF = "doh-rollout.enabled";
+const ROLLOUT_SELF_ENABLED_PREF = "doh-rollout.self-enabled";
+const HEURISTICS_DISABLED_PREF = "doh-rollout.disable-heuristics";
+const FIRST_RESOLVER_VALUE = DoHTestUtils.providers[0].uri;
+const SECOND_RESOLVER_VALUE = DoHTestUtils.providers[1].uri;
+const DEFAULT_RESOLVER_VALUE = FIRST_RESOLVER_VALUE;
+
+const defaultPrefValues = Object.freeze({
+ [TRR_MODE_PREF]: 0,
+ [TRR_CUSTOM_URI_PREF]: "",
+});
+
+// See bug 1741554. This test should not actually try to create a connection to
+// the real DoH endpoint. But a background request could do that while the test
+// is in progress, before we've actually disabled TRR, and would cause a crash
+// due to connecting to a non-local IP.
+// To prevent that we override the IP to a local address.
+Cc["@mozilla.org/network/native-dns-override;1"]
+ .getService(Ci.nsINativeDNSResolverOverride)
+ .addIPOverride("mozilla.cloudflare-dns.com", "127.0.0.1");
+
+async function clearEvents() {
+ Services.telemetry.clearEvents();
+ await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_ALL_CHANNELS,
+ true
+ ).parent;
+ return !events || !events.length;
+ });
+}
+
+async function getEvent(filter1, filter2) {
+ let event = await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_ALL_CHANNELS,
+ true
+ ).parent;
+ return events?.find(e => e[1] == filter1 && e[2] == filter2);
+ }, "recorded telemetry for the load");
+ event.shift();
+ return event;
+}
+
+async function resetPrefs() {
+ await DoHTestUtils.resetRemoteSettingsConfig();
+ await DoHController._uninit();
+ Services.prefs.clearUserPref(TRR_MODE_PREF);
+ Services.prefs.clearUserPref(TRR_URI_PREF);
+ Services.prefs.clearUserPref(TRR_CUSTOM_URI_PREF);
+ Services.prefs.getChildList("doh-rollout.").forEach(pref => {
+ Services.prefs.clearUserPref(pref);
+ });
+ // Clear out any telemetry events generated by DoHController so that we don't
+ // confuse tests running after this one that are looking at those.
+ Services.telemetry.clearEvents();
+ await DoHController.init();
+}
+Services.prefs.setStringPref("network.trr.confirmationNS", "skip");
+
+registerCleanupFunction(async () => {
+ await resetPrefs();
+ Services.prefs.clearUserPref("network.trr.confirmationNS");
+});
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["toolkit.telemetry.testing.overrideProductsCheck", true]],
+ });
+
+ await DoHTestUtils.resetRemoteSettingsConfig();
+});
+
+function waitForPrefObserver(name) {
+ return new Promise(resolve => {
+ const observer = {
+ observe(aSubject, aTopic, aData) {
+ if (aData == name) {
+ Services.prefs.removeObserver(name, observer);
+ resolve();
+ }
+ },
+ };
+ Services.prefs.addObserver(name, observer);
+ });
+}
+
+async function testWithProperties(props, startTime) {
+ info(
+ Date.now() -
+ startTime +
+ ": testWithProperties: testing with " +
+ JSON.stringify(props)
+ );
+
+ // There are two different signals that the DoHController is ready, depending
+ // on the config being tested. If we're setting the TRR mode pref, we should
+ // expect the disable-heuristics pref to be set as the signal. Else, we can
+ // expect the self-enabled pref as the signal.
+ let rolloutReadyPromise;
+ if (props.hasOwnProperty(TRR_MODE_PREF)) {
+ if (
+ [2, 3, 5].includes(props[TRR_MODE_PREF]) &&
+ props.hasOwnProperty(ROLLOUT_ENABLED_PREF)
+ ) {
+ // Only initialize the promise if we're going to enable the rollout -
+ // otherwise we will never await it, which could cause a leak if it doesn't
+ // end up resolving.
+ rolloutReadyPromise = waitForPrefObserver(HEURISTICS_DISABLED_PREF);
+ }
+ Services.prefs.setIntPref(TRR_MODE_PREF, props[TRR_MODE_PREF]);
+ }
+ if (props.hasOwnProperty(ROLLOUT_ENABLED_PREF)) {
+ if (!rolloutReadyPromise) {
+ rolloutReadyPromise = waitForPrefObserver(ROLLOUT_SELF_ENABLED_PREF);
+ }
+ Services.prefs.setBoolPref(
+ ROLLOUT_ENABLED_PREF,
+ props[ROLLOUT_ENABLED_PREF]
+ );
+ await rolloutReadyPromise;
+ }
+ if (props.hasOwnProperty(TRR_CUSTOM_URI_PREF)) {
+ Services.prefs.setStringPref(
+ TRR_CUSTOM_URI_PREF,
+ props[TRR_CUSTOM_URI_PREF]
+ );
+ }
+ if (props.hasOwnProperty(TRR_URI_PREF)) {
+ Services.prefs.setStringPref(TRR_URI_PREF, props[TRR_URI_PREF]);
+ }
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ info(Date.now() - startTime + ": testWithProperties: tab now open");
+ let modeRadioGroup = doc.getElementById("dohCategoryRadioGroup");
+ let uriTextbox = doc.getElementById("dohEnabledInputField");
+ let resolverMenulist = doc.getElementById("dohStrictResolverChoices");
+ let modePrefChangedPromise;
+ let uriPrefChangedPromise;
+ let disableHeuristicsPrefChangedPromise;
+
+ modeRadioGroup.scrollIntoView();
+
+ if (props.hasOwnProperty("expectedSelectedIndex")) {
+ await TestUtils.waitForCondition(
+ () => modeRadioGroup.selectedIndex === props.expectedSelectedIndex
+ );
+ is(
+ modeRadioGroup.selectedIndex,
+ props.expectedSelectedIndex,
+ "dohCategoryRadioGroup has expected selected index"
+ );
+ }
+ if (props.hasOwnProperty("expectedUriValue")) {
+ await TestUtils.waitForCondition(
+ () => uriTextbox.value === props.expectedUriValue
+ );
+ is(
+ uriTextbox.value,
+ props.expectedUriValue,
+ "URI textbox has expected value"
+ );
+ }
+ if (props.hasOwnProperty("expectedResolverListValue")) {
+ await TestUtils.waitForCondition(
+ () => resolverMenulist.value === props.expectedResolverListValue
+ );
+ is(
+ resolverMenulist.value,
+ props.expectedResolverListValue,
+ "resolver menulist has expected value"
+ );
+ }
+
+ if (props.clickMode) {
+ await clearEvents();
+ info(
+ Date.now() -
+ startTime +
+ ": testWithProperties: clickMode, waiting for the pref observer"
+ );
+ modePrefChangedPromise = waitForPrefObserver(TRR_MODE_PREF);
+ if (props.hasOwnProperty("expectedDisabledHeuristics")) {
+ disableHeuristicsPrefChangedPromise = waitForPrefObserver(
+ HEURISTICS_DISABLED_PREF
+ );
+ }
+ info(
+ Date.now() - startTime + ": testWithProperties: clickMode, pref changed"
+ );
+ let option = doc.getElementById(props.clickMode);
+ option.scrollIntoView();
+ let win = doc.ownerGlobal;
+ EventUtils.synthesizeMouseAtCenter(option, {}, win);
+ info(
+ `${Date.now() - startTime} : testWithProperties: clickMode=${
+ props.clickMode
+ }, mouse click synthesized`
+ );
+ let clickEvent = await getEvent("security.doh.settings", "mode_changed");
+ Assert.deepEqual(clickEvent, [
+ "security.doh.settings",
+ "mode_changed",
+ "button",
+ props.clickMode,
+ ]);
+ }
+ if (props.hasOwnProperty("selectResolver")) {
+ await clearEvents();
+ info(
+ Date.now() -
+ startTime +
+ ": testWithProperties: selectResolver, creating change event"
+ );
+ resolverMenulist.focus();
+ resolverMenulist.value = props.selectResolver;
+ resolverMenulist.dispatchEvent(new Event("input", { bubbles: true }));
+ resolverMenulist.dispatchEvent(new Event("command", { bubbles: true }));
+ info(
+ Date.now() -
+ startTime +
+ ": testWithProperties: selectResolver, item value set and events dispatched"
+ );
+ let choiceEvent = await getEvent(
+ "security.doh.settings",
+ "provider_choice"
+ );
+ Assert.deepEqual(choiceEvent, [
+ "security.doh.settings",
+ "provider_choice",
+ "value",
+ props.selectResolver,
+ ]);
+ }
+ if (props.hasOwnProperty("inputUriKeys")) {
+ info(
+ Date.now() -
+ startTime +
+ ": testWithProperties: inputUriKeys, waiting for the pref observer"
+ );
+ uriPrefChangedPromise = waitForPrefObserver(TRR_CUSTOM_URI_PREF);
+ info(
+ Date.now() -
+ startTime +
+ ": testWithProperties: inputUriKeys, pref changed, now enter the new value"
+ );
+ let win = doc.ownerGlobal;
+ uriTextbox.focus();
+ uriTextbox.value = props.inputUriKeys;
+ uriTextbox.dispatchEvent(new win.Event("input", { bubbles: true }));
+ uriTextbox.dispatchEvent(new win.Event("change", { bubbles: true }));
+ info(
+ Date.now() -
+ startTime +
+ ": testWithProperties: inputUriKeys, input and change events dispatched"
+ );
+ }
+
+ info(
+ Date.now() -
+ startTime +
+ ": testWithProperties: waiting for any of uri and mode prefs to change"
+ );
+ await Promise.all([
+ uriPrefChangedPromise,
+ modePrefChangedPromise,
+ disableHeuristicsPrefChangedPromise,
+ ]);
+ info(Date.now() - startTime + ": testWithProperties: prefs changed");
+
+ if (props.hasOwnProperty("expectedFinalUriPref")) {
+ if (props.expectedFinalUriPref) {
+ let uriPref = Services.prefs.getStringPref(TRR_URI_PREF);
+ is(
+ uriPref,
+ props.expectedFinalUriPref,
+ "uri pref ended up with the expected value"
+ );
+ } else {
+ ok(
+ !Services.prefs.prefHasUserValue(TRR_URI_PREF),
+ `uri pref ended up with the expected value (unset) got ${Services.prefs.getStringPref(
+ TRR_URI_PREF
+ )}`
+ );
+ }
+ }
+
+ if (props.hasOwnProperty("expectedModePref")) {
+ let modePref = Services.prefs.getIntPref(TRR_MODE_PREF);
+ is(
+ modePref,
+ props.expectedModePref,
+ "mode pref ended up with the expected value"
+ );
+ }
+
+ if (props.hasOwnProperty("expectedDisabledHeuristics")) {
+ let disabledHeuristicsPref = Services.prefs.getBoolPref(
+ HEURISTICS_DISABLED_PREF
+ );
+ is(
+ disabledHeuristicsPref,
+ props.expectedDisabledHeuristics,
+ "disable-heuristics pref ended up with the expected value"
+ );
+ }
+
+ if (props.hasOwnProperty("expectedFinalCustomUriPref")) {
+ let customUriPref = Services.prefs.getStringPref(TRR_CUSTOM_URI_PREF);
+ is(
+ customUriPref,
+ props.expectedFinalCustomUriPref,
+ "custom_uri pref ended up with the expected value"
+ );
+ }
+
+ if (props.hasOwnProperty("expectedModeValue")) {
+ let modeValue = Services.prefs.getIntPref(TRR_MODE_PREF);
+ is(modeValue, props.expectedModeValue, "mode pref has expected value");
+ }
+
+ gBrowser.removeCurrentTab();
+ info(Date.now() - startTime + ": testWithProperties: fin");
+}
+
+add_task(async function default_values() {
+ let customUriPref = Services.prefs.getStringPref(TRR_CUSTOM_URI_PREF);
+ let uriPrefHasUserValue = Services.prefs.prefHasUserValue(TRR_URI_PREF);
+ let modePref = Services.prefs.getIntPref(TRR_MODE_PREF);
+ is(
+ modePref,
+ defaultPrefValues[TRR_MODE_PREF],
+ `Actual value of ${TRR_MODE_PREF} matches expected default value`
+ );
+ ok(
+ !uriPrefHasUserValue,
+ `Actual value of ${TRR_URI_PREF} matches expected default value (unset)`
+ );
+ is(
+ customUriPref,
+ defaultPrefValues[TRR_CUSTOM_URI_PREF],
+ `Actual value of ${TRR_CUSTOM_URI_PREF} matches expected default value`
+ );
+});
+
+const DEFAULT_OPTION_INDEX = 0;
+const ENABLED_OPTION_INDEX = 1;
+const STRICT_OPTION_INDEX = 2;
+const OFF_OPTION_INDEX = 3;
+
+let testVariations = [
+ // verify state with defaults
+ {
+ name: "default",
+ expectedModePref: 0,
+ expectedSelectedIndex: DEFAULT_OPTION_INDEX,
+ expectedUriValue: "",
+ },
+
+ // verify each of the modes maps to the correct checked state
+ {
+ name: "mode 0",
+ [TRR_MODE_PREF]: 0,
+ expectedSelectedIndex: DEFAULT_OPTION_INDEX,
+ },
+ {
+ name: "mode 1",
+ [TRR_MODE_PREF]: 1,
+ expectedSelectedIndex: OFF_OPTION_INDEX,
+ },
+ {
+ name: "mode 2",
+ [TRR_MODE_PREF]: 2,
+ expectedSelectedIndex: ENABLED_OPTION_INDEX,
+ expectedFinalUriPref: "",
+ },
+ {
+ name: "mode 3",
+ [TRR_MODE_PREF]: 3,
+ expectedSelectedIndex: STRICT_OPTION_INDEX,
+ expectedFinalUriPref: "",
+ },
+ {
+ name: "mode 4",
+ [TRR_MODE_PREF]: 4,
+ expectedSelectedIndex: OFF_OPTION_INDEX,
+ },
+ {
+ name: "mode 5",
+ [TRR_MODE_PREF]: 5,
+ expectedSelectedIndex: OFF_OPTION_INDEX,
+ },
+ // verify an out of bounds mode value maps to the correct checked state
+ {
+ name: "mode out-of-bounds",
+ [TRR_MODE_PREF]: 77,
+ expectedSelectedIndex: OFF_OPTION_INDEX,
+ },
+
+ // verify automatic heuristics states
+ {
+ name: "heuristics on and mode unset",
+ [TRR_MODE_PREF]: 0,
+ [ROLLOUT_ENABLED_PREF]: true,
+ expectedSelectedIndex: DEFAULT_OPTION_INDEX,
+ },
+ {
+ name: "heuristics on and mode set to 2",
+ [TRR_MODE_PREF]: 2,
+ [ROLLOUT_ENABLED_PREF]: true,
+ expectedSelectedIndex: ENABLED_OPTION_INDEX,
+ },
+ {
+ name: "heuristics on but disabled, mode unset",
+ [TRR_MODE_PREF]: 5,
+ [ROLLOUT_ENABLED_PREF]: true,
+ expectedSelectedIndex: OFF_OPTION_INDEX,
+ },
+ {
+ name: "heuristics on but disabled, mode set to 2",
+ [TRR_MODE_PREF]: 2,
+ [ROLLOUT_ENABLED_PREF]: true,
+ expectedSelectedIndex: ENABLED_OPTION_INDEX,
+ },
+
+ // verify picking each radio button option gives the right outcomes
+ {
+ name: "toggle mode on",
+ clickMode: "dohEnabledRadio",
+ expectedModeValue: 2,
+ expectedUriValue: "",
+ expectedFinalUriPref: "",
+ },
+ {
+ name: "toggle mode off",
+ [TRR_MODE_PREF]: 2,
+ expectedSelectedIndex: ENABLED_OPTION_INDEX,
+ clickMode: "dohOffRadio",
+ expectedModePref: 5,
+ },
+ {
+ name: "toggle mode off when on due to heuristics",
+ [TRR_MODE_PREF]: 0,
+ [ROLLOUT_ENABLED_PREF]: true,
+ expectedSelectedIndex: DEFAULT_OPTION_INDEX,
+ clickMode: "dohOffRadio",
+ expectedModePref: 5,
+ expectedDisabledHeuristics: true,
+ },
+ // Test selecting non-default, non-custom TRR provider, NextDNS.
+ {
+ name: "Select NextDNS as TRR provider",
+ [TRR_MODE_PREF]: 2,
+ selectResolver: SECOND_RESOLVER_VALUE,
+ expectedFinalUriPref: SECOND_RESOLVER_VALUE,
+ },
+ // Test selecting non-default, non-custom TRR provider, NextDNS,
+ // with DoH not enabled. The provider selection should stick.
+ {
+ name: "Select NextDNS as TRR provider in mode 0",
+ [TRR_MODE_PREF]: 0,
+ selectResolver: SECOND_RESOLVER_VALUE,
+ expectedFinalUriPref: SECOND_RESOLVER_VALUE,
+ },
+ {
+ name: "return to default from NextDNS",
+ [TRR_MODE_PREF]: 2,
+ [TRR_URI_PREF]: SECOND_RESOLVER_VALUE,
+ expectedResolverListValue: SECOND_RESOLVER_VALUE,
+ selectResolver: DEFAULT_RESOLVER_VALUE,
+ expectedFinalUriPref: FIRST_RESOLVER_VALUE,
+ },
+ // test that selecting Custom, when we have a TRR_CUSTOM_URI_PREF subsequently changes TRR_URI_PREF
+ {
+ name: "select custom with existing custom_uri pref value",
+ [TRR_MODE_PREF]: 2,
+ [TRR_CUSTOM_URI_PREF]: "https://example.com",
+ expectedModeValue: 2,
+ expectedSelectedIndex: ENABLED_OPTION_INDEX,
+ selectResolver: "custom",
+ expectedUriValue: "https://example.com",
+ expectedFinalUriPref: "https://example.com",
+ expectedFinalCustomUriPref: "https://example.com",
+ },
+ {
+ name: "select custom and enter new custom_uri pref value",
+ [TRR_URI_PREF]: "",
+ [TRR_CUSTOM_URI_PREF]: "",
+ clickMode: "dohEnabledRadio",
+ selectResolver: "custom",
+ inputUriKeys: "https://custom.com",
+ expectedModePref: 2,
+ expectedFinalUriPref: "https://custom.com",
+ expectedFinalCustomUriPref: "https://custom.com",
+ },
+
+ {
+ name: "return to default from custom",
+ [TRR_MODE_PREF]: 2,
+ [TRR_URI_PREF]: "https://example.com",
+ [TRR_CUSTOM_URI_PREF]: "https://custom.com",
+ expectedUriValue: "https://example.com",
+ expectedResolverListValue: "custom",
+ selectResolver: DEFAULT_RESOLVER_VALUE,
+ expectedFinalUriPref: DEFAULT_RESOLVER_VALUE,
+ expectedFinalCustomUriPref: "https://example.com",
+ },
+ {
+ name: "clear the custom uri",
+ [TRR_MODE_PREF]: 2,
+ [TRR_URI_PREF]: "https://example.com",
+ [TRR_CUSTOM_URI_PREF]: "https://example.com",
+ expectedUriValue: "https://example.com",
+ expectedResolverListValue: "custom",
+ inputUriKeys: "",
+ expectedFinalUriPref: " ",
+ expectedFinalCustomUriPref: "",
+ },
+ {
+ name: "empty default resolver list",
+ [TRR_MODE_PREF]: 2,
+ [TRR_URI_PREF]: "https://example.com",
+ [TRR_CUSTOM_URI_PREF]: "",
+ expectedUriValue: "https://example.com",
+ expectedResolverListValue: "custom",
+ expectedFinalUriPref: "https://example.com",
+ expectedFinalCustomUriPref: "https://example.com",
+ },
+];
+
+for (let props of testVariations) {
+ add_task(async function testVariation() {
+ let startTime = Date.now();
+ info("starting test: " + props.name);
+ await testWithProperties(props, startTime);
+ await resetPrefs();
+ });
+}
+
+add_task(async function testRemoteSettingsEnable() {
+ let startTime = Date.now();
+ // Enable the rollout.
+ await DoHTestUtils.loadRemoteSettingsConfig({
+ providers: "example-1, example-2",
+ rolloutEnabled: true,
+ steeringEnabled: false,
+ steeringProviders: "",
+ autoDefaultEnabled: false,
+ autoDefaultProviders: "",
+ id: "global",
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ info(Date.now() - startTime + ": testWithProperties: tab now open");
+ let modeRadioGroup = doc.getElementById("dohCategoryRadioGroup");
+
+ is(modeRadioGroup.value, "0", "expecting default mode");
+
+ let status = doc.getElementById("dohStatus");
+ await TestUtils.waitForCondition(
+ () => document.l10n.getAttributes(status).args.status == "Active"
+ );
+ is(
+ document.l10n.getAttributes(status).args.status,
+ "Active",
+ "expecting status active"
+ );
+
+ let provider = doc.getElementById("dohResolver");
+ is(
+ provider.hidden,
+ false,
+ "Provider should not be hidden when status is active"
+ );
+ await TestUtils.waitForCondition(
+ () =>
+ document.l10n.getAttributes(provider).args.name ==
+ DoHConfigController.currentConfig.providerList[0].UIName
+ );
+ is(
+ document.l10n.getAttributes(provider).args.name,
+ DoHConfigController.currentConfig.providerList[0].UIName,
+ "expecting the right provider name"
+ );
+
+ let option = doc.getElementById("dohEnabledRadio");
+ option.scrollIntoView();
+ let win = doc.ownerGlobal;
+ EventUtils.synthesizeMouseAtCenter(option, {}, win);
+
+ await TestUtils.waitForCondition(() =>
+ Services.prefs.prefHasUserValue("doh-rollout.disable-heuristics")
+ );
+ is(provider.hidden, false);
+ await TestUtils.waitForCondition(
+ () =>
+ document.l10n.getAttributes(provider).args.name ==
+ DoHConfigController.currentConfig.providerList[0].UIName
+ );
+ is(
+ document.l10n.getAttributes(provider).args.name,
+ DoHConfigController.currentConfig.providerList[0].UIName,
+ "expecting the right provider name"
+ );
+ is(
+ Services.prefs.getIntPref("network.trr.mode"),
+ Ci.nsIDNSService.MODE_TRRFIRST
+ );
+
+ option = doc.getElementById("dohOffRadio");
+ option.scrollIntoView();
+ win = doc.ownerGlobal;
+ EventUtils.synthesizeMouseAtCenter(option, {}, win);
+ await TestUtils.waitForCondition(() => status.innerHTML == "Status: Off");
+ is(
+ Services.prefs.getIntPref("network.trr.mode"),
+ Ci.nsIDNSService.MODE_TRROFF
+ );
+ is(provider.hidden, true, "Expecting provider to be hidden when DoH is off");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function testEnterprisePolicy() {
+ async function withPolicy(policy, fn, preFn = () => {}) {
+ await resetPrefs();
+ PoliciesPrefTracker.start();
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson(policy);
+ await preFn();
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ let modeRadioGroup = doc.getElementById("dohCategoryRadioGroup");
+ let resolverMenulist = doc.getElementById("dohEnabledResolverChoices");
+ let uriTextbox = doc.getElementById("dohEnabledInputField");
+
+ await fn({
+ modeRadioGroup,
+ resolverMenulist,
+ doc,
+ uriTextbox,
+ });
+
+ gBrowser.removeCurrentTab();
+ EnterprisePolicyTesting.resetRunOnceState();
+ PoliciesPrefTracker.stop();
+ }
+
+ info("Check that a locked policy does not allow any changes in the UI");
+ await withPolicy(
+ {
+ policies: {
+ DNSOverHTTPS: {
+ Enabled: true,
+ ProviderURL: "https://examplelocked.com/provider",
+ ExcludedDomains: ["examplelocked.com", "example.org"],
+ Locked: true,
+ },
+ },
+ },
+ async res => {
+ is(res.modeRadioGroup.disabled, true, "The mode menu should be locked.");
+ is(res.modeRadioGroup.value, "2", "Should be enabled");
+ is(res.resolverMenulist.value, "custom", "Resolver list shows custom");
+ is(
+ res.uriTextbox.value,
+ "https://examplelocked.com/provider",
+ "Custom URI should be set"
+ );
+ }
+ );
+
+ info("Check that an unlocked policy has editable fields in the dialog");
+ await withPolicy(
+ {
+ policies: {
+ DNSOverHTTPS: {
+ Enabled: true,
+ ProviderURL: "https://example.com/provider",
+ ExcludedDomains: ["example.com", "example.org"],
+ },
+ },
+ },
+ async res => {
+ is(
+ res.modeRadioGroup.disabled,
+ false,
+ "The mode menu should not be locked."
+ );
+ is(res.modeRadioGroup.value, "2", "Should be enabled");
+ is(res.resolverMenulist.value, "custom", "Resolver list shows custom");
+ is(
+ res.uriTextbox.value,
+ "https://example.com/provider",
+ "Expected custom resolver"
+ );
+ }
+ );
+
+ info("Check that a locked disabled policy disables the buttons");
+ await withPolicy(
+ {
+ policies: {
+ DNSOverHTTPS: {
+ Enabled: false,
+ ProviderURL: "https://example.com/provider",
+ ExcludedDomains: ["example.com", "example.org"],
+ Locked: true,
+ },
+ },
+ },
+ async res => {
+ is(res.modeRadioGroup.disabled, true, "The mode menu should be locked.");
+ is(res.modeRadioGroup.value, "5", "Should be disabled");
+ }
+ );
+
+ info("Check that an unlocked disabled policy has editable fields");
+ await withPolicy(
+ {
+ policies: {
+ DNSOverHTTPS: {
+ Enabled: false,
+ ProviderURL: "https://example.com/provider",
+ ExcludedDomains: ["example.com", "example.org"],
+ },
+ },
+ },
+ async res => {
+ is(
+ res.modeRadioGroup.disabled,
+ false,
+ "The mode menu should not be locked."
+ );
+ is(res.modeRadioGroup.value, "5", "Should be disabled");
+ }
+ );
+
+ info("Check that the remote settings config doesn't override the policy");
+ await withPolicy(
+ {
+ policies: {
+ DNSOverHTTPS: {
+ Enabled: true,
+ ProviderURL: "https://example.com/provider",
+ ExcludedDomains: ["example.com", "example.org"],
+ },
+ },
+ },
+ async res => {
+ is(
+ res.modeRadioGroup.disabled,
+ false,
+ "The mode menu should not be locked."
+ );
+ is(res.resolverMenulist.value, "custom", "Resolver list shows custom");
+ is(
+ res.uriTextbox.value,
+ "https://example.com/provider",
+ "Expected custom resolver"
+ );
+ },
+ async function runAfterSettingPolicy() {
+ await DoHTestUtils.loadRemoteSettingsConfig({
+ providers: "example-1, example-2",
+ rolloutEnabled: true,
+ steeringEnabled: false,
+ steeringProviders: "",
+ autoDefaultEnabled: false,
+ autoDefaultProviders: "",
+ id: "global",
+ });
+ }
+ );
+});
+
+add_task(async function clickWarnButton() {
+ Services.prefs.setBoolPref(
+ "network.trr_ui.show_fallback_warning_option",
+ true
+ );
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ await clearEvents();
+ let checkbox = doc.getElementById("dohWarnCheckbox1");
+ checkbox.click();
+
+ let event = await getEvent("security.doh.settings", "warn_checkbox");
+ Assert.deepEqual(event, [
+ "security.doh.settings",
+ "warn_checkbox",
+ "checkbox",
+ "true",
+ ]);
+ Assert.equal(
+ Services.prefs.getBoolPref("network.trr.display_fallback_warning"),
+ true,
+ "Clicking the checkbox should change the pref"
+ );
+
+ checkbox.click();
+ event = await getEvent("security.doh.settings", "warn_checkbox");
+ Assert.deepEqual(event, [
+ "security.doh.settings",
+ "warn_checkbox",
+ "checkbox",
+ "false",
+ ]);
+ Assert.equal(
+ Services.prefs.getBoolPref("network.trr.display_fallback_warning"),
+ false,
+ "Clicking the checkbox should change the pref"
+ );
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_privacy_firefoxSuggest.js b/browser/components/preferences/tests/browser_privacy_firefoxSuggest.js
new file mode 100644
index 0000000000..883e19acc2
--- /dev/null
+++ b/browser/components/preferences/tests/browser_privacy_firefoxSuggest.js
@@ -0,0 +1,855 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This tests the Privacy pane's Firefox Suggest UI.
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ QuickSuggest: "resource:///modules/QuickSuggest.sys.mjs",
+});
+
+XPCOMUtils.defineLazyGetter(this, "QuickSuggestTestUtils", () => {
+ const { QuickSuggestTestUtils: module } = ChromeUtils.importESModule(
+ "resource://testing-common/QuickSuggestTestUtils.sys.mjs"
+ );
+ module.init(this);
+ return module;
+});
+
+const CONTAINER_ID = "firefoxSuggestContainer";
+const NONSPONSORED_TOGGLE_ID = "firefoxSuggestNonsponsoredToggle";
+const SPONSORED_TOGGLE_ID = "firefoxSuggestSponsoredToggle";
+const DATA_COLLECTION_TOGGLE_ID = "firefoxSuggestDataCollectionToggle";
+const INFO_BOX_ID = "firefoxSuggestInfoBox";
+const INFO_TEXT_ID = "firefoxSuggestInfoText";
+const LEARN_MORE_CLASS = "firefoxSuggestLearnMore";
+const BEST_MATCH_CONTAINER_ID = "firefoxSuggestBestMatchContainer";
+const BEST_MATCH_CHECKBOX_ID = "firefoxSuggestBestMatch";
+const BUTTON_RESTORE_DISMISSED_ID = "restoreDismissedSuggestions";
+const PREF_URLBAR_QUICKSUGGEST_BLOCKLIST =
+ "browser.urlbar.quicksuggest.blockedDigests";
+const PREF_URLBAR_WEATHER_USER_ENABLED = "browser.urlbar.suggest.weather";
+
+// Maps text element IDs to `{ enabled, disabled }`, where `enabled` is the
+// expected l10n ID when the Firefox Suggest feature is enabled, and `disabled`
+// is when disabled.
+const EXPECTED_L10N_IDS = {
+ locationBarGroupHeader: {
+ enabled: "addressbar-header-firefox-suggest",
+ disabled: "addressbar-header",
+ },
+ locationBarSuggestionLabel: {
+ enabled: "addressbar-suggest-firefox-suggest",
+ disabled: "addressbar-suggest",
+ },
+};
+
+// This test can take a while due to the many permutations some of these tasks
+// run through, so request a longer timeout.
+requestLongerTimeout(10);
+
+// The following tasks check the visibility of the Firefox Suggest UI based on
+// the value of the feature pref. See doVisibilityTest().
+
+add_task(async function historyToOffline() {
+ await doVisibilityTest({
+ initialScenario: "history",
+ initialExpectedVisibility: false,
+ newScenario: "offline",
+ newExpectedVisibility: true,
+ });
+});
+
+add_task(async function historyToOnline() {
+ await doVisibilityTest({
+ initialScenario: "history",
+ initialExpectedVisibility: false,
+ newScenario: "online",
+ newExpectedVisibility: true,
+ });
+});
+
+add_task(async function offlineToHistory() {
+ await doVisibilityTest({
+ initialScenario: "offline",
+ initialExpectedVisibility: true,
+ newScenario: "history",
+ newExpectedVisibility: false,
+ });
+});
+
+add_task(async function offlineToOnline() {
+ await doVisibilityTest({
+ initialScenario: "offline",
+ initialExpectedVisibility: true,
+ newScenario: "online",
+ newExpectedVisibility: true,
+ });
+});
+
+add_task(async function onlineToHistory() {
+ await doVisibilityTest({
+ initialScenario: "online",
+ initialExpectedVisibility: true,
+ newScenario: "history",
+ newExpectedVisibility: false,
+ });
+});
+
+add_task(async function onlineToOffline() {
+ await doVisibilityTest({
+ initialScenario: "online",
+ initialExpectedVisibility: true,
+ newScenario: "offline",
+ newExpectedVisibility: true,
+ });
+});
+
+/**
+ * Runs a test that checks the visibility of the Firefox Suggest preferences UI
+ * based on scenario pref.
+ *
+ * @param {string} initialScenario
+ * The initial scenario.
+ * @param {boolean} initialExpectedVisibility
+ * Whether the UI should be visible with the initial scenario.
+ * @param {string} newScenario
+ * The updated scenario.
+ * @param {boolean} newExpectedVisibility
+ * Whether the UI should be visible after setting the new scenario.
+ */
+async function doVisibilityTest({
+ initialScenario,
+ initialExpectedVisibility,
+ newScenario,
+ newExpectedVisibility,
+}) {
+ info(
+ "Running visibility test: " +
+ JSON.stringify(
+ {
+ initialScenario,
+ initialExpectedVisibility,
+ newScenario,
+ newExpectedVisibility,
+ },
+ null,
+ 2
+ )
+ );
+
+ // Set the initial scenario.
+ await QuickSuggestTestUtils.setScenario(initialScenario);
+
+ Assert.equal(
+ Services.prefs.getBoolPref("browser.urlbar.quicksuggest.enabled"),
+ initialExpectedVisibility,
+ `quicksuggest.enabled is correct after setting initial scenario, initialExpectedVisibility=${initialExpectedVisibility}`
+ );
+
+ // Open prefs and check the initial visibility.
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let container = doc.getElementById(CONTAINER_ID);
+ Assert.equal(
+ BrowserTestUtils.is_visible(container),
+ initialExpectedVisibility,
+ `The container has the expected initial visibility, initialExpectedVisibility=${initialExpectedVisibility}`
+ );
+
+ // Check the text elements' l10n IDs.
+ for (let [id, { enabled, disabled }] of Object.entries(EXPECTED_L10N_IDS)) {
+ Assert.equal(
+ doc.getElementById(id).dataset.l10nId,
+ initialExpectedVisibility ? enabled : disabled,
+ `Initial l10n ID for element with ID ${id}, initialExpectedVisibility=${initialExpectedVisibility}`
+ );
+ }
+
+ // Set the new scenario.
+ await QuickSuggestTestUtils.setScenario(newScenario);
+
+ Assert.equal(
+ Services.prefs.getBoolPref("browser.urlbar.quicksuggest.enabled"),
+ newExpectedVisibility,
+ `quicksuggest.enabled is correct after setting new scenario, newExpectedVisibility=${newExpectedVisibility}`
+ );
+
+ // Check visibility again.
+ Assert.equal(
+ BrowserTestUtils.is_visible(container),
+ newExpectedVisibility,
+ `The container has the expected visibility after setting new scenario, newExpectedVisibility=${newExpectedVisibility}`
+ );
+
+ // Check the text elements' l10n IDs again.
+ for (let [id, { enabled, disabled }] of Object.entries(EXPECTED_L10N_IDS)) {
+ Assert.equal(
+ doc.getElementById(id).dataset.l10nId,
+ newExpectedVisibility ? enabled : disabled,
+ `New l10n ID for element with ID ${id}, newExpectedVisibility=${newExpectedVisibility}`
+ );
+ }
+
+ // Clean up.
+ gBrowser.removeCurrentTab();
+ await QuickSuggestTestUtils.setScenario(null);
+}
+
+// Verifies all 8 states of the 3 toggles and their related info box states.
+add_task(async function togglesAndInfoBox() {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ // suggest.quicksuggest.nonsponsored = true
+ // suggest.quicksuggest.sponsored = true
+ // quicksuggest.dataCollection.enabled = true
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.quicksuggest.nonsponsored", true],
+ ["browser.urlbar.suggest.quicksuggest.sponsored", true],
+ ["browser.urlbar.quicksuggest.dataCollection.enabled", true],
+ ],
+ });
+ assertPrefUIState({
+ [NONSPONSORED_TOGGLE_ID]: true,
+ [SPONSORED_TOGGLE_ID]: true,
+ [DATA_COLLECTION_TOGGLE_ID]: true,
+ });
+ await assertInfoBox("addressbar-firefox-suggest-info-all");
+ await SpecialPowers.popPrefEnv();
+
+ // suggest.quicksuggest.nonsponsored = true
+ // suggest.quicksuggest.sponsored = true
+ // quicksuggest.dataCollection.enabled = false
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.quicksuggest.nonsponsored", true],
+ ["browser.urlbar.suggest.quicksuggest.sponsored", true],
+ ["browser.urlbar.quicksuggest.dataCollection.enabled", false],
+ ],
+ });
+ assertPrefUIState({
+ [NONSPONSORED_TOGGLE_ID]: true,
+ [SPONSORED_TOGGLE_ID]: true,
+ [DATA_COLLECTION_TOGGLE_ID]: false,
+ });
+ await assertInfoBox("addressbar-firefox-suggest-info-nonsponsored-sponsored");
+ await SpecialPowers.popPrefEnv();
+
+ // suggest.quicksuggest.nonsponsored = true
+ // suggest.quicksuggest.sponsored = false
+ // quicksuggest.dataCollection.enabled = true
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.quicksuggest.nonsponsored", true],
+ ["browser.urlbar.suggest.quicksuggest.sponsored", false],
+ ["browser.urlbar.quicksuggest.dataCollection.enabled", true],
+ ],
+ });
+ assertPrefUIState({
+ [NONSPONSORED_TOGGLE_ID]: true,
+ [SPONSORED_TOGGLE_ID]: false,
+ [DATA_COLLECTION_TOGGLE_ID]: true,
+ });
+ await assertInfoBox("addressbar-firefox-suggest-info-nonsponsored-data");
+ await SpecialPowers.popPrefEnv();
+
+ // suggest.quicksuggest.nonsponsored = true
+ // suggest.quicksuggest.sponsored = false
+ // quicksuggest.dataCollection.enabled = false
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.quicksuggest.nonsponsored", true],
+ ["browser.urlbar.suggest.quicksuggest.sponsored", false],
+ ["browser.urlbar.quicksuggest.dataCollection.enabled", false],
+ ],
+ });
+ assertPrefUIState({
+ [NONSPONSORED_TOGGLE_ID]: true,
+ [SPONSORED_TOGGLE_ID]: false,
+ [DATA_COLLECTION_TOGGLE_ID]: false,
+ });
+ await assertInfoBox("addressbar-firefox-suggest-info-nonsponsored");
+ await SpecialPowers.popPrefEnv();
+
+ // suggest.quicksuggest.nonsponsored = false
+ // suggest.quicksuggest.sponsored = true
+ // quicksuggest.dataCollection.enabled = true
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.quicksuggest.nonsponsored", false],
+ ["browser.urlbar.suggest.quicksuggest.sponsored", true],
+ ["browser.urlbar.quicksuggest.dataCollection.enabled", true],
+ ],
+ });
+ assertPrefUIState({
+ [NONSPONSORED_TOGGLE_ID]: false,
+ [SPONSORED_TOGGLE_ID]: true,
+ [DATA_COLLECTION_TOGGLE_ID]: true,
+ });
+ await assertInfoBox("addressbar-firefox-suggest-info-sponsored-data");
+ await SpecialPowers.popPrefEnv();
+
+ // suggest.quicksuggest.nonsponsored = false
+ // suggest.quicksuggest.sponsored = true
+ // quicksuggest.dataCollection.enabled = false
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.quicksuggest.nonsponsored", false],
+ ["browser.urlbar.suggest.quicksuggest.sponsored", true],
+ ["browser.urlbar.quicksuggest.dataCollection.enabled", false],
+ ],
+ });
+ assertPrefUIState({
+ [NONSPONSORED_TOGGLE_ID]: false,
+ [SPONSORED_TOGGLE_ID]: true,
+ [DATA_COLLECTION_TOGGLE_ID]: false,
+ });
+ await assertInfoBox("addressbar-firefox-suggest-info-sponsored");
+ await SpecialPowers.popPrefEnv();
+
+ // suggest.quicksuggest.nonsponsored = false
+ // suggest.quicksuggest.sponsored = false
+ // quicksuggest.dataCollection.enabled = true
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.quicksuggest.nonsponsored", false],
+ ["browser.urlbar.suggest.quicksuggest.sponsored", false],
+ ["browser.urlbar.quicksuggest.dataCollection.enabled", true],
+ ],
+ });
+ assertPrefUIState({
+ [NONSPONSORED_TOGGLE_ID]: false,
+ [SPONSORED_TOGGLE_ID]: false,
+ [DATA_COLLECTION_TOGGLE_ID]: true,
+ });
+ await assertInfoBox("addressbar-firefox-suggest-info-data");
+ await SpecialPowers.popPrefEnv();
+
+ // suggest.quicksuggest.nonsponsored = false
+ // suggest.quicksuggest.sponsored = false
+ // quicksuggest.dataCollection.enabled = false
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.quicksuggest.nonsponsored", false],
+ ["browser.urlbar.suggest.quicksuggest.sponsored", false],
+ ["browser.urlbar.quicksuggest.dataCollection.enabled", false],
+ ],
+ });
+ assertPrefUIState({
+ [NONSPONSORED_TOGGLE_ID]: false,
+ [SPONSORED_TOGGLE_ID]: false,
+ [DATA_COLLECTION_TOGGLE_ID]: false,
+ });
+ await assertInfoBox(null);
+ await SpecialPowers.popPrefEnv();
+
+ gBrowser.removeCurrentTab();
+});
+
+// Clicks each of the toggles and makes sure the prefs and info box are updated.
+add_task(async function clickToggles() {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let addressBarSection = doc.getElementById("locationBarGroup");
+ addressBarSection.scrollIntoView();
+
+ async function clickToggle(id) {
+ let toggle = doc.getElementById(id);
+ let changed = BrowserTestUtils.waitForEvent(toggle, "toggle");
+ let button = toggle.buttonEl;
+ await EventUtils.synthesizeMouseAtCenter(
+ button,
+ {},
+ gBrowser.selectedBrowser.contentWindow
+ );
+ await changed;
+ }
+
+ // Set initial state.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.quicksuggest.nonsponsored", true],
+ ["browser.urlbar.suggest.quicksuggest.sponsored", true],
+ ["browser.urlbar.quicksuggest.dataCollection.enabled", true],
+ ],
+ });
+ assertPrefUIState({
+ [NONSPONSORED_TOGGLE_ID]: true,
+ [SPONSORED_TOGGLE_ID]: true,
+ [DATA_COLLECTION_TOGGLE_ID]: true,
+ });
+ await assertInfoBox("addressbar-firefox-suggest-info-all");
+
+ // non-sponsored toggle
+ await clickToggle(NONSPONSORED_TOGGLE_ID);
+ Assert.ok(
+ !Services.prefs.getBoolPref(
+ "browser.urlbar.suggest.quicksuggest.nonsponsored"
+ ),
+ "suggest.quicksuggest.nonsponsored is false after clicking non-sponsored toggle"
+ );
+ assertPrefUIState({
+ [NONSPONSORED_TOGGLE_ID]: false,
+ [SPONSORED_TOGGLE_ID]: true,
+ [DATA_COLLECTION_TOGGLE_ID]: true,
+ });
+ await assertInfoBox("addressbar-firefox-suggest-info-sponsored-data");
+
+ // sponsored toggle
+ await clickToggle(SPONSORED_TOGGLE_ID);
+ Assert.ok(
+ !Services.prefs.getBoolPref(
+ "browser.urlbar.suggest.quicksuggest.nonsponsored"
+ ),
+ "suggest.quicksuggest.nonsponsored remains false after clicking sponsored toggle"
+ );
+ Assert.ok(
+ !Services.prefs.getBoolPref(
+ "browser.urlbar.suggest.quicksuggest.sponsored"
+ ),
+ "suggest.quicksuggest.sponsored is false after clicking sponsored toggle"
+ );
+ assertPrefUIState({
+ [NONSPONSORED_TOGGLE_ID]: false,
+ [SPONSORED_TOGGLE_ID]: false,
+ [DATA_COLLECTION_TOGGLE_ID]: true,
+ });
+ await assertInfoBox("addressbar-firefox-suggest-info-data");
+
+ // data collection toggle
+ await clickToggle(DATA_COLLECTION_TOGGLE_ID);
+ Assert.ok(
+ !Services.prefs.getBoolPref(
+ "browser.urlbar.suggest.quicksuggest.nonsponsored"
+ ),
+ "suggest.quicksuggest.nonsponsored remains false after clicking sponsored toggle"
+ );
+ Assert.ok(
+ !Services.prefs.getBoolPref(
+ "browser.urlbar.suggest.quicksuggest.sponsored"
+ ),
+ "suggest.quicksuggest.sponsored remains false after clicking data collection toggle"
+ );
+ Assert.ok(
+ !Services.prefs.getBoolPref(
+ "browser.urlbar.quicksuggest.dataCollection.enabled"
+ ),
+ "quicksuggest.dataCollection.enabled is false after clicking data collection toggle"
+ );
+ assertPrefUIState({
+ [NONSPONSORED_TOGGLE_ID]: false,
+ [SPONSORED_TOGGLE_ID]: false,
+ [DATA_COLLECTION_TOGGLE_ID]: false,
+ });
+ await assertInfoBox(null);
+
+ gBrowser.removeCurrentTab();
+ await SpecialPowers.popPrefEnv();
+});
+
+// Clicks the learn-more links and checks the help page is opened in a new tab.
+add_task(async function clickLearnMore() {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let addressBarSection = doc.getElementById("locationBarGroup");
+ addressBarSection.scrollIntoView();
+
+ // Set initial state so that the info box and learn more link are shown.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.quicksuggest.nonsponsored", true],
+ ["browser.urlbar.suggest.quicksuggest.sponsored", true],
+ ["browser.urlbar.quicksuggest.dataCollection.enabled", true],
+ ],
+ });
+ await assertInfoBox("addressbar-firefox-suggest-info-all");
+
+ let learnMoreLinks = doc.querySelectorAll("." + LEARN_MORE_CLASS);
+ Assert.equal(
+ learnMoreLinks.length,
+ 3,
+ "Expected number of learn-more links are present"
+ );
+ for (let link of learnMoreLinks) {
+ Assert.ok(
+ BrowserTestUtils.is_visible(link),
+ "Learn-more link is visible: " + link.id
+ );
+ }
+
+ let prefsTab = gBrowser.selectedTab;
+ for (let link of learnMoreLinks) {
+ let tabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ QuickSuggest.HELP_URL
+ );
+ info("Clicking learn-more link: " + link.id);
+ Assert.ok(link.id, "Sanity check: Learn-more link has an ID");
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#" + link.id,
+ {},
+ gBrowser.selectedBrowser
+ );
+ info("Waiting for help page to load in a new tab");
+ await tabPromise;
+ gBrowser.removeCurrentTab();
+ gBrowser.selectedTab = prefsTab;
+ }
+
+ gBrowser.removeCurrentTab();
+ await SpecialPowers.popPrefEnv();
+});
+
+// Tests the visibility of the best match checkbox based on the values of
+// `browser.urlbar.quicksuggest.enabled` and `browser.urlbar.bestMatch.enabled`.
+add_task(async function bestMatchVisibility() {
+ for (let initialQuickSuggest of [false, true]) {
+ for (let initialBestMatch of [false, true]) {
+ for (let newQuickSuggest of [false, true]) {
+ for (let newBestMatch of [false, true]) {
+ await doBestMatchVisibilityTest({
+ initialQuickSuggest,
+ initialBestMatch,
+ newQuickSuggest,
+ newBestMatch,
+ });
+ }
+ }
+ }
+ }
+});
+
+// Tests the "Restore" button for dismissed suggestions.
+add_task(async function restoreDismissedSuggestions() {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let addressBarSection = doc.getElementById("locationBarGroup");
+ addressBarSection.scrollIntoView();
+
+ let button = doc.getElementById(BUTTON_RESTORE_DISMISSED_ID);
+ Assert.equal(
+ Services.prefs.getStringPref(PREF_URLBAR_QUICKSUGGEST_BLOCKLIST, ""),
+ "",
+ "Block list is empty initially"
+ );
+ Assert.ok(
+ Services.prefs.getBoolPref(PREF_URLBAR_WEATHER_USER_ENABLED),
+ "Weather suggestions are enabled initially"
+ );
+ Assert.ok(button.disabled, "Restore button is disabled initially.");
+
+ await QuickSuggest.blockedSuggestions.add("https://example.com/");
+ Assert.notEqual(
+ Services.prefs.getStringPref(PREF_URLBAR_QUICKSUGGEST_BLOCKLIST, ""),
+ "",
+ "Block list is non-empty after adding URL"
+ );
+ Assert.ok(!button.disabled, "Restore button is enabled after blocking URL.");
+ button.click();
+ Assert.equal(
+ Services.prefs.getStringPref(PREF_URLBAR_QUICKSUGGEST_BLOCKLIST, ""),
+ "",
+ "Block list is empty clicking Restore button"
+ );
+ Assert.ok(button.disabled, "Restore button is disabled after clicking it.");
+
+ Services.prefs.setBoolPref(PREF_URLBAR_WEATHER_USER_ENABLED, false);
+ Assert.ok(
+ !button.disabled,
+ "Restore button is enabled after disabling weather suggestions."
+ );
+ button.click();
+ Assert.ok(
+ Services.prefs.getBoolPref(PREF_URLBAR_WEATHER_USER_ENABLED),
+ "Weather suggestions are enabled after clicking Restore button"
+ );
+ Assert.ok(
+ button.disabled,
+ "Restore button is disabled after clicking it again."
+ );
+
+ gBrowser.removeCurrentTab();
+ await SpecialPowers.popPrefEnv();
+});
+
+/**
+ * Runs a test that checks the visibility of the Firefox Suggest best match
+ * checkbox. It does the following:
+ *
+ * 1. Sets the quick suggest and best match feature prefs
+ * 2. Opens about:preferences and checks the visibility of the checkbox
+ * 3. Sets the quick suggest and best match feature prefs again
+ * 4. Checks the visibility of the checkbox again
+ *
+ * @param {boolean} initialQuickSuggest
+ * The value to set for `browser.urlbar.quicksuggest.enabled` before
+ * about:preferences is opened.
+ * @param {boolean} initialBestMatch
+ * The value to set for `browser.urlbar.bestMatch.enabled` before
+ * about:preferences is opened.
+ * @param {boolean} newQuickSuggest
+ * The value to set for `browser.urlbar.quicksuggest.enabled` while
+ * about:preferences is open.
+ * @param {boolean} newBestMatch
+ * The value to set for `browser.urlbar.bestMatch.enabled` while
+ * about:preferences is open.
+ */
+async function doBestMatchVisibilityTest({
+ initialQuickSuggest,
+ initialBestMatch,
+ newQuickSuggest,
+ newBestMatch,
+}) {
+ info(
+ "Running best match visibility test: " +
+ JSON.stringify(
+ {
+ initialQuickSuggest,
+ initialBestMatch,
+ newQuickSuggest,
+ newBestMatch,
+ },
+ null,
+ 2
+ )
+ );
+
+ // Set the initial pref values.
+ Services.prefs.setBoolPref(
+ "browser.urlbar.quicksuggest.enabled",
+ initialQuickSuggest
+ );
+ Services.prefs.setBoolPref(
+ "browser.urlbar.bestMatch.enabled",
+ initialBestMatch
+ );
+
+ // Open prefs and check the initial visibility.
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let container = doc.getElementById(BEST_MATCH_CONTAINER_ID);
+ Assert.equal(
+ BrowserTestUtils.is_visible(container),
+ initialBestMatch,
+ "The checkbox container has the expected initial visibility"
+ );
+
+ // Set the new pref values.
+ Services.prefs.setBoolPref(
+ "browser.urlbar.quicksuggest.enabled",
+ newQuickSuggest
+ );
+ Services.prefs.setBoolPref("browser.urlbar.bestMatch.enabled", newBestMatch);
+
+ // Check visibility again.
+ Assert.equal(
+ BrowserTestUtils.is_visible(container),
+ newBestMatch,
+ "The checkbox container has the expected visibility after setting prefs"
+ );
+
+ // Clean up.
+ gBrowser.removeCurrentTab();
+ Services.prefs.clearUserPref("browser.urlbar.quicksuggest.enabled");
+ Services.prefs.clearUserPref("browser.urlbar.bestMatch.enabled");
+}
+
+// Tests the visibility of the best match checkbox when the best match feature
+// is enabled via a Nimbus experiment before about:preferences is opened.
+add_task(async function bestMatchVisibility_experiment_beforeOpen() {
+ await QuickSuggestTestUtils.withExperiment({
+ valueOverrides: {
+ bestMatchEnabled: true,
+ },
+ callback: async () => {
+ await openPreferencesViaOpenPreferencesAPI("privacy", {
+ leaveOpen: true,
+ });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let container = doc.getElementById(BEST_MATCH_CONTAINER_ID);
+ Assert.ok(
+ BrowserTestUtils.is_visible(container),
+ "The checkbox container is visible"
+ );
+ gBrowser.removeCurrentTab();
+ },
+ });
+});
+
+// Tests the visibility of the best match checkbox when the best match feature
+// is enabled via a Nimbus experiment after about:preferences is opened.
+add_task(async function bestMatchVisibility_experiment_afterOpen() {
+ // Open prefs and check the initial visibility.
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let container = doc.getElementById(BEST_MATCH_CONTAINER_ID);
+ Assert.ok(
+ BrowserTestUtils.is_hidden(container),
+ "The checkbox container is hidden initially"
+ );
+
+ // Install an experiment with best match enabled.
+ await QuickSuggestTestUtils.withExperiment({
+ valueOverrides: {
+ bestMatchEnabled: true,
+ },
+ callback: () => {
+ Assert.ok(
+ BrowserTestUtils.is_visible(container),
+ "The checkbox container is visible after installing the experiment"
+ );
+ },
+ });
+
+ Assert.ok(
+ BrowserTestUtils.is_hidden(container),
+ "The checkbox container is hidden again after the experiment is uninstalled"
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+// Check the pref and the checkbox for best match.
+add_task(async function bestMatchToggle() {
+ // Enable the feature so that the toggle appears.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.bestMatch.enabled", true]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ const doc = gBrowser.selectedBrowser.contentDocument;
+ const checkbox = doc.getElementById(BEST_MATCH_CHECKBOX_ID);
+ checkbox.scrollIntoView();
+
+ info("Check if the checkbox stauts reflects the pref value");
+ for (const isEnabled of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.bestmatch", isEnabled]],
+ });
+ assertPrefUIState({ [BEST_MATCH_CHECKBOX_ID]: isEnabled }, "checked");
+ await SpecialPowers.popPrefEnv();
+ }
+
+ info("Check if the pref value reflects the checkbox status");
+ for (let i = 0; i < 2; i++) {
+ const initialValue = checkbox.checked;
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#" + BEST_MATCH_CHECKBOX_ID,
+ {},
+ gBrowser.selectedBrowser
+ );
+ Assert.ok(initialValue !== checkbox.checked);
+ Assert.equal(
+ Services.prefs.getBoolPref("browser.urlbar.suggest.bestmatch"),
+ checkbox.checked
+ );
+ }
+
+ // Clean up.
+ Services.prefs.clearUserPref("browser.urlbar.suggest.bestmatch");
+ gBrowser.removeCurrentTab();
+ await SpecialPowers.popPrefEnv();
+});
+
+// Clicks the learn-more link for best match and checks the help page is opened
+// in a new tab.
+add_task(async function clickBestMatchLearnMore() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.bestMatch.enabled", true]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ const doc = gBrowser.selectedBrowser.contentDocument;
+ const link = doc.getElementById("firefoxSuggestBestMatchLearnMore");
+ Assert.ok(BrowserTestUtils.is_visible(link), "Learn-more link is visible");
+
+ const tabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ QuickSuggest.HELP_URL
+ );
+
+ info("Clicking learn-more link");
+ link.scrollIntoView();
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#firefoxSuggestBestMatchLearnMore",
+ {},
+ gBrowser.selectedBrowser
+ );
+
+ info("Waiting for help page to load in a new tab");
+ const tab = await tabPromise;
+ gBrowser.removeTab(tab);
+
+ // Clean up.
+ gBrowser.removeCurrentTab();
+ await SpecialPowers.popPrefEnv();
+});
+
+/**
+ * Verifies the state of pref related toggles and checkboxes.
+ *
+ * @param {object} stateByElementID
+ * Maps toggle/checkbox element IDs to booleans. Each boolean
+ * is the expected state of the corresponding ID.
+ * @param {object} attr
+ * Attribute to check against the expected state. The "pressed"
+ * attribute is verified by default, since this is mostly used
+ * for toggle buttons.
+ */
+function assertPrefUIState(stateByElementID, attr = "pressed") {
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let container = doc.getElementById(CONTAINER_ID);
+ Assert.ok(BrowserTestUtils.is_visible(container), "The container is visible");
+ for (let [id, state] of Object.entries(stateByElementID)) {
+ let element = doc.getElementById(id);
+ Assert.equal(element[attr], state, "Expected state for ID: " + id);
+ }
+}
+
+/**
+ * Verifies the state of the info box.
+ *
+ * @param {string} expectedL10nID
+ * The l10n ID of the string that should be visible in the info box, null if
+ * the info box should be hidden.
+ */
+async function assertInfoBox(expectedL10nID) {
+ info("Checking info box with expected l10n ID: " + expectedL10nID);
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let infoBox = doc.getElementById(INFO_BOX_ID);
+ await TestUtils.waitForCondition(
+ () => BrowserTestUtils.is_visible(infoBox) == !!expectedL10nID,
+ "Waiting for expected info box visibility: " + !!expectedL10nID
+ );
+
+ let infoIcon = infoBox.querySelector(".info-icon");
+ Assert.equal(
+ BrowserTestUtils.is_visible(infoIcon),
+ !!expectedL10nID,
+ "The info icon is visible iff a description should be shown"
+ );
+
+ let learnMore = infoBox.querySelector("." + LEARN_MORE_CLASS);
+ Assert.ok(learnMore, "Found the info box learn more link");
+ Assert.equal(
+ BrowserTestUtils.is_visible(learnMore),
+ !!expectedL10nID,
+ "The info box learn more link is visible iff a description should be shown"
+ );
+
+ if (expectedL10nID) {
+ let infoText = doc.getElementById(INFO_TEXT_ID);
+ Assert.equal(
+ infoText.dataset.l10nId,
+ expectedL10nID,
+ "Info text has expected l10n ID"
+ );
+ }
+}
diff --git a/browser/components/preferences/tests/browser_privacy_passwordGenerationAndAutofill.js b/browser/components/preferences/tests/browser_privacy_passwordGenerationAndAutofill.js
new file mode 100644
index 0000000000..6a6e419229
--- /dev/null
+++ b/browser/components/preferences/tests/browser_privacy_passwordGenerationAndAutofill.js
@@ -0,0 +1,199 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function initialState() {
+ // check pref permutations to verify the UI opens in the correct state
+ const prefTests = [
+ {
+ initialPrefs: [
+ ["signon.rememberSignons", true],
+ ["signon.generation.available", true],
+ ["signon.generation.enabled", true],
+ ["signon.autofillForms", true],
+ ],
+ expected: "checked",
+ },
+ {
+ initialPrefs: [
+ ["signon.rememberSignons", true],
+ ["signon.generation.available", true],
+ ["signon.generation.enabled", false],
+ ["signon.autofillForms", false],
+ ],
+ expected: "unchecked",
+ },
+ {
+ initialPrefs: [
+ ["signon.rememberSignons", true],
+ ["signon.generation.available", false],
+ ["signon.generation.enabled", false],
+ ],
+ expected: "hidden",
+ },
+ {
+ initialPrefs: [
+ ["signon.rememberSignons", false],
+ ["signon.generation.available", true],
+ ["signon.generation.enabled", true],
+ ["signon.autofillForms", true],
+ ],
+ expected: "disabled",
+ },
+ ];
+ for (let test of prefTests) {
+ // set initial pref values
+ info("initialState, testing with: " + JSON.stringify(test));
+ await SpecialPowers.pushPrefEnv({ set: test.initialPrefs });
+
+ // open about:privacy in a tab
+ // verify expected conditions
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "about:preferences#privacy",
+ },
+ async function (browser) {
+ let doc = browser.contentDocument;
+ let generatePasswordsCheckbox = doc.getElementById("generatePasswords");
+ let autofillFormsCheckbox = doc.getElementById(
+ "passwordAutofillCheckbox"
+ );
+ doc.getElementById("passwordSettings").scrollIntoView();
+
+ info("initialState, assert on expected state:" + test.expected);
+ switch (test.expected) {
+ case "hidden":
+ is_element_hidden(
+ generatePasswordsCheckbox,
+ "#generatePasswords checkbox is hidden"
+ );
+ break;
+ case "checked":
+ is_element_visible(
+ generatePasswordsCheckbox,
+ "#generatePasswords checkbox is visible"
+ );
+ ok(
+ generatePasswordsCheckbox.checked,
+ "#generatePasswords checkbox is checked"
+ );
+ ok(
+ autofillFormsCheckbox.checked,
+ "#passwordAutofillCheckbox is checked"
+ );
+ break;
+ case "unchecked":
+ ok(
+ !generatePasswordsCheckbox.checked,
+ "#generatePasswords checkbox is un-checked"
+ );
+ ok(
+ !autofillFormsCheckbox.checked,
+ "#passwordAutofillCheckbox is un-checked"
+ );
+ break;
+ case "disabled":
+ ok(
+ generatePasswordsCheckbox.disabled,
+ "#generatePasswords checkbox is disabled"
+ );
+ ok(
+ autofillFormsCheckbox.disabled,
+ "#passwordAutofillCheckbox is disabled"
+ );
+ break;
+ default:
+ ok(false, "Unknown expected state: " + test.expected);
+ }
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+add_task(async function toggleGenerationEnabled() {
+ // clicking the checkbox should toggle the pref
+ SpecialPowers.pushPrefEnv({
+ set: [
+ ["signon.generation.available", true],
+ ["signon.generation.enabled", false],
+ ["signon.rememberSignons", true],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "about:preferences#privacy",
+ },
+ async function (browser) {
+ let doc = browser.contentDocument;
+ let checkbox = doc.getElementById("generatePasswords");
+
+ info("waiting for the browser to have focus");
+ await SimpleTest.promiseFocus(browser);
+ let prefChanged = TestUtils.waitForPrefChange(
+ "signon.generation.enabled"
+ );
+
+ // the preferences "Search" bar obscures the checkbox if we scrollIntoView and try to click on it
+ // so use keyboard events instead
+ checkbox.focus();
+ is(doc.activeElement, checkbox, "checkbox is focused");
+ EventUtils.synthesizeKey(" ");
+
+ info("waiting for pref to change");
+ await prefChanged;
+ ok(checkbox.checked, "#generatePasswords checkbox is checked");
+ ok(
+ Services.prefs.getBoolPref("signon.generation.enabled"),
+ "enabled pref is now true"
+ );
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function toggleRememberSignon() {
+ // toggling rememberSignons checkbox should make generation checkbox disabled
+ SpecialPowers.pushPrefEnv({
+ set: [
+ ["signon.generation.available", true],
+ ["signon.generation.enabled", true],
+ ["signon.rememberSignons", true],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "about:preferences#privacy",
+ },
+ async function (browser) {
+ let doc = browser.contentDocument;
+ let checkbox = doc.getElementById("savePasswords");
+ let generationCheckbox = doc.getElementById("generatePasswords");
+
+ ok(
+ !generationCheckbox.disabled,
+ "generation checkbox is not initially disabled"
+ );
+
+ info("waiting for the browser to have focus");
+ await SimpleTest.promiseFocus(browser);
+ let prefChanged = TestUtils.waitForPrefChange("signon.rememberSignons");
+
+ // the preferences "Search" bar obscures the checkbox if we scrollIntoView and try to click on it
+ // so use keyboard events instead
+ checkbox.focus();
+ is(doc.activeElement, checkbox, "checkbox is focused");
+ EventUtils.synthesizeKey(" ");
+
+ info("waiting for pref to change");
+ await prefChanged;
+ ok(!checkbox.checked, "#savePasswords checkbox is un-checked");
+ ok(generationCheckbox.disabled, "generation checkbox becomes disabled");
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/preferences/tests/browser_privacy_quickactions.js b/browser/components/preferences/tests/browser_privacy_quickactions.js
new file mode 100644
index 0000000000..14f907cc5c
--- /dev/null
+++ b/browser/components/preferences/tests/browser_privacy_quickactions.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This tests the Privacy pane's Firefox QuickActions UI.
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarProviderQuickActions:
+ "resource:///modules/UrlbarProviderQuickActions.sys.mjs",
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+});
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.quickactions", true],
+ ["browser.urlbar.quickactions.enabled", true],
+ ],
+ });
+
+ UrlbarProviderQuickActions.addAction("testaction", {
+ commands: ["testaction"],
+ label: "quickactions-downloads2",
+ });
+
+ registerCleanupFunction(() => {
+ UrlbarProviderQuickActions.removeAction("testaction");
+ });
+});
+
+async function isGroupHidden(tab) {
+ return SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => content.document.getElementById("quickActionsBox").hidden
+ );
+}
+
+add_task(async function test_show_prefs() {
+ Services.prefs.setBoolPref("browser.urlbar.quickactions.showPrefs", false);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:preferences#privacy"
+ );
+
+ Assert.ok(
+ await isGroupHidden(tab),
+ "The preferences are hidden when pref disabled"
+ );
+
+ Services.prefs.setBoolPref("browser.urlbar.quickactions.showPrefs", true);
+
+ Assert.ok(
+ !(await isGroupHidden(tab)),
+ "The preferences are shown when pref enabled"
+ );
+
+ Services.prefs.clearUserPref("browser.urlbar.quickactions.showPrefs");
+ await BrowserTestUtils.removeTab(tab);
+});
+
+async function testActionIsShown(window) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "testact",
+ waitForFocus: SimpleTest.waitForFocus,
+ });
+ try {
+ let { result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ return result.providerName == "quickactions";
+ } catch (e) {
+ return false;
+ }
+}
+
+add_task(async function test_prefs() {
+ Services.prefs.setBoolPref("browser.urlbar.quickactions.showPrefs", true);
+ Services.prefs.setBoolPref("browser.urlbar.suggest.quickactions", false);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:preferences#privacy"
+ );
+
+ Assert.ok(
+ !(await testActionIsShown(window)),
+ "Actions are not shown while pref disabled"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ let checkbox = content.document.getElementById("enableQuickActions");
+ is(
+ checkbox.checked,
+ false,
+ "Checkbox is not checked while feature is disabled"
+ );
+ checkbox.click();
+ });
+
+ Assert.ok(
+ await testActionIsShown(window),
+ "Actions are shown after user clicks checkbox"
+ );
+
+ Services.prefs.clearUserPref("browser.urlbar.quickactions.showPrefs");
+ Services.prefs.clearUserPref("browser.urlbar.suggest.quickactions");
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/preferences/tests/browser_privacy_relayIntegration.js b/browser/components/preferences/tests/browser_privacy_relayIntegration.js
new file mode 100644
index 0000000000..23d62f38bd
--- /dev/null
+++ b/browser/components/preferences/tests/browser_privacy_relayIntegration.js
@@ -0,0 +1,251 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function initialState() {
+ // check pref permutations to verify the UI opens in the correct state
+ const prefTests = [
+ {
+ initialPrefs: [
+ ["signon.firefoxRelay.feature", undefined],
+ ["signon.rememberSignons", true],
+ ],
+ expected: "hidden",
+ },
+ {
+ initialPrefs: [
+ ["signon.firefoxRelay.feature", "available"],
+ ["signon.rememberSignons", true],
+ ],
+ expected: "checked",
+ },
+ {
+ initialPrefs: [
+ ["signon.firefoxRelay.feature", "enabled"],
+ ["signon.rememberSignons", true],
+ ],
+ expected: "checked",
+ },
+ {
+ initialPrefs: [
+ ["signon.firefoxRelay.feature", "disabled"],
+ ["signon.rememberSignons", true],
+ ],
+ expected: "unchecked",
+ },
+ {
+ initialPrefs: [
+ ["signon.firefoxRelay.feature", undefined],
+ ["signon.rememberSignons", false],
+ ],
+ expected: "hidden",
+ },
+ {
+ initialPrefs: [
+ ["signon.firefoxRelay.feature", "available"],
+ ["signon.rememberSignons", false],
+ ],
+ expected: "checked",
+ },
+ {
+ initialPrefs: [
+ ["signon.firefoxRelay.feature", "enabled"],
+ ["signon.rememberSignons", false],
+ ],
+ expected: "checked",
+ },
+ {
+ initialPrefs: [
+ ["signon.firefoxRelay.feature", "disabled"],
+ ["signon.rememberSignons", false],
+ ],
+ expected: "unchecked",
+ },
+ ];
+ for (let test of prefTests) {
+ // set initial pref values
+ info("initialState, testing with: " + JSON.stringify(test));
+ await SpecialPowers.pushPrefEnv({ set: test.initialPrefs });
+
+ // open about:privacy in a tab
+ // verify expected conditions
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "about:preferences#privacy",
+ },
+ async function (browser) {
+ const doc = browser.contentDocument;
+ const relayGroup = doc.getElementById("relayIntegrationBox");
+ const checkbox = doc.getElementById("relayIntegration");
+ const savePasswords = doc.getElementById("savePasswords");
+ doc.getElementById("passwordSettings").scrollIntoView();
+
+ Assert.equal(
+ checkbox.disabled,
+ !savePasswords.checked,
+ "#relayIntegration checkbox disabled when #passwordAutofillCheckbox is unchecked"
+ );
+
+ switch (test.expected) {
+ case "hidden":
+ is_element_hidden(relayGroup, "#relayIntegrationBox is hidden");
+ break;
+ case "checked":
+ is_element_visible(relayGroup, "#relayIntegrationBox is visible");
+ Assert.ok(
+ checkbox.checked,
+ "#relayIntegration checkbox is checked"
+ );
+ break;
+ case "unchecked":
+ is_element_visible(relayGroup, "#relayIntegrationBox is visible");
+ Assert.ok(
+ !checkbox.checked,
+ "#relayIntegration checkbox is un-checked"
+ );
+ break;
+ default:
+ Assert.ok(false, "Unknown expected state: " + test.expected);
+ break;
+ }
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+add_task(async function toggleRelayIntegration() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["signon.firefoxRelay.feature", "enabled"],
+ ["signon.rememberSignons", true],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "about:preferences#privacy",
+ },
+ async browser => {
+ await SimpleTest.promiseFocus(browser);
+
+ // the preferences "Search" bar obscures the checkbox if we scrollIntoView and try to click on it
+ // so use keyboard events instead
+ const doc = browser.contentDocument;
+ const relayCheckbox = doc.getElementById("relayIntegration");
+ relayCheckbox.focus();
+ Assert.equal(doc.activeElement, relayCheckbox, "checkbox is focused");
+ Assert.equal(
+ relayCheckbox.checked,
+ true,
+ "#relayIntegration checkbox is not checked"
+ );
+
+ async function clickOnFeatureCheckbox(
+ expectedPrefValue,
+ expectedCheckValue,
+ message
+ ) {
+ const prefChanged = TestUtils.waitForPrefChange(
+ "signon.firefoxRelay.feature"
+ );
+ EventUtils.synthesizeKey(" ");
+ await prefChanged;
+ Assert.equal(
+ Services.prefs.getStringPref("signon.firefoxRelay.feature"),
+ expectedPrefValue,
+ message
+ );
+ Assert.equal(
+ relayCheckbox.checked,
+ expectedCheckValue,
+ `#relayIntegration checkbox is ${
+ expectedCheckValue ? "checked" : "unchecked"
+ }`
+ );
+ }
+
+ await clickOnFeatureCheckbox(
+ "disabled",
+ false,
+ 'Turn integration off from "enabled" feature state'
+ );
+ await clickOnFeatureCheckbox(
+ "available",
+ true,
+ 'Turn integration on from "enabled" feature state'
+ );
+ await clickOnFeatureCheckbox(
+ "disabled",
+ false,
+ 'Turn integration off from "enabled" feature state'
+ );
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function toggleRememberSignon() {
+ // toggling rememberSignons checkbox should make generation checkbox disabled
+ SpecialPowers.pushPrefEnv({
+ set: [
+ ["signon.firefoxRelay.feature", "available"],
+ ["signon.rememberSignons", true],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "about:preferences#privacy",
+ },
+ async function (browser) {
+ const doc = browser.contentDocument;
+ const checkbox = doc.getElementById("savePasswords");
+ const relayCheckbox = doc.getElementById("relayIntegration");
+
+ Assert.ok(
+ !relayCheckbox.disabled,
+ "generation checkbox is not initially disabled"
+ );
+
+ await SimpleTest.promiseFocus(browser);
+ const prefChanged = TestUtils.waitForPrefChange("signon.rememberSignons");
+
+ // the preferences "Search" bar obscures the checkbox if we scrollIntoView and try to click on it
+ // so use keyboard events instead
+ checkbox.focus();
+ Assert.equal(doc.activeElement, checkbox, "checkbox is focused");
+ EventUtils.synthesizeKey(" ");
+
+ await prefChanged;
+ Assert.ok(!checkbox.checked, "#savePasswords checkbox is un-checked");
+ Assert.ok(
+ relayCheckbox.disabled,
+ "Relay integration checkbox becomes disabled"
+ );
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function testLockedRelayPreference() {
+ // Locking relay preference should disable checkbox
+ Services.prefs.lockPref("signon.firefoxRelay.feature");
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "about:preferences#privacy",
+ },
+ async function (browser) {
+ const doc = browser.contentDocument;
+ const relayCheckbox = doc.getElementById("relayIntegration");
+
+ Assert.ok(relayCheckbox.disabled, "Relay checkbox should be disabled");
+ }
+ );
+
+ Services.prefs.unlockPref("signon.firefoxRelay.feature");
+});
diff --git a/browser/components/preferences/tests/browser_privacy_segmentation_pref.js b/browser/components/preferences/tests/browser_privacy_segmentation_pref.js
new file mode 100644
index 0000000000..9b71d91e11
--- /dev/null
+++ b/browser/components/preferences/tests/browser_privacy_segmentation_pref.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the privacy segmentation pref and preferences UI.
+
+"use strict";
+
+const PREF = "browser.dataFeatureRecommendations.enabled";
+const PREF_VISIBILITY = "browser.privacySegmentation.preferences.show";
+
+add_task(async function test_preferences_section() {
+ if (!AppConstants.MOZ_DATA_REPORTING) {
+ ok(true, "Skipping test because data reporting is disabled");
+ return;
+ }
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let section = doc.getElementById("privacySegmentationSection");
+ let sectionHeader = section.querySelector("h2");
+ let sectionDescription = section.querySelector("label");
+ let radioGroup = section.querySelector(
+ "#privacyDataFeatureRecommendationRadioGroup"
+ );
+ let radioEnabled = radioGroup.querySelector(
+ "#privacyDataFeatureRecommendationEnabled"
+ );
+ let radioDisabled = radioGroup.querySelector(
+ "#privacyDataFeatureRecommendationDisabled"
+ );
+
+ for (let show of [false, true]) {
+ Services.prefs.setBoolPref(PREF_VISIBILITY, show);
+ let showStr = show ? "visible" : "hidden";
+
+ is(
+ BrowserTestUtils.is_visible(section),
+ show,
+ `Privacy Segmentation section should be ${showStr}.`
+ );
+ is(
+ BrowserTestUtils.is_visible(sectionHeader),
+ show,
+ `Privacy Segmentation section header should be ${showStr}.`
+ );
+ is(
+ BrowserTestUtils.is_visible(sectionDescription),
+ show,
+ `Privacy Segmentation section description should be ${showStr}.`
+ );
+ is(
+ BrowserTestUtils.is_visible(radioGroup),
+ show,
+ `Privacy Segmentation radio group should be ${showStr}.`
+ );
+
+ // The section is visible, test radio buttons.
+ if (show) {
+ Services.prefs.setBoolPref(PREF, false);
+
+ is(
+ radioGroup.value,
+ "false",
+ "Radio group should reflect initial pref state of false."
+ );
+
+ info("Selecting radio on.");
+ radioEnabled.click();
+ is(
+ Services.prefs.getBoolPref(PREF),
+ true,
+ "Privacy Segmentation should be enabled."
+ );
+
+ info("Selecting radio off.");
+ radioDisabled.click();
+ is(
+ Services.prefs.getBoolPref(PREF),
+ false,
+ "Privacy Segmentation should be disabled."
+ );
+
+ info("Updating pref externally");
+ is(
+ radioGroup.value,
+ "false",
+ "Radio group should reflect initial pref state of false."
+ );
+ Services.prefs.setBoolPref(PREF, true);
+ await BrowserTestUtils.waitForMutationCondition(
+ radioGroup,
+ { attributeFilter: ["value"] },
+ () => radioGroup.value == "true"
+ );
+ is(
+ radioGroup.value,
+ "true",
+ "Updating Privacy Segmentation pref also updates radio group."
+ );
+ }
+ }
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ Services.prefs.clearUserPref(PREF_VISIBILITY);
+ Services.prefs.clearUserPref(PREF);
+});
+
+add_task(async function test_preferences_section_data_reporting_disabled() {
+ if (AppConstants.MOZ_DATA_REPORTING) {
+ ok(true, "Skipping test because data reporting is enabled");
+ return;
+ }
+
+ for (let show of [false, true]) {
+ Services.prefs.setBoolPref(PREF_VISIBILITY, show);
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let section = doc.getElementById("privacySegmentationSection");
+ is(
+ !!section,
+ show,
+ "Section should only exist when privacy segmentation section is enabled."
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+
+ Services.prefs.clearUserPref(PREF_VISIBILITY);
+});
diff --git a/browser/components/preferences/tests/browser_privacy_syncDataClearing.js b/browser/components/preferences/tests/browser_privacy_syncDataClearing.js
new file mode 100644
index 0000000000..d5b6b904ab
--- /dev/null
+++ b/browser/components/preferences/tests/browser_privacy_syncDataClearing.js
@@ -0,0 +1,287 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * With no custom cleaning categories set and sanitizeOnShutdown disabled,
+ * the checkboxes "alwaysClear" and "deleteOnClose" should share the same state.
+ * The state of the cleaning categories cookies, cache and offlineApps should be in the state of the "deleteOnClose" box.
+ */
+add_task(async function test_syncWithoutCustomPrefs() {
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+
+ let document = gBrowser.contentDocument;
+ let deleteOnCloseBox = document.getElementById("deleteOnClose");
+ let alwaysClearBox = document.getElementById("alwaysClear");
+
+ ok(!deleteOnCloseBox.checked, "DeleteOnClose initial state is deselected");
+ ok(!alwaysClearBox.checked, "AlwaysClear initial state is deselected");
+
+ deleteOnCloseBox.click();
+
+ ok(deleteOnCloseBox.checked, "DeleteOnClose is selected");
+ is(
+ deleteOnCloseBox.checked,
+ alwaysClearBox.checked,
+ "DeleteOnClose sets alwaysClear in the same state, selected"
+ );
+ ok(
+ Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies"),
+ "Cookie cleaning pref is set"
+ );
+ ok(
+ Services.prefs.getBoolPref("privacy.clearOnShutdown.cache"),
+ "Cache cleaning pref is set"
+ );
+ ok(
+ Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps"),
+ "OfflineApps cleaning pref is set"
+ );
+ ok(
+ !Services.prefs.getBoolPref("privacy.clearOnShutdown.downloads"),
+ "Downloads cleaning pref is not set"
+ );
+
+ deleteOnCloseBox.click();
+
+ ok(!deleteOnCloseBox.checked, "DeleteOnClose is deselected");
+ is(
+ deleteOnCloseBox.checked,
+ alwaysClearBox.checked,
+ "DeleteOnclose sets alwaysClear in the same state, deselected"
+ );
+
+ ok(
+ !Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies"),
+ "Cookie cleaning pref is reset"
+ );
+ ok(
+ !Services.prefs.getBoolPref("privacy.clearOnShutdown.cache"),
+ "Cache cleaning pref is reset"
+ );
+ ok(
+ !Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps"),
+ "OfflineApps cleaning pref is reset"
+ );
+ ok(
+ !Services.prefs.getBoolPref("privacy.clearOnShutdown.downloads"),
+ "Downloads cleaning pref is not set"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ Services.prefs.clearUserPref("privacy.clearOnShutdown.downloads");
+ Services.prefs.clearUserPref("privacy.clearOnShutdown.offlineApps");
+ Services.prefs.clearUserPref("privacy.clearOnShutdown.cache");
+ Services.prefs.clearUserPref("privacy.clearOnShutdown.cookies");
+ Services.prefs.clearUserPref("privacy.sanitize.sanitizeOnShutdown");
+});
+
+/*
+ * With custom cleaning category already set and SanitizeOnShutdown enabled,
+ * deselecting "deleteOnClose" should not change the state of "alwaysClear".
+ * The state of the cleaning categories cookies, cache and offlineApps should be in the state of the "deleteOnClose" box.
+ */
+add_task(async function test_syncWithCustomPrefs() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.clearOnShutdown.history", true],
+ ["privacy.sanitize.sanitizeOnShutdown", true],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+
+ let document = gBrowser.contentDocument;
+ let deleteOnCloseBox = document.getElementById("deleteOnClose");
+ let alwaysClearBox = document.getElementById("alwaysClear");
+
+ ok(!deleteOnCloseBox.checked, "DeleteOnClose initial state is deselected");
+ ok(alwaysClearBox.checked, "AlwaysClear initial state is selected");
+
+ deleteOnCloseBox.click();
+
+ ok(deleteOnCloseBox.checked, "DeleteOnClose is selected");
+ is(
+ deleteOnCloseBox.checked,
+ alwaysClearBox.checked,
+ "AlwaysClear and deleteOnClose are in the same state, selected"
+ );
+ ok(
+ Services.prefs.getBoolPref("privacy.clearOnShutdown.history"),
+ "History cleaning pref is still set"
+ );
+
+ ok(
+ Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies"),
+ "Cookie cleaning pref is set"
+ );
+ ok(
+ Services.prefs.getBoolPref("privacy.clearOnShutdown.cache"),
+ "Cache cleaning pref is set"
+ );
+ ok(
+ Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps"),
+ "OfflineApps cleaning pref is set"
+ );
+
+ deleteOnCloseBox.click();
+
+ ok(!deleteOnCloseBox.checked, "DeleteOnClose is deselected");
+ is(
+ !deleteOnCloseBox.checked,
+ alwaysClearBox.checked,
+ "AlwaysClear is not synced with deleteOnClose, only deleteOnClose is deselected"
+ );
+
+ ok(
+ !Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies"),
+ "Cookie cleaning pref is reset"
+ );
+ ok(
+ !Services.prefs.getBoolPref("privacy.clearOnShutdown.cache"),
+ "Cache cleaning pref is reset"
+ );
+ ok(
+ !Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps"),
+ "OfflineApps cleaning pref is reset"
+ );
+ ok(
+ Services.prefs.getBoolPref("privacy.clearOnShutdown.history"),
+ "History cleaning pref is still set"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await SpecialPowers.popPrefEnv();
+});
+
+/*
+ * Setting/resetting cleaning prefs for cookies, cache, offline apps
+ * and selecting/deselecting the "alwaysClear" Box, also selects/deselects
+ * the "deleteOnClose" box.
+ */
+
+add_task(async function test_syncWithCustomPrefs() {
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+
+ let document = gBrowser.contentDocument;
+ let deleteOnCloseBox = document.getElementById("deleteOnClose");
+ let alwaysClearBox = document.getElementById("alwaysClear");
+
+ ok(!deleteOnCloseBox.checked, "DeleteOnClose initial state is deselected");
+ ok(!alwaysClearBox.checked, "AlwaysClear initial state is deselected");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.clearOnShutdown.cookies", true],
+ ["privacy.clearOnShutdown.cache", true],
+ ["privacy.clearOnShutdown.offlineApps", true],
+ ["privacy.sanitize.sanitizeOnShutdown", true],
+ ],
+ });
+
+ ok(alwaysClearBox.checked, "AlwaysClear is selected");
+ is(
+ deleteOnCloseBox.checked,
+ alwaysClearBox.checked,
+ "AlwaysClear and deleteOnClose are in the same state, selected"
+ );
+
+ alwaysClearBox.click();
+
+ ok(!alwaysClearBox.checked, "AlwaysClear is deselected");
+ is(
+ deleteOnCloseBox.checked,
+ alwaysClearBox.checked,
+ "AlwaysClear and deleteOnClose are in the same state, deselected"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await SpecialPowers.popPrefEnv();
+});
+
+/*
+ * On loading the page, the ClearOnClose box should be set according to the pref selection
+ */
+add_task(async function test_initialState() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.clearOnShutdown.cookies", true],
+ ["privacy.clearOnShutdown.cache", true],
+ ["privacy.clearOnShutdown.offlineApps", true],
+ ["privacy.sanitize.sanitizeOnShutdown", true],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+
+ let document = gBrowser.contentDocument;
+ let deleteOnCloseBox = document.getElementById("deleteOnClose");
+
+ ok(
+ deleteOnCloseBox.checked,
+ "DeleteOnClose is set accordingly to the prefs, selected"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.clearOnShutdown.cookies", false],
+ ["privacy.clearOnShutdown.cache", false],
+ ["privacy.clearOnShutdown.offlineApps", false],
+ ["privacy.sanitize.sanitizeOnShutdown", true],
+ ["privacy.clearOnShutdown.history", true],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+
+ document = gBrowser.contentDocument;
+ deleteOnCloseBox = document.getElementById("deleteOnClose");
+
+ ok(
+ !deleteOnCloseBox.checked,
+ "DeleteOnClose is set accordingly to the prefs, deselected"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // When private browsing mode autostart is selected, the deleteOnClose Box is selected always
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.clearOnShutdown.cookies", false],
+ ["privacy.clearOnShutdown.cache", false],
+ ["privacy.clearOnShutdown.offlineApps", false],
+ ["privacy.sanitize.sanitizeOnShutdown", false],
+ ["browser.privatebrowsing.autostart", true],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+
+ document = gBrowser.contentDocument;
+ deleteOnCloseBox = document.getElementById("deleteOnClose");
+
+ ok(
+ deleteOnCloseBox.checked,
+ "DeleteOnClose is set accordingly to the private Browsing autostart pref, selected"
+ );
+
+ // Reset history mode
+ let historyMode = document.getElementById("historyMode");
+ historyMode.value = "remember";
+ historyMode.doCommand();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/preferences/tests/browser_privacypane_2.js b/browser/components/preferences/tests/browser_privacypane_2.js
new file mode 100644
index 0000000000..46fb3347c8
--- /dev/null
+++ b/browser/components/preferences/tests/browser_privacypane_2.js
@@ -0,0 +1,19 @@
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + "/";
+}
+/* import-globals-from privacypane_tests_perwindow.js */
+Services.scriptloader.loadSubScript(
+ rootDir + "privacypane_tests_perwindow.js",
+ this
+);
+
+run_test_subset([
+ test_pane_visibility,
+ test_dependent_elements,
+ test_dependent_cookie_elements,
+ test_dependent_clearonclose_elements,
+ test_dependent_prefs,
+]);
diff --git a/browser/components/preferences/tests/browser_privacypane_3.js b/browser/components/preferences/tests/browser_privacypane_3.js
new file mode 100644
index 0000000000..e88a6059eb
--- /dev/null
+++ b/browser/components/preferences/tests/browser_privacypane_3.js
@@ -0,0 +1,21 @@
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + "/";
+}
+/* import-globals-from privacypane_tests_perwindow.js */
+Services.scriptloader.loadSubScript(
+ rootDir + "privacypane_tests_perwindow.js",
+ this
+);
+
+run_test_subset([
+ test_custom_retention("rememberHistory", "remember"),
+ test_custom_retention("rememberHistory", "custom"),
+ test_custom_retention("rememberForms", "custom"),
+ test_custom_retention("rememberForms", "custom"),
+ test_historymode_retention("remember", "custom"),
+ test_custom_retention("alwaysClear", "remember"),
+ test_custom_retention("alwaysClear", "custom"),
+]);
diff --git a/browser/components/preferences/tests/browser_proxy_backup.js b/browser/components/preferences/tests/browser_proxy_backup.js
new file mode 100644
index 0000000000..fc05e19ada
--- /dev/null
+++ b/browser/components/preferences/tests/browser_proxy_backup.js
@@ -0,0 +1,84 @@
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ // network.proxy.type needs to be backed up and restored because mochitest
+ // changes this setting from the default
+ let oldNetworkProxyType = Services.prefs.getIntPref("network.proxy.type");
+ registerCleanupFunction(function () {
+ Services.prefs.setIntPref("network.proxy.type", oldNetworkProxyType);
+ Services.prefs.clearUserPref("network.proxy.share_proxy_settings");
+ for (let proxyType of ["http", "ssl", "socks"]) {
+ Services.prefs.clearUserPref("network.proxy." + proxyType);
+ Services.prefs.clearUserPref("network.proxy." + proxyType + "_port");
+ if (proxyType == "http") {
+ continue;
+ }
+ Services.prefs.clearUserPref("network.proxy.backup." + proxyType);
+ Services.prefs.clearUserPref(
+ "network.proxy.backup." + proxyType + "_port"
+ );
+ }
+ // On accepting the dialog, we also write TRR values, so we need to clear
+ // them. They are tested separately in browser_privacy_dnsoverhttps.js.
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("network.trr.uri");
+ });
+
+ let connectionURL =
+ "chrome://browser/content/preferences/dialogs/connection.xhtml";
+
+ // Set a shared proxy and an SSL backup
+ Services.prefs.setIntPref("network.proxy.type", 1);
+ Services.prefs.setBoolPref("network.proxy.share_proxy_settings", true);
+ Services.prefs.setCharPref("network.proxy.http", "example.com");
+ Services.prefs.setIntPref("network.proxy.http_port", 1200);
+ Services.prefs.setCharPref("network.proxy.ssl", "example.com");
+ Services.prefs.setIntPref("network.proxy.ssl_port", 1200);
+ Services.prefs.setCharPref("network.proxy.backup.ssl", "127.0.0.1");
+ Services.prefs.setIntPref("network.proxy.backup.ssl_port", 9050);
+
+ /*
+ The connection dialog alone won't save onaccept since it uses type="child",
+ 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) {
+ is(
+ gBrowser.currentURI.spec,
+ "about:preferences",
+ "about:preferences loaded"
+ );
+ let dialog = await openAndLoadSubDialog(connectionURL);
+ let dialogElement = dialog.document.getElementById("ConnectionsDialog");
+ let dialogClosingPromise = BrowserTestUtils.waitForEvent(
+ dialogElement,
+ "dialogclosing"
+ );
+
+ ok(dialog, "connection window opened");
+ dialogElement.acceptDialog();
+
+ let dialogClosingEvent = await dialogClosingPromise;
+ ok(dialogClosingEvent, "connection window closed");
+
+ // The SSL backup should not be replaced by the shared value
+ is(
+ Services.prefs.getCharPref("network.proxy.backup.ssl"),
+ "127.0.0.1",
+ "Shared proxy backup shouldn't be replaced"
+ );
+ is(
+ Services.prefs.getIntPref("network.proxy.backup.ssl_port"),
+ 9050,
+ "Shared proxy port backup shouldn't be replaced"
+ );
+
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+}
diff --git a/browser/components/preferences/tests/browser_sanitizeOnShutdown_prefLocked.js b/browser/components/preferences/tests/browser_sanitizeOnShutdown_prefLocked.js
new file mode 100644
index 0000000000..2a7cb2d10e
--- /dev/null
+++ b/browser/components/preferences/tests/browser_sanitizeOnShutdown_prefLocked.js
@@ -0,0 +1,47 @@
+"use strict";
+
+function switchToCustomHistoryMode(doc) {
+ // Select the last item in the menulist.
+ let menulist = doc.getElementById("historyMode");
+ menulist.focus();
+ EventUtils.sendKey("UP");
+}
+
+function testPrefStateMatchesLockedState() {
+ let win = gBrowser.contentWindow;
+ let doc = win.document;
+ switchToCustomHistoryMode(doc);
+
+ let checkbox = doc.getElementById("alwaysClear");
+ let preference = win.Preferences.get("privacy.sanitize.sanitizeOnShutdown");
+ is(
+ checkbox.disabled,
+ preference.locked,
+ "Always Clear checkbox should be enabled when preference is not locked."
+ );
+
+ Services.prefs.clearUserPref("privacy.history.custom");
+ gBrowser.removeCurrentTab();
+}
+
+add_setup(function () {
+ registerCleanupFunction(function resetPreferences() {
+ Services.prefs.unlockPref("privacy.sanitize.sanitizeOnShutdown");
+ Services.prefs.clearUserPref("privacy.history.custom");
+ });
+});
+
+add_task(async function test_preference_enabled_when_unlocked() {
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ testPrefStateMatchesLockedState();
+});
+
+add_task(async function test_preference_disabled_when_locked() {
+ Services.prefs.lockPref("privacy.sanitize.sanitizeOnShutdown");
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ testPrefStateMatchesLockedState();
+});
diff --git a/browser/components/preferences/tests/browser_searchChangedEngine.js b/browser/components/preferences/tests/browser_searchChangedEngine.js
new file mode 100644
index 0000000000..0882c9775e
--- /dev/null
+++ b/browser/components/preferences/tests/browser_searchChangedEngine.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+const { SearchTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SearchTestUtils.sys.mjs"
+);
+const { SearchUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/SearchUtils.sys.mjs"
+);
+
+AddonTestUtils.initMochitest(this);
+SearchTestUtils.init(this);
+
+function findRow(tree, expectedName) {
+ for (let i = 0; i < tree.view.rowCount; i++) {
+ let name = tree.view.getCellText(
+ i,
+ tree.columns.getNamedColumn("engineName")
+ );
+
+ if (name == expectedName) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+add_task(async function test_change_engine() {
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ await SearchTestUtils.installSearchExtension({
+ id: "example@tests.mozilla.org",
+ name: "Example",
+ version: "1.0",
+ keyword: "foo",
+ favicon_url: "img123.png",
+ });
+
+ let tree = doc.querySelector("#engineList");
+
+ let row = findRow(tree, "Example");
+
+ Assert.notEqual(row, -1, "Should have found the entry");
+ Assert.ok(
+ tree.view
+ .getImageSrc(row, tree.columns.getNamedColumn("engineName"))
+ .includes("img123.png"),
+ "Should have the correct image URL"
+ );
+ Assert.equal(
+ tree.view.getCellText(row, tree.columns.getNamedColumn("engineKeyword")),
+ "foo",
+ "Should show the correct keyword"
+ );
+
+ let updatedPromise = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+ await SearchTestUtils.installSearchExtension({
+ id: "example@tests.mozilla.org",
+ name: "Example 2",
+ version: "2.0",
+ keyword: "bar",
+ favicon_url: "img456.png",
+ });
+ await updatedPromise;
+
+ row = findRow(tree, "Example 2");
+
+ Assert.notEqual(row, -1, "Should have found the updated entry");
+ Assert.ok(
+ tree.view
+ .getImageSrc(row, tree.columns.getNamedColumn("engineName"))
+ .includes("img456.png"),
+ "Should have the correct image URL"
+ );
+ Assert.equal(
+ tree.view.getCellText(row, tree.columns.getNamedColumn("engineKeyword")),
+ "bar",
+ "Should show the correct keyword"
+ );
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_searchDefaultEngine.js b/browser/components/preferences/tests/browser_searchDefaultEngine.js
new file mode 100644
index 0000000000..e64f88fab3
--- /dev/null
+++ b/browser/components/preferences/tests/browser_searchDefaultEngine.js
@@ -0,0 +1,372 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { SearchTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SearchTestUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
+});
+
+SearchTestUtils.init(this);
+
+add_setup(async function () {
+ await SearchTestUtils.installSearchExtension({
+ name: "engine1",
+ search_url: "https://example.com/engine1",
+ search_url_get_params: "search={searchTerms}",
+ });
+ await SearchTestUtils.installSearchExtension({
+ name: "engine2",
+ search_url: "https://example.com/engine2",
+ search_url_get_params: "search={searchTerms}",
+ });
+
+ const defaultEngine = await Services.search.getDefault();
+ const defaultPrivateEngine = await Services.search.getDefaultPrivate();
+
+ registerCleanupFunction(async () => {
+ await Services.search.setDefault(
+ defaultEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await Services.search.setDefaultPrivate(
+ defaultPrivateEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ });
+});
+
+add_task(async function test_openWithPrivateDefaultNotEnabledFirst() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", false],
+ ["browser.search.separatePrivateDefault", false],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ const doc = gBrowser.selectedBrowser.contentDocument;
+ const separateEngineCheckbox = doc.getElementById(
+ "browserSeparateDefaultEngine"
+ );
+ const privateDefaultVbox = doc.getElementById(
+ "browserPrivateEngineSelection"
+ );
+
+ Assert.ok(
+ separateEngineCheckbox.hidden,
+ "Should have hidden the separate search engine checkbox"
+ );
+ Assert.ok(
+ privateDefaultVbox.hidden,
+ "Should have hidden the private engine selection box"
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.separatePrivateDefault.ui.enabled", true]],
+ });
+
+ Assert.ok(
+ !separateEngineCheckbox.hidden,
+ "Should have displayed the separate search engine checkbox"
+ );
+ Assert.ok(
+ privateDefaultVbox.hidden,
+ "Should not have displayed the private engine selection box"
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.separatePrivateDefault", true]],
+ });
+
+ Assert.ok(
+ !separateEngineCheckbox.hidden,
+ "Should still be displaying the separate search engine checkbox"
+ );
+ Assert.ok(
+ !privateDefaultVbox.hidden,
+ "Should have displayed the private engine selection box"
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function test_openWithPrivateDefaultEnabledFirst() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", true],
+ ["browser.search.separatePrivateDefault", true],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ const doc = gBrowser.selectedBrowser.contentDocument;
+ const separateEngineCheckbox = doc.getElementById(
+ "browserSeparateDefaultEngine"
+ );
+ const privateDefaultVbox = doc.getElementById(
+ "browserPrivateEngineSelection"
+ );
+
+ Assert.ok(
+ !separateEngineCheckbox.hidden,
+ "Should not have hidden the separate search engine checkbox"
+ );
+ Assert.ok(
+ !privateDefaultVbox.hidden,
+ "Should not have hidden the private engine selection box"
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.separatePrivateDefault", false]],
+ });
+
+ Assert.ok(
+ !separateEngineCheckbox.hidden,
+ "Should not have hidden the separate search engine checkbox"
+ );
+ Assert.ok(
+ privateDefaultVbox.hidden,
+ "Should have hidden the private engine selection box"
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.separatePrivateDefault.ui.enabled", false]],
+ });
+
+ Assert.ok(
+ separateEngineCheckbox.hidden,
+ "Should have hidden the separate private engine checkbox"
+ );
+ Assert.ok(
+ privateDefaultVbox.hidden,
+ "Should still be hiding the private engine selection box"
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function test_separatePrivateDefault() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", true],
+ ["browser.search.separatePrivateDefault", false],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ const doc = gBrowser.selectedBrowser.contentDocument;
+ const separateEngineCheckbox = doc.getElementById(
+ "browserSeparateDefaultEngine"
+ );
+ const privateDefaultVbox = doc.getElementById(
+ "browserPrivateEngineSelection"
+ );
+
+ Assert.ok(
+ privateDefaultVbox.hidden,
+ "Should not be displaying the private engine selection box"
+ );
+
+ separateEngineCheckbox.checked = false;
+ separateEngineCheckbox.doCommand();
+
+ Assert.ok(
+ Services.prefs.getBoolPref("browser.search.separatePrivateDefault"),
+ "Should have correctly set the pref"
+ );
+
+ Assert.ok(
+ !privateDefaultVbox.hidden,
+ "Should be displaying the private engine selection box"
+ );
+
+ separateEngineCheckbox.checked = true;
+ separateEngineCheckbox.doCommand();
+
+ Assert.ok(
+ !Services.prefs.getBoolPref("browser.search.separatePrivateDefault"),
+ "Should have correctly turned the pref off"
+ );
+
+ Assert.ok(
+ privateDefaultVbox.hidden,
+ "Should have hidden the private engine selection box"
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+async function setDefaultEngine(
+ testPrivate,
+ currentEngineName,
+ expectedEngineName
+) {
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ const doc = gBrowser.selectedBrowser.contentDocument;
+ const defaultEngineSelector = doc.getElementById(
+ testPrivate ? "defaultPrivateEngine" : "defaultEngine"
+ );
+
+ Assert.equal(
+ defaultEngineSelector.selectedItem.engine.name,
+ currentEngineName,
+ "Should have the correct engine as default on first open"
+ );
+
+ const popup = defaultEngineSelector.menupopup;
+ const popupShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(
+ defaultEngineSelector,
+ {},
+ defaultEngineSelector.ownerGlobal
+ );
+ await popupShown;
+
+ const items = Array.from(popup.children);
+ const engine2Item = items.find(
+ item => item.engine.name == expectedEngineName
+ );
+
+ const defaultChanged = SearchTestUtils.promiseSearchNotification(
+ testPrivate ? "engine-default-private" : "engine-default",
+ "browser-search-engine-modified"
+ );
+ // Waiting for popupHiding here seemed to cause a race condition, however
+ // as we're really just interested in the notification, we'll just use
+ // that here.
+ EventUtils.synthesizeMouseAtCenter(engine2Item, {}, engine2Item.ownerGlobal);
+ await defaultChanged;
+
+ const newDefault = testPrivate
+ ? await Services.search.getDefaultPrivate()
+ : await Services.search.getDefault();
+ Assert.equal(
+ newDefault.name,
+ expectedEngineName,
+ "Should have changed the default engine to engine2"
+ );
+}
+
+add_task(async function test_setDefaultEngine() {
+ const engine1 = Services.search.getEngineByName("engine1");
+
+ // Set an initial default so we have a known engine.
+ await Services.search.setDefault(
+ engine1,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ Services.telemetry.clearEvents();
+ Services.fog.testResetFOG();
+
+ await setDefaultEngine(false, "engine1", "engine2");
+
+ TelemetryTestUtils.assertEvents(
+ [
+ {
+ object: "change_default",
+ value: "user",
+ extra: {
+ prev_id: engine1.telemetryId,
+ new_id: "other-engine2",
+ new_name: "engine2",
+ new_load_path: "[addon]engine2@tests.mozilla.org",
+ new_sub_url: "",
+ },
+ },
+ ],
+ { category: "search", method: "engine" }
+ );
+
+ let snapshot = await Glean.searchEngineDefault.changed.testGetValue();
+ delete snapshot[0].timestamp;
+ Assert.deepEqual(
+ snapshot[0],
+ {
+ category: "search.engine.default",
+ name: "changed",
+ extra: {
+ change_source: "user",
+ previous_engine_id: engine1.telemetryId,
+ new_engine_id: "other-engine2",
+ new_display_name: "engine2",
+ new_load_path: "[addon]engine2@tests.mozilla.org",
+ new_submission_url: "",
+ },
+ },
+ "Should have received the correct event details"
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function test_setPrivateDefaultEngine() {
+ Services.telemetry.clearEvents();
+ Services.fog.testResetFOG();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", true],
+ ["browser.search.separatePrivateDefault", true],
+ ],
+ });
+
+ const engine2 = Services.search.getEngineByName("engine2");
+
+ // Set an initial default so we have a known engine.
+ await Services.search.setDefaultPrivate(
+ engine2,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ Services.telemetry.clearEvents();
+ Services.fog.testResetFOG();
+
+ await setDefaultEngine(true, "engine2", "engine1");
+
+ TelemetryTestUtils.assertEvents(
+ [
+ {
+ object: "change_private",
+ value: "user",
+ extra: {
+ prev_id: engine2.telemetryId,
+ new_id: "other-engine1",
+ new_name: "engine1",
+ new_load_path: "[addon]engine1@tests.mozilla.org",
+ new_sub_url: "",
+ },
+ },
+ ],
+ { category: "search", method: "engine" }
+ );
+
+ let snapshot = await Glean.searchEnginePrivate.changed.testGetValue();
+ delete snapshot[0].timestamp;
+ console.log(snapshot);
+ Assert.deepEqual(
+ snapshot[0],
+ {
+ category: "search.engine.private",
+ name: "changed",
+ extra: {
+ change_source: "user",
+ previous_engine_id: engine2.telemetryId,
+ new_engine_id: "other-engine1",
+ new_display_name: "engine1",
+ new_load_path: "[addon]engine1@tests.mozilla.org",
+ new_submission_url: "",
+ },
+ },
+ "Should have received the correct event details"
+ );
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_searchFindMoreLink.js b/browser/components/preferences/tests/browser_searchFindMoreLink.js
new file mode 100644
index 0000000000..92864c9f54
--- /dev/null
+++ b/browser/components/preferences/tests/browser_searchFindMoreLink.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URL = "https://example.org/";
+
+add_setup(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.searchEnginesURL", TEST_URL]],
+ });
+});
+
+add_task(async function test_click_find_more_link() {
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ let promiseNewTab = BrowserTestUtils.waitForNewTab(gBrowser);
+
+ gBrowser.selectedBrowser.contentDocument
+ .getElementById("addEngines")
+ .scrollIntoView();
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#addEngines",
+ {},
+ gBrowser.selectedBrowser.browsingContext
+ );
+
+ let tab = await promiseNewTab;
+ Assert.equal(
+ tab.linkedBrowser.documentURI.spec,
+ TEST_URL,
+ "Should have loaded the expected page"
+ );
+
+ // Close both tabs.
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_searchRestoreDefaults.js b/browser/components/preferences/tests/browser_searchRestoreDefaults.js
new file mode 100644
index 0000000000..80588d0cce
--- /dev/null
+++ b/browser/components/preferences/tests/browser_searchRestoreDefaults.js
@@ -0,0 +1,259 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { SearchTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SearchTestUtils.sys.mjs"
+);
+const { SearchUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/SearchUtils.sys.mjs"
+);
+add_task(async function test_restore_functionality() {
+ // Ensure no engines are hidden to begin with.
+ for (let engine of await Services.search.getAppProvidedEngines()) {
+ if (engine.hidden) {
+ engine.hidden = false;
+ }
+ }
+
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let restoreDefaultsButton = doc.getElementById("restoreDefaultSearchEngines");
+
+ Assert.ok(
+ restoreDefaultsButton.disabled,
+ "Should have disabled the restore default search engines button on open"
+ );
+
+ let updatedPromise = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+
+ let tree = doc.querySelector("#engineList");
+ // Check for default search engines to be displayed in the engineList
+ let defaultEngines = await Services.search.getAppProvidedEngines();
+ for (let i = 0; i < defaultEngines.length; i++) {
+ let cellName = tree.view.getCellText(
+ i,
+ tree.columns.getNamedColumn("engineName")
+ );
+ if (cellName == "DuckDuckGo") {
+ tree.view.selection.select(i);
+ break;
+ }
+ }
+ doc.getElementById("removeEngineButton").click();
+ await updatedPromise;
+
+ let engine = await Services.search.getEngineByName("DuckDuckGo");
+
+ Assert.ok(engine.hidden, "Should have hidden the engine");
+ Assert.ok(
+ !restoreDefaultsButton.disabled,
+ "Should have enabled the restore default search engines button"
+ );
+
+ updatedPromise = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+ restoreDefaultsButton.click();
+ await updatedPromise;
+ // Let the stack unwind so that the restore defaults button can update its
+ // state.
+ await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
+
+ Assert.ok(!engine.hidden, "Should have re-enabled the disabled engine");
+ Assert.ok(
+ restoreDefaultsButton.disabled,
+ "Should have disabled the restore default search engines button after use"
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function test_restoreEnabledOnOpenWithEngineHidden() {
+ let engine = await Services.search.getEngineByName("DuckDuckGo");
+ engine.hidden = true;
+
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let restoreDefaultsButton = doc.getElementById("restoreDefaultSearchEngines");
+
+ Assert.ok(
+ !restoreDefaultsButton.disabled,
+ "Should have enabled the restore default search engines button on open"
+ );
+
+ let updatedPromise = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+ restoreDefaultsButton.click();
+ await updatedPromise;
+ // Let the stack unwind so that the restore defaults button can update its
+ // state.
+ await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
+
+ Assert.ok(!engine.hidden, "Should have re-enabled the disabled engine");
+ Assert.ok(
+ restoreDefaultsButton.disabled,
+ "Should have disabled the restore default search engines button after use"
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+// This removes the last two engines and then the remaining engines from top to
+// bottom, and then it restores the default engines. See bug 1681818.
+add_task(async function test_removeOutOfOrder() {
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let restoreDefaultsButton = doc.getElementById("restoreDefaultSearchEngines");
+ Assert.ok(
+ restoreDefaultsButton.disabled,
+ "The restore-defaults button is disabled initially"
+ );
+
+ let tree = doc.querySelector("#engineList");
+ let removeEngineButton = doc.getElementById("removeEngineButton");
+ removeEngineButton.scrollIntoView();
+
+ let defaultEngines = await Services.search.getAppProvidedEngines();
+
+ // Remove the last two engines. After each removal, the selection should move
+ // to the first local shortcut.
+ for (let i = 0; i < 2; i++) {
+ tree.view.selection.select(defaultEngines.length - i - 1);
+ let updatedPromise = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+ removeEngineButton.click();
+ await updatedPromise;
+ Assert.ok(
+ removeEngineButton.disabled,
+ "The remove-engine button is disabled because a local shortcut is selected"
+ );
+ Assert.ok(
+ !restoreDefaultsButton.disabled,
+ "The restore-defaults button is enabled after removing an engine"
+ );
+ }
+
+ // Remove the remaining engines from top to bottom except for the default engine
+ // which can't be removed.
+ for (let i = 0; i < defaultEngines.length - 3; i++) {
+ tree.view.selection.select(0);
+
+ if (defaultEngines[0].name == Services.search.defaultEngine.name) {
+ tree.view.selection.select(1);
+ }
+
+ let updatedPromise = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+ removeEngineButton.click();
+ await updatedPromise;
+ Assert.ok(
+ !restoreDefaultsButton.disabled,
+ "The restore-defaults button is enabled after removing an engine"
+ );
+ }
+ Assert.ok(
+ removeEngineButton.disabled,
+ "The remove-engine button is disabled because only one engine remains"
+ );
+
+ // Click the restore-defaults button.
+ let updatedPromise = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+ restoreDefaultsButton.click();
+ await updatedPromise;
+
+ // Wait for the restore-defaults button to update its state.
+ await TestUtils.waitForCondition(
+ () => restoreDefaultsButton.disabled,
+ "Waiting for the restore-defaults button to become disabled"
+ );
+
+ Assert.ok(
+ restoreDefaultsButton.disabled,
+ "The restore-defaults button is disabled after restoring defaults"
+ );
+ Assert.equal(
+ tree.view.rowCount,
+ defaultEngines.length + UrlbarUtils.LOCAL_SEARCH_MODES.length,
+ "All engines are restored"
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function test_removeAndRestoreMultiple() {
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let restoreDefaultsButton = doc.getElementById("restoreDefaultSearchEngines");
+ let tree = doc.querySelector("#engineList");
+ let removeEngineButton = doc.getElementById("removeEngineButton");
+ removeEngineButton.scrollIntoView();
+
+ let defaultEngines = await Services.search.getAppProvidedEngines();
+
+ // Remove the second and fourth engines.
+ for (let i = 0; i < 2; i++) {
+ tree.view.selection.select(i * 2 + 1);
+ let updatedPromise = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+ removeEngineButton.click();
+ await updatedPromise;
+ }
+
+ // Click the restore-defaults button.
+ let updatedPromise = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+ restoreDefaultsButton.click();
+ await updatedPromise;
+
+ // Remove the third engine.
+ tree.view.selection.select(3);
+ updatedPromise = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+ removeEngineButton.click();
+ await updatedPromise;
+
+ // Now restore again.
+ updatedPromise = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+ restoreDefaultsButton.click();
+ await updatedPromise;
+
+ // Wait for the restore-defaults button to update its state.
+ await TestUtils.waitForCondition(
+ () => restoreDefaultsButton.disabled,
+ "Waiting for the restore-defaults button to become disabled"
+ );
+
+ Assert.equal(
+ tree.view.rowCount,
+ defaultEngines.length + UrlbarUtils.LOCAL_SEARCH_MODES.length,
+ "Should have the correct amount of engines"
+ );
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_searchScroll.js b/browser/components/preferences/tests/browser_searchScroll.js
new file mode 100644
index 0000000000..ef3af646e9
--- /dev/null
+++ b/browser/components/preferences/tests/browser_searchScroll.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js",
+ this
+);
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+const { SearchTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SearchTestUtils.sys.mjs"
+);
+const { SearchUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/SearchUtils.sys.mjs"
+);
+
+AddonTestUtils.initMochitest(this);
+SearchTestUtils.init(this);
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.w3c_touch_events.enabled", 0]],
+ });
+});
+
+add_task(async function test_scroll() {
+ info("Open preferences page for search");
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ const doc = gBrowser.selectedBrowser.contentDocument;
+ const tree = doc.querySelector("#engineList");
+
+ info("Add engines to make the tree scrollable");
+ for (let i = 0, n = parseInt(tree.getAttribute("rows")); i < n; i++) {
+ let extension = await SearchTestUtils.installSearchExtension({
+ id: `${i}@tests.mozilla.org`,
+ name: `example${i}`,
+ version: "1.0",
+ keyword: `example${i}`,
+ });
+ await AddonTestUtils.waitForSearchProviderStartup(extension);
+ }
+
+ info("Make tree element move into viewport");
+ const mainContent = doc.querySelector(".main-content");
+ const readyForScrollIntoView = new Promise(r => {
+ mainContent.addEventListener("scroll", r, { once: true });
+ });
+ tree.scrollIntoView();
+ await readyForScrollIntoView;
+
+ const previousScroll = mainContent.scrollTop;
+
+ await promiseMoveMouseAndScrollWheelOver(tree, 1, 1, false);
+
+ Assert.equal(
+ previousScroll,
+ mainContent.scrollTop,
+ "Container element does not scroll"
+ );
+
+ info("Clean up");
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_searchShowSuggestionsFirst.js b/browser/components/preferences/tests/browser_searchShowSuggestionsFirst.js
new file mode 100644
index 0000000000..33ab63e8bf
--- /dev/null
+++ b/browser/components/preferences/tests/browser_searchShowSuggestionsFirst.js
@@ -0,0 +1,240 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const MAIN_PREF = "browser.search.suggest.enabled";
+const URLBAR_PREF = "browser.urlbar.suggest.searches";
+const FIRST_PREF = "browser.urlbar.showSearchSuggestionsFirst";
+const FIRST_CHECKBOX_ID = "showSearchSuggestionsFirstCheckbox";
+
+add_setup(async function () {
+ // Make sure the main and urlbar suggestion prefs are enabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [MAIN_PREF, true],
+ [URLBAR_PREF, true],
+ ],
+ });
+});
+
+// Open preferences with search suggestions shown first (the default).
+add_task(async function openWithSearchSuggestionsShownFirst() {
+ // Initially the pref should be true so search suggestions are shown first.
+ Assert.ok(
+ Services.prefs.getBoolPref(FIRST_PREF),
+ "Pref should be true initially"
+ );
+
+ // Open preferences. The checkbox should be checked.
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let checkbox = doc.getElementById(FIRST_CHECKBOX_ID);
+ Assert.ok(checkbox.checked, "Checkbox should be checked");
+ Assert.ok(!checkbox.disabled, "Checkbox should be enabled");
+
+ // Uncheck the checkbox.
+ checkbox.checked = false;
+ checkbox.doCommand();
+
+ // The pref should now be false so that history is shown first.
+ Assert.ok(
+ !Services.prefs.getBoolPref(FIRST_PREF),
+ "Pref should now be false to show history first"
+ );
+
+ // Make sure the checkbox state didn't change.
+ Assert.ok(!checkbox.checked, "Checkbox should remain unchecked");
+ Assert.ok(!checkbox.disabled, "Checkbox should remain enabled");
+
+ // Clear the pref.
+ Services.prefs.clearUserPref(FIRST_PREF);
+
+ // The checkbox should have become checked again.
+ Assert.ok(
+ checkbox.checked,
+ "Checkbox should become checked after clearing pref"
+ );
+ Assert.ok(
+ !checkbox.disabled,
+ "Checkbox should remain enabled after clearing pref"
+ );
+
+ // Clean up.
+ gBrowser.removeCurrentTab();
+});
+
+// Open preferences with history shown first.
+add_task(async function openWithHistoryShownFirst() {
+ // Set the pref to show history first.
+ Services.prefs.setBoolPref(FIRST_PREF, false);
+
+ // Open preferences. The checkbox should be unchecked.
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let checkbox = doc.getElementById(FIRST_CHECKBOX_ID);
+ Assert.ok(!checkbox.checked, "Checkbox should be unchecked");
+ Assert.ok(!checkbox.disabled, "Checkbox should be enabled");
+
+ // Check the checkbox.
+ checkbox.checked = true;
+ checkbox.doCommand();
+
+ // Make sure the checkbox state didn't change.
+ Assert.ok(checkbox.checked, "Checkbox should remain checked");
+ Assert.ok(!checkbox.disabled, "Checkbox should remain enabled");
+
+ // The pref should now be true so that search suggestions are shown first.
+ Assert.ok(
+ Services.prefs.getBoolPref(FIRST_PREF),
+ "Pref should now be true to show search suggestions first"
+ );
+
+ // Set the pref to false again.
+ Services.prefs.setBoolPref(FIRST_PREF, false);
+
+ // The checkbox should have become unchecked again.
+ Assert.ok(
+ !checkbox.checked,
+ "Checkbox should become unchecked after setting pref to false"
+ );
+ Assert.ok(
+ !checkbox.disabled,
+ "Checkbox should remain enabled after setting pref to false"
+ );
+
+ // Clean up.
+ gBrowser.removeCurrentTab();
+ Services.prefs.clearUserPref(FIRST_PREF);
+});
+
+// Checks how the show-suggestions-first pref and checkbox reacts to updates to
+// URLBAR_PREF and MAIN_PREF.
+add_task(async function superprefInteraction() {
+ // Initially the pref should be true so search suggestions are shown first.
+ Assert.ok(
+ Services.prefs.getBoolPref(FIRST_PREF),
+ "Pref should be true initially"
+ );
+
+ // Open preferences. The checkbox should be checked.
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let checkbox = doc.getElementById(FIRST_CHECKBOX_ID);
+ Assert.ok(checkbox.checked, "Checkbox should be checked");
+ Assert.ok(!checkbox.disabled, "Checkbox should be enabled");
+
+ // Two superior prefs control the show-suggestion-first pref: URLBAR_PREF and
+ // MAIN_PREF. Toggle each and make sure the show-suggestion-first checkbox
+ // reacts appropriately.
+ for (let superiorPref of [URLBAR_PREF, MAIN_PREF]) {
+ info(`Testing superior pref ${superiorPref}`);
+
+ // Set the superior pref to false.
+ Services.prefs.setBoolPref(superiorPref, false);
+
+ // The pref should remain true.
+ Assert.ok(
+ Services.prefs.getBoolPref(FIRST_PREF),
+ "Pref should remain true"
+ );
+
+ // The checkbox should have become unchecked and disabled.
+ Assert.ok(
+ !checkbox.checked,
+ "Checkbox should become unchecked after disabling urlbar suggestions"
+ );
+ Assert.ok(
+ checkbox.disabled,
+ "Checkbox should become disabled after disabling urlbar suggestions"
+ );
+
+ // Set the superior pref to true.
+ Services.prefs.setBoolPref(superiorPref, true);
+
+ // The pref should remain true.
+ Assert.ok(
+ Services.prefs.getBoolPref(FIRST_PREF),
+ "Pref should remain true"
+ );
+
+ // The checkbox should have become checked and enabled again.
+ Assert.ok(
+ checkbox.checked,
+ "Checkbox should become checked after re-enabling urlbar suggestions"
+ );
+ Assert.ok(
+ !checkbox.disabled,
+ "Checkbox should become enabled after re-enabling urlbar suggestions"
+ );
+
+ // Set the pref to false.
+ Services.prefs.setBoolPref(FIRST_PREF, false);
+
+ // The checkbox should have become unchecked.
+ Assert.ok(
+ !checkbox.checked,
+ "Checkbox should become unchecked after setting pref to false"
+ );
+ Assert.ok(
+ !checkbox.disabled,
+ "Checkbox should remain enabled after setting pref to false"
+ );
+
+ // Set the superior pref to false again.
+ Services.prefs.setBoolPref(superiorPref, false);
+
+ // The pref should remain false.
+ Assert.ok(
+ !Services.prefs.getBoolPref(FIRST_PREF),
+ "Pref should remain false"
+ );
+
+ // The checkbox should remain unchecked and become disabled.
+ Assert.ok(
+ !checkbox.checked,
+ "Checkbox should remain unchecked after disabling urlbar suggestions"
+ );
+ Assert.ok(
+ checkbox.disabled,
+ "Checkbox should become disabled after disabling urlbar suggestions"
+ );
+
+ // Set the superior pref to true.
+ Services.prefs.setBoolPref(superiorPref, true);
+
+ // The pref should remain false.
+ Assert.ok(
+ !Services.prefs.getBoolPref(FIRST_PREF),
+ "Pref should remain false"
+ );
+
+ // The checkbox should remain unchecked and become enabled.
+ Assert.ok(
+ !checkbox.checked,
+ "Checkbox should remain unchecked after re-enabling urlbar suggestions"
+ );
+ Assert.ok(
+ !checkbox.disabled,
+ "Checkbox should become enabled after re-enabling urlbar suggestions"
+ );
+
+ // Finally, set the pref back to true.
+ Services.prefs.setBoolPref(FIRST_PREF, true);
+
+ // The checkbox should have become checked.
+ Assert.ok(
+ checkbox.checked,
+ "Checkbox should become checked after setting pref back to true"
+ );
+ Assert.ok(
+ !checkbox.disabled,
+ "Checkbox should remain enabled after setting pref back to true"
+ );
+ }
+
+ // Clean up.
+ gBrowser.removeCurrentTab();
+
+ Services.prefs.clearUserPref(FIRST_PREF);
+ Services.prefs.clearUserPref(URLBAR_PREF);
+ Services.prefs.clearUserPref(MAIN_PREF);
+});
diff --git a/browser/components/preferences/tests/browser_search_no_results_change_category.js b/browser/components/preferences/tests/browser_search_no_results_change_category.js
new file mode 100644
index 0000000000..131492632e
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_no_results_change_category.js
@@ -0,0 +1,44 @@
+"use strict";
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.search", true]],
+ });
+});
+
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+ is(
+ searchInput,
+ gBrowser.contentDocument.activeElement.closest("#searchInput"),
+ "Search input should be focused when visiting preferences"
+ );
+ let query = "ffff____noresults____ffff";
+ let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == query
+ );
+ EventUtils.sendString(query);
+ await searchCompletedPromise;
+
+ let noResultsEl = gBrowser.contentDocument.querySelector(
+ "#no-results-message"
+ );
+ is_element_visible(
+ noResultsEl,
+ "Should be reporting no results for this query"
+ );
+
+ await gBrowser.contentWindow.gotoPref("panePrivacy");
+ is_element_hidden(
+ noResultsEl,
+ "Should not be showing the 'no results' message after selecting a category"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_searchTerms.js b/browser/components/preferences/tests/browser_search_searchTerms.js
new file mode 100644
index 0000000000..0af0591355
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_searchTerms.js
@@ -0,0 +1,201 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ Tests the showSearchTerms option on the about:preferences#search page.
+*/
+
+"use strict";
+
+XPCOMUtils.defineLazyGetter(this, "QuickSuggestTestUtils", () => {
+ const { QuickSuggestTestUtils: module } = ChromeUtils.importESModule(
+ "resource://testing-common/QuickSuggestTestUtils.sys.mjs"
+ );
+ module.init(this);
+ return module;
+});
+
+const GROUP_ID = "searchbarGroup";
+const CHECKBOX_ID = "searchShowSearchTermCheckbox";
+const PREF_SEARCHTERMS = "browser.urlbar.showSearchTerms.enabled";
+const PREF_FEATUREGATE = "browser.urlbar.showSearchTerms.featureGate";
+
+/*
+ If Nimbus experiment is enabled, check option visibility.
+*/
+add_task(async function showSearchTermsVisibility_experiment_beforeOpen() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_FEATUREGATE, false]],
+ });
+ await QuickSuggestTestUtils.withExperiment({
+ valueOverrides: {
+ showSearchTermsFeatureGate: true,
+ },
+ callback: async () => {
+ await openPreferencesViaOpenPreferencesAPI("search", {
+ leaveOpen: true,
+ });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let container = doc.getElementById(CHECKBOX_ID);
+ Assert.ok(
+ BrowserTestUtils.is_visible(container),
+ "The option box is visible"
+ );
+ gBrowser.removeCurrentTab();
+ },
+ });
+ await SpecialPowers.popPrefEnv();
+});
+
+/*
+ If Nimbus experiment is not enabled initially but eventually enabled,
+ check option visibility on Preferences page.
+*/
+add_task(async function showSearchTermsVisibility_experiment_afterOpen() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_FEATUREGATE, false]],
+ });
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let container = doc.getElementById(CHECKBOX_ID);
+ Assert.ok(
+ BrowserTestUtils.is_hidden(container),
+ "The option box is initially hidden."
+ );
+
+ // Install experiment.
+ await QuickSuggestTestUtils.withExperiment({
+ valueOverrides: {
+ showSearchTermsFeatureGate: true,
+ },
+ callback: async () => {
+ Assert.ok(
+ BrowserTestUtils.is_visible(container),
+ "The option box is visible"
+ );
+ },
+ });
+
+ Assert.ok(
+ BrowserTestUtils.is_hidden(container),
+ "The option box is hidden again after the experiment is uninstalled."
+ );
+
+ gBrowser.removeCurrentTab();
+ await SpecialPowers.popPrefEnv();
+});
+
+/*
+ Check using the checkbox modifies the preference.
+*/
+add_task(async function showSearchTerms_checkbox() {
+ // Enable the feature.
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_FEATUREGATE, true]],
+ });
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ doc.getElementById(GROUP_ID).scrollIntoView();
+
+ let option = doc.getElementById(CHECKBOX_ID);
+
+ // Evaluate checkbox pref is true.
+ Assert.ok(option.checked, "Option box should be checked.");
+
+ // Evaluate checkbox when pref is false.
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_SEARCHTERMS, false]],
+ });
+ Assert.ok(!option.checked, "Option box should not be checked.");
+ await SpecialPowers.popPrefEnv();
+
+ // Evaluate pref when checkbox is un-checked.
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#" + CHECKBOX_ID,
+ {},
+ gBrowser.selectedBrowser
+ );
+ Assert.equal(
+ Services.prefs.getBoolPref(PREF_SEARCHTERMS),
+ false,
+ "Preference should be false if un-checked."
+ );
+
+ // Evaluate pref when checkbox is checked.
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#" + CHECKBOX_ID,
+ {},
+ gBrowser.selectedBrowser
+ );
+ Assert.equal(
+ Services.prefs.getBoolPref(PREF_SEARCHTERMS),
+ true,
+ "Preference should be true if checked."
+ );
+
+ // Clean-up.
+ Services.prefs.clearUserPref(PREF_SEARCHTERMS);
+ gBrowser.removeCurrentTab();
+ await SpecialPowers.popPrefEnv();
+});
+
+/*
+ When loading the search preferences panel, the
+ showSearchTerms checkbox should be disabled if
+ the search bar is enabled.
+*/
+add_task(async function showSearchTerms_and_searchBar_preference_load() {
+ // Enable the feature.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_FEATUREGATE, true],
+ ["browser.search.widget.inNavBar", true],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ let checkbox = doc.getElementById(CHECKBOX_ID);
+ Assert.ok(
+ checkbox.disabled,
+ "showSearchTerms checkbox should be disabled when search bar is enabled."
+ );
+
+ // Clean-up.
+ gBrowser.removeCurrentTab();
+ await SpecialPowers.popPrefEnv();
+});
+
+/*
+ If the search bar is enabled while the search
+ preferences panel is open, the showSearchTerms
+ checkbox should not be clickable.
+*/
+add_task(async function showSearchTerms_and_searchBar_preference_change() {
+ // Enable the feature.
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_FEATUREGATE, true]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ let checkbox = doc.getElementById(CHECKBOX_ID);
+ Assert.ok(!checkbox.disabled, "showSearchTerms checkbox should be enabled.");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.widget.inNavBar", true]],
+ });
+ Assert.ok(
+ checkbox.disabled,
+ "showSearchTerms checkbox should be disabled when search bar is enabled."
+ );
+
+ // Clean-up.
+ await SpecialPowers.popPrefEnv();
+ Assert.ok(!checkbox.disabled, "showSearchTerms checkbox should be enabled.");
+
+ gBrowser.removeCurrentTab();
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/preferences/tests/browser_search_subdialog_tooltip_saved_addresses.js b/browser/components/preferences/tests/browser_search_subdialog_tooltip_saved_addresses.js
new file mode 100644
index 0000000000..8235f8dd19
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_subdialog_tooltip_saved_addresses.js
@@ -0,0 +1,39 @@
+"use strict";
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.search", true]],
+ });
+});
+
+add_task(async function test_show_search_term_tooltip_in_subdialog() {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let keyword = "organization";
+ await runSearchInput(keyword);
+
+ let formAutofillGroupBox = gBrowser.contentDocument.getElementById(
+ "formAutofillGroupBox"
+ );
+ let savedAddressesButton =
+ formAutofillGroupBox.querySelector(".accessory-button");
+
+ info("Clicking saved addresses button to open subdialog");
+ savedAddressesButton.click();
+ info("Waiting for addresses subdialog to appear");
+ await BrowserTestUtils.waitForCondition(() => {
+ let dialogBox = gBrowser.contentDocument.querySelector(".dialogBox");
+ return !!dialogBox;
+ });
+ let tooltip = gBrowser.contentDocument.querySelector(".search-tooltip");
+
+ is_element_visible(
+ tooltip,
+ "Tooltip with search term should be visible in subdialog"
+ );
+ is(tooltip.textContent, keyword, "Tooltip should have correct search term");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_1.js b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_1.js
new file mode 100644
index 0000000000..e676de0a6c
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_1.js
@@ -0,0 +1,48 @@
+/*
+ * This file contains tests for the Preferences search bar.
+ */
+
+// Enabling Searching functionatily. Will display search bar form this testcase forward.
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.search", true]],
+ });
+});
+
+/**
+ * Test for searching for the "Set Home Page" subdialog.
+ */
+add_task(async function () {
+ // Set custom URL so bookmark button will be shown on the page (otherwise it is hidden)
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.startup.homepage", "about:robots"],
+ ["browser.startup.page", 1],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true });
+
+ // Wait for Activity Stream to add its panels
+ await BrowserTestUtils.waitForCondition(() =>
+ SpecialPowers.spawn(
+ gBrowser.selectedTab.linkedBrowser,
+ [],
+ () => !!content.document.getElementById("homeContentsGroup")
+ )
+ );
+
+ await evaluateSearchResults("Set Home Page", "homepageGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for searching for the "Languages" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("Choose languages", "languagesGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_2.js b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_2.js
new file mode 100644
index 0000000000..6ff05dee29
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_2.js
@@ -0,0 +1,36 @@
+/*
+ * This file contains tests for the Preferences search bar.
+ */
+
+// Enabling Searching functionatily. Will display search bar form this testcase forward.
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.search", true]],
+ });
+});
+
+/**
+ * Test for searching for the "Saved Logins" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("sites are stored", "passwordsGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for searching for the "Exceptions - Enhanced Tracking Protection" subdialog:
+ * "You can specify which websites have Enhanced Tracking Protection turned off." #permissions-exceptions-manage-etp-desc
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults(
+ "Enhanced Tracking Protection turned off",
+ "trackingGroup"
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_3.js b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_3.js
new file mode 100644
index 0000000000..45f8774d73
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_3.js
@@ -0,0 +1,35 @@
+/*
+ * This file contains tests for the Preferences search bar.
+ */
+
+// Enabling Searching functionatily. Will display search bar form this testcase forward.
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.search", true]],
+ });
+});
+
+/**
+ * Test for searching for the "Allowed Sites - Add-ons Installation" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("allowed to install add-ons", "permissionsGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for searching for the "Certificate Manager" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults(
+ "identify these certificate authorities",
+ "certSelection"
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_4.js b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_4.js
new file mode 100644
index 0000000000..9cfcb51f1b
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_4.js
@@ -0,0 +1,39 @@
+/*
+ * This file contains tests for the Preferences search bar.
+ */
+
+// Enabling Searching functionatily. Will display search bar form this testcase forward.
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.search", true]],
+ });
+});
+
+/**
+ * Test for searching for the "Update History" subdialog.
+ */
+add_task(async function () {
+ // The updates panel is disabled in MSIX builds.
+ if (
+ AppConstants.platform === "win" &&
+ Services.sysinfo.getProperty("hasWinPackageId")
+ ) {
+ return;
+ }
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("updates have been installed", "updateApp");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for searching for the "Location Permissions" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("location permissions", "permissionsGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_5.js b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_5.js
new file mode 100644
index 0000000000..9a4ae09696
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_5.js
@@ -0,0 +1,46 @@
+/*
+ * This file contains tests for the Preferences search bar.
+ */
+
+requestLongerTimeout(2);
+
+// Enabling Searching functionatily. Will display search bar form this testcase forward.
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.search", true]],
+ });
+});
+
+/**
+ * Test for searching for the "Fonts" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ // Oh, Canada:
+ await evaluateSearchResults("Unified Canadian Syllabary", "fontsGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for searching for the "Colors" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("Link Colors", "colorsGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for searching for the "Exceptions - Saved Logins" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("sites will not be saved", "passwordsGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_6.js b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_6.js
new file mode 100644
index 0000000000..1091dd2dd4
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_6.js
@@ -0,0 +1,35 @@
+/*
+ * This file contains tests for the Preferences search bar.
+ */
+
+// Enabling Searching functionatily. Will display search bar form this testcase forward.
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.search", true]],
+ });
+});
+
+/**
+ * Test for searching for the "Block Lists" subdialog.
+ */
+add_task(async function () {
+ async function doTest() {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("block online trackers", "trackingGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+ await doTest();
+});
+
+/**
+ * Test for searching for the "Allowed Sites - Pop-ups" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("open pop-up windows", "permissionsGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_7.js b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_7.js
new file mode 100644
index 0000000000..1c4a923f06
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_7.js
@@ -0,0 +1,34 @@
+/*
+ * This file contains tests for the Preferences search bar.
+ */
+
+requestLongerTimeout(2);
+
+// Enabling Searching functionatily. Will display search bar form this testcase forward.
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.search", true]],
+ });
+});
+
+/**
+ * Test for searching for the "Device Manager" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("Security Modules and Devices", "certSelection");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for searching for the "Connection Settings" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("Use system proxy settings", "connectionGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_8.js b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_8.js
new file mode 100644
index 0000000000..9827e89239
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_8.js
@@ -0,0 +1,45 @@
+/*
+ * This file contains tests for the Preferences search bar.
+ */
+
+requestLongerTimeout(2);
+
+// Enabling Searching functionatily. Will display search bar form this testcase forward.
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.search", true]],
+ });
+});
+
+/**
+ * Test for searching for the "Camera Permissions" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("camera permissions", "permissionsGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for searching for the "Microphone Permissions" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("microphone permissions", "permissionsGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for searching for the "Notification Permissions" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("notification permissions", "permissionsGroup");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_site_data.js b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_site_data.js
new file mode 100644
index 0000000000..b7ca239358
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_subdialogs_within_preferences_site_data.js
@@ -0,0 +1,45 @@
+/*
+ * This file contains tests for the Preferences search bar.
+ */
+
+// Enabling Searching functionatily. Will display search bar form this testcase forward.
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.search", true]],
+ });
+});
+
+/**
+ * Test for searching for the "Settings - Site Data" subdialog.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("cookies", ["siteDataGroup", "trackingGroup"]);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("site data", ["siteDataGroup"]);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("cache", ["siteDataGroup"]);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ await evaluateSearchResults("cross-site", ["trackingGroup"]);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_within_preferences_1.js b/browser/components/preferences/tests/browser_search_within_preferences_1.js
new file mode 100644
index 0000000000..504191ecb5
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_within_preferences_1.js
@@ -0,0 +1,344 @@
+"use strict";
+/**
+ * This file contains tests for the Preferences search bar.
+ */
+
+requestLongerTimeout(6);
+
+/**
+ * Tests to see if search bar is being shown when pref is turned on
+ */
+add_task(async function show_search_bar_when_pref_is_enabled() {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+ is_element_visible(searchInput, "Search box should be shown");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for "Search Result" panel.
+ * After it runs a search, it tests if the "Search Results" panel is the only selected category.
+ * The search is then cleared, it then tests if the "General" panel is the only selected category.
+ */
+add_task(async function show_search_results_pane_only_then_revert_to_general() {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ // Performs search
+ let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+
+ is(
+ searchInput,
+ gBrowser.contentDocument.activeElement.closest("#searchInput"),
+ "Search input should be focused when visiting preferences"
+ );
+
+ let query = "password";
+ let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == query
+ );
+ EventUtils.sendString(query);
+ await searchCompletedPromise;
+
+ let categoriesList = gBrowser.contentDocument.getElementById("categories");
+
+ for (let i = 0; i < categoriesList.childElementCount; i++) {
+ let child = categoriesList.itemChildren[i];
+ is(child.selected, false, "No other panel should be selected");
+ }
+ // Takes search off
+ searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == ""
+ );
+ let count = query.length;
+ while (count--) {
+ EventUtils.sendKey("BACK_SPACE");
+ }
+ await searchCompletedPromise;
+
+ // Checks if back to generalPane
+ for (let i = 0; i < categoriesList.childElementCount; i++) {
+ let child = categoriesList.itemChildren[i];
+ if (child.id == "category-general") {
+ is(child.selected, true, "General panel should be selected");
+ } else if (child.id) {
+ is(child.selected, false, "No other panel should be selected");
+ }
+ }
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for "password" case. When we search "password", it should show the "passwordGroup"
+ */
+add_task(async function search_for_password_show_passwordGroup() {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ // Performs search
+ let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+
+ is(
+ searchInput,
+ gBrowser.contentDocument.activeElement.closest("#searchInput"),
+ "Search input should be focused when visiting preferences"
+ );
+
+ let query = "password";
+ let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == query
+ );
+ EventUtils.sendString(query);
+ await searchCompletedPromise;
+
+ let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
+
+ for (let i = 0; i < mainPrefTag.childElementCount; i++) {
+ let child = mainPrefTag.children[i];
+ if (
+ child.id == "passwordsGroup" ||
+ child.id == "weavePrefsDeck" ||
+ child.id == "header-searchResults" ||
+ child.id == "certSelection" ||
+ child.id == "connectionGroup" ||
+ child.id == "dataMigrationGroup"
+ ) {
+ is_element_visible(child, "Should be in search results");
+ } else if (child.id) {
+ is_element_hidden(child, "Should not be in search results");
+ }
+ }
+
+ // Takes search off
+ searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == ""
+ );
+ let count = query.length;
+ while (count--) {
+ EventUtils.sendKey("BACK_SPACE");
+ }
+ await searchCompletedPromise;
+
+ let expectedChildren = [
+ "paneGeneral",
+ "startupGroup",
+ "languagesGroup",
+ "webAppearanceGroup",
+ "colorsGroup",
+ "fontsGroup",
+ "zoomGroup",
+ "downloadsGroup",
+ "applicationsGroup",
+ "drmGroup",
+ "browsingGroup",
+ "performanceGroup",
+ "connectionGroup",
+ "generalCategory",
+ "languageAndAppearanceCategory",
+ "filesAndApplicationsCategory",
+ "performanceCategory",
+ "browsingCategory",
+ "networkProxyCategory",
+ "dataMigrationGroup",
+ "translationsGroup",
+ ];
+ // Only visible for non-MSIX builds
+ if (
+ AppConstants.platform !== "win" ||
+ !Services.sysinfo.getProperty("hasWinPackageId", false)
+ ) {
+ expectedChildren.push("updatesCategory");
+ expectedChildren.push("updateApp");
+ }
+ // Checks if back to generalPane
+ for (let i = 0; i < mainPrefTag.childElementCount; i++) {
+ let child = mainPrefTag.children[i];
+ if (expectedChildren.includes(child.id)) {
+ is_element_visible(child, `Should be in general tab: ${child.id}`);
+ } else if (child.id) {
+ is_element_hidden(child, `Should not be in general tab: ${child.id}`);
+ }
+ }
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for if nothing is found
+ */
+add_task(async function search_with_nothing_found() {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let noResultsEl = gBrowser.contentDocument.querySelector(
+ "#no-results-message"
+ );
+ let sorryMsgQueryEl = gBrowser.contentDocument.getElementById(
+ "sorry-message-query"
+ );
+
+ is_element_hidden(noResultsEl, "Should not be in search results yet");
+
+ // Performs search
+ let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+
+ is(
+ searchInput,
+ gBrowser.contentDocument.activeElement.closest("#searchInput"),
+ "Search input should be focused when visiting preferences"
+ );
+
+ let query = "coach";
+ let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == query
+ );
+ EventUtils.sendString(query);
+ await searchCompletedPromise;
+
+ is_element_visible(noResultsEl, "Should be in search results");
+ is(
+ sorryMsgQueryEl.textContent,
+ query,
+ "sorry-message-query should contain the query"
+ );
+
+ // Takes search off
+ searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == ""
+ );
+ let count = query.length;
+ while (count--) {
+ EventUtils.sendKey("BACK_SPACE");
+ }
+ await searchCompletedPromise;
+
+ is_element_hidden(noResultsEl, "Should not be in search results");
+ is(
+ sorryMsgQueryEl.textContent.length,
+ 0,
+ "sorry-message-query should be empty"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for if we go back to general tab after search case
+ */
+add_task(async function exiting_search_reverts_to_general_pane() {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let generalPane = gBrowser.contentDocument.getElementById("generalCategory");
+
+ is_element_hidden(generalPane, "Should not be in general");
+
+ // Performs search
+ let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+
+ is(
+ searchInput,
+ gBrowser.contentDocument.activeElement.closest("#searchInput"),
+ "Search input should be focused when visiting preferences"
+ );
+
+ let query = "password";
+ let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == query
+ );
+ EventUtils.sendString(query);
+ await searchCompletedPromise;
+
+ // Takes search off
+ searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == ""
+ );
+ let count = query.length;
+ while (count--) {
+ EventUtils.sendKey("BACK_SPACE");
+ }
+ await searchCompletedPromise;
+
+ // Checks if back to normal
+ is_element_visible(generalPane, "Should be in generalPane");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test for if we go to another tab after searching
+ */
+add_task(async function changing_tabs_after_searching() {
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+
+ is(
+ searchInput,
+ gBrowser.contentDocument.activeElement.closest("#searchInput"),
+ "Search input should be focused when visiting preferences"
+ );
+
+ let query = "permission";
+ let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == query
+ );
+ EventUtils.sendString(query);
+ await searchCompletedPromise;
+
+ // Search header should be shown for the permissions group
+ let permissionsSearchHeader = gBrowser.contentDocument.querySelector(
+ "#permissionsGroup .search-header"
+ );
+ is(
+ permissionsSearchHeader.hidden,
+ false,
+ "Permissions search-header should be visible"
+ );
+
+ let privacyCategory =
+ gBrowser.contentDocument.getElementById("category-privacy");
+ privacyCategory.click();
+ is(searchInput.value, "", "search input should be empty");
+ let categoriesList = gBrowser.contentDocument.getElementById("categories");
+ for (let i = 0; i < categoriesList.childElementCount; i++) {
+ let child = categoriesList.itemChildren[i];
+ if (child.id == "category-privacy") {
+ is(child.selected, true, "Privacy panel should be selected");
+ } else if (child.id) {
+ is(child.selected, false, "No other panel should be selected");
+ }
+ }
+
+ // Search header should now be hidden when viewing the permissions group not through a search
+ is(
+ permissionsSearchHeader.hidden,
+ true,
+ "Permissions search-header should be hidden"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_within_preferences_2.js b/browser/components/preferences/tests/browser_search_within_preferences_2.js
new file mode 100644
index 0000000000..6de068fbe4
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_within_preferences_2.js
@@ -0,0 +1,180 @@
+"use strict";
+/**
+ * This file contains tests for the Preferences search bar.
+ */
+
+/**
+ * Enabling searching functionality. Will display search bar from this testcase forward.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.preferences.search", true]],
+ });
+});
+
+/**
+ * Test that we only search the selected child of a XUL deck.
+ * When we search "Remove Account",
+ * it should not show the "Remove Account" button if the Firefox account is not logged in yet.
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("paneSync", { leaveOpen: true });
+
+ let weavePrefsDeck =
+ gBrowser.contentDocument.getElementById("weavePrefsDeck");
+ is(
+ weavePrefsDeck.selectedIndex,
+ 0,
+ "Should select the #noFxaAccount child node"
+ );
+
+ // Performs search.
+ let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+
+ is(
+ searchInput,
+ gBrowser.contentDocument.activeElement.closest("#searchInput"),
+ "Search input should be focused when visiting preferences"
+ );
+
+ let query = "Sync";
+ let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == query
+ );
+ EventUtils.sendString(query);
+ await searchCompletedPromise;
+
+ let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
+ for (let i = 0; i < mainPrefTag.childElementCount; i++) {
+ let child = mainPrefTag.children[i];
+ if (child.id == "header-searchResults" || child.id == "weavePrefsDeck") {
+ is_element_visible(child, "Should be in search results");
+ } else if (child.id) {
+ is_element_hidden(child, "Should not be in search results");
+ }
+ }
+
+ // Ensure the "Remove Account" button exists in the hidden child of the <xul:deck>.
+ let unlinkFxaAccount = weavePrefsDeck.children[1].querySelector(
+ "#unverifiedUnlinkFxaAccount"
+ );
+ is(
+ unlinkFxaAccount.label,
+ "Remove Account",
+ "The Remove Account button should exist"
+ );
+
+ // Performs search.
+ searchInput.focus();
+ query = "Remove Account";
+ searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == query
+ );
+ EventUtils.sendString(query);
+ await searchCompletedPromise;
+
+ let noResultsEl = gBrowser.contentDocument.querySelector(
+ "#no-results-message"
+ );
+ is_element_visible(noResultsEl, "Should be reporting no results");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+/**
+ * Test that we search using `search-l10n-ids`.
+ *
+ * The test uses element `showUpdateHistory` and
+ * l10n id `language-and-appearance-header` and expects the element
+ * to be matched on the first word from the l10n id value ("Language" in en-US).
+ */
+add_task(async function () {
+ let l10nId = "language-and-appearance-header";
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ // First, lets make sure that the element is not matched without
+ // `search-l10n-ids`.
+ {
+ let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+ let suhElem = gBrowser.contentDocument.getElementById("showUpdateHistory");
+
+ is(
+ searchInput,
+ gBrowser.contentDocument.activeElement.closest("#searchInput"),
+ "Search input should be focused when visiting preferences"
+ );
+
+ ok(
+ !suhElem.getAttribute("search-l10n-ids").includes(l10nId),
+ "showUpdateHistory element should not contain the l10n id here."
+ );
+
+ let query = "Language";
+ let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == query
+ );
+ EventUtils.sendString(query);
+ await searchCompletedPromise;
+
+ is_element_hidden(
+ suhElem,
+ "showUpdateHistory should not be in search results"
+ );
+ }
+
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // Now, let's add the l10n id to the element and perform the same search again.
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ {
+ let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+
+ is(
+ searchInput,
+ gBrowser.contentDocument.activeElement.closest("#searchInput"),
+ "Search input should be focused when visiting preferences"
+ );
+
+ let suhElem = gBrowser.contentDocument.getElementById("showUpdateHistory");
+ suhElem.setAttribute("search-l10n-ids", l10nId);
+
+ let query = "Language";
+ let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == query
+ );
+ EventUtils.sendString(query);
+ await searchCompletedPromise;
+
+ if (
+ AppConstants.platform === "win" &&
+ Services.sysinfo.getProperty("hasWinPackageId")
+ ) {
+ is_element_hidden(
+ suhElem,
+ "showUpdateHistory should not be in search results"
+ );
+ } else {
+ is_element_visible(
+ suhElem,
+ "showUpdateHistory should be in search results"
+ );
+ }
+ }
+
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_search_within_preferences_command.js b/browser/components/preferences/tests/browser_search_within_preferences_command.js
new file mode 100644
index 0000000000..736493b418
--- /dev/null
+++ b/browser/components/preferences/tests/browser_search_within_preferences_command.js
@@ -0,0 +1,45 @@
+"use strict";
+
+/**
+ * Test for "command" event on search input (when user clicks the x button)
+ */
+add_task(async function () {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ let generalPane = gBrowser.contentDocument.getElementById("generalCategory");
+
+ is_element_hidden(generalPane, "Should not be in general");
+
+ // Performs search
+ let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+ is(
+ searchInput,
+ gBrowser.contentDocument.activeElement.closest("#searchInput"),
+ "Search input should be focused when visiting preferences"
+ );
+
+ let query = "x";
+ let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == query
+ );
+ EventUtils.sendString(query);
+ await searchCompletedPromise;
+
+ is_element_hidden(generalPane, "Should not be in generalPane");
+
+ // Takes search off with "command"
+ searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == ""
+ );
+ searchInput.value = "";
+ searchInput.doCommand();
+ await searchCompletedPromise;
+
+ // Checks if back to normal
+ is_element_visible(generalPane, "Should be in generalPane");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_searchsuggestions.js b/browser/components/preferences/tests/browser_searchsuggestions.js
new file mode 100644
index 0000000000..f3ce1615ea
--- /dev/null
+++ b/browser/components/preferences/tests/browser_searchsuggestions.js
@@ -0,0 +1,128 @@
+const SUGGEST_PREF_NAME = "browser.search.suggest.enabled";
+const URLBAR_SUGGEST_PREF_NAME = "browser.urlbar.suggest.searches";
+const PRIVATE_PREF_NAME = "browser.search.suggest.enabled.private";
+
+let initialUrlbarSuggestValue;
+let initialSuggestionsInPrivateValue;
+
+add_setup(async function () {
+ const originalSuggest = Services.prefs.getBoolPref(SUGGEST_PREF_NAME);
+ initialUrlbarSuggestValue = Services.prefs.getBoolPref(
+ URLBAR_SUGGEST_PREF_NAME
+ );
+ initialSuggestionsInPrivateValue =
+ Services.prefs.getBoolPref(PRIVATE_PREF_NAME);
+
+ registerCleanupFunction(() => {
+ Services.prefs.setBoolPref(SUGGEST_PREF_NAME, originalSuggest);
+ Services.prefs.setBoolPref(
+ PRIVATE_PREF_NAME,
+ initialSuggestionsInPrivateValue
+ );
+ });
+});
+
+// Open with suggestions enabled
+add_task(async function test_suggestions_start_enabled() {
+ Services.prefs.setBoolPref(SUGGEST_PREF_NAME, true);
+
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let urlbarBox = doc.getElementById("urlBarSuggestion");
+ let privateBox = doc.getElementById("showSearchSuggestionsPrivateWindows");
+ ok(!urlbarBox.disabled, "Should have enabled the urlbar checkbox");
+ ok(
+ !privateBox.disabled,
+ "Should have enabled the private mode suggestions checkbox"
+ );
+ is(
+ urlbarBox.checked,
+ initialUrlbarSuggestValue,
+ "Should have the correct value for the urlbar checkbox"
+ );
+ is(
+ privateBox.checked,
+ initialSuggestionsInPrivateValue,
+ "Should have the correct value for the private mode suggestions checkbox"
+ );
+
+ async function toggleElement(id, prefName, element, initialValue, desc) {
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ `#${id}`,
+ {},
+ gBrowser.selectedBrowser
+ );
+ is(
+ element.checked,
+ !initialValue,
+ `Should have flipped the ${desc} checkbox`
+ );
+ let prefValue = Services.prefs.getBoolPref(prefName);
+ is(
+ prefValue,
+ !initialValue,
+ `Should have updated the ${desc} preference value`
+ );
+
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ `#${id}`,
+ {},
+ gBrowser.selectedBrowser
+ );
+ is(
+ element.checked,
+ initialValue,
+ `Should have flipped the ${desc} checkbox back to the original value`
+ );
+ prefValue = Services.prefs.getBoolPref(prefName);
+ is(
+ prefValue,
+ initialValue,
+ `Should have updated the ${desc} preference back to the original value`
+ );
+ }
+
+ await toggleElement(
+ "urlBarSuggestion",
+ URLBAR_SUGGEST_PREF_NAME,
+ urlbarBox,
+ initialUrlbarSuggestValue,
+ "urlbar"
+ );
+ await toggleElement(
+ "showSearchSuggestionsPrivateWindows",
+ PRIVATE_PREF_NAME,
+ privateBox,
+ initialSuggestionsInPrivateValue,
+ "private suggestion"
+ );
+
+ Services.prefs.setBoolPref(SUGGEST_PREF_NAME, false);
+ ok(!urlbarBox.checked, "Should have unchecked the urlbar box");
+ ok(urlbarBox.disabled, "Should have disabled the urlbar box");
+ ok(!privateBox.checked, "Should have unchecked the private suggestions box");
+ ok(privateBox.disabled, "Should have disabled the private suggestions box");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Open with suggestions disabled
+add_task(async function test_suggestions_start_disabled() {
+ Services.prefs.setBoolPref(SUGGEST_PREF_NAME, false);
+
+ await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let urlbarBox = doc.getElementById("urlBarSuggestion");
+ ok(urlbarBox.disabled, "Should have the urlbar box disabled");
+ let privateBox = doc.getElementById("showSearchSuggestionsPrivateWindows");
+ ok(privateBox.disabled, "Should have the private suggestions box disabled");
+
+ Services.prefs.setBoolPref(SUGGEST_PREF_NAME, true);
+
+ ok(!urlbarBox.disabled, "Should have enabled the urlbar box");
+ ok(!privateBox.disabled, "Should have enabled the private suggestions box");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/tests/browser_security-1.js b/browser/components/preferences/tests/browser_security-1.js
new file mode 100644
index 0000000000..80f3e6902b
--- /dev/null
+++ b/browser/components/preferences/tests/browser_security-1.js
@@ -0,0 +1,106 @@
+const PREFS = [
+ "browser.safebrowsing.phishing.enabled",
+ "browser.safebrowsing.malware.enabled",
+
+ "browser.safebrowsing.downloads.enabled",
+
+ "browser.safebrowsing.downloads.remote.block_potentially_unwanted",
+ "browser.safebrowsing.downloads.remote.block_uncommon",
+];
+
+let originals = PREFS.map(pref => [pref, Services.prefs.getBoolPref(pref)]);
+let originalMalwareTable = Services.prefs.getCharPref(
+ "urlclassifier.malwareTable"
+);
+registerCleanupFunction(function () {
+ originals.forEach(([pref, val]) => Services.prefs.setBoolPref(pref, val));
+ Services.prefs.setCharPref(
+ "urlclassifier.malwareTable",
+ originalMalwareTable
+ );
+});
+
+// This test only opens the Preferences once, and then reloads the page
+// each time that it wants to test various preference combinations. We
+// only use one tab (instead of opening/closing for each test) for all
+// to help improve test times on debug builds.
+add_setup(async function () {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ registerCleanupFunction(async function () {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+});
+
+// test the safebrowsing preference
+add_task(async function () {
+ async function checkPrefSwitch(val1, val2) {
+ Services.prefs.setBoolPref("browser.safebrowsing.phishing.enabled", val1);
+ Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", val2);
+
+ gBrowser.reload();
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let checkbox = doc.getElementById("enableSafeBrowsing");
+ let blockDownloads = doc.getElementById("blockDownloads");
+ let blockUncommon = doc.getElementById("blockUncommonUnwanted");
+ let checked = checkbox.checked;
+ is(
+ blockDownloads.hasAttribute("disabled"),
+ !checked,
+ "block downloads checkbox is set correctly"
+ );
+
+ is(
+ checked,
+ val1 && val2,
+ "safebrowsing preference is initialized correctly"
+ );
+ // should be disabled when checked is false (= pref is turned off)
+ is(
+ blockUncommon.hasAttribute("disabled"),
+ !checked,
+ "block uncommon checkbox is set correctly"
+ );
+
+ // scroll the checkbox into the viewport and click checkbox
+ checkbox.scrollIntoView();
+ EventUtils.synthesizeMouseAtCenter(
+ checkbox,
+ {},
+ gBrowser.selectedBrowser.contentWindow
+ );
+
+ // check that both settings are now turned on or off
+ is(
+ Services.prefs.getBoolPref("browser.safebrowsing.phishing.enabled"),
+ !checked,
+ "safebrowsing.enabled is set correctly"
+ );
+ is(
+ Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled"),
+ !checked,
+ "safebrowsing.malware.enabled is set correctly"
+ );
+
+ // check if the other checkboxes have updated
+ checked = checkbox.checked;
+ if (blockDownloads) {
+ is(
+ blockDownloads.hasAttribute("disabled"),
+ !checked,
+ "block downloads checkbox is set correctly"
+ );
+ is(
+ blockUncommon.hasAttribute("disabled"),
+ !checked || !blockDownloads.checked,
+ "block uncommon checkbox is set correctly"
+ );
+ }
+ }
+
+ await checkPrefSwitch(true, true);
+ await checkPrefSwitch(false, true);
+ await checkPrefSwitch(true, false);
+ await checkPrefSwitch(false, false);
+});
diff --git a/browser/components/preferences/tests/browser_security-2.js b/browser/components/preferences/tests/browser_security-2.js
new file mode 100644
index 0000000000..88be366810
--- /dev/null
+++ b/browser/components/preferences/tests/browser_security-2.js
@@ -0,0 +1,177 @@
+const PREFS = [
+ "browser.safebrowsing.phishing.enabled",
+ "browser.safebrowsing.malware.enabled",
+
+ "browser.safebrowsing.downloads.enabled",
+
+ "browser.safebrowsing.downloads.remote.block_potentially_unwanted",
+ "browser.safebrowsing.downloads.remote.block_uncommon",
+];
+
+let originals = PREFS.map(pref => [pref, Services.prefs.getBoolPref(pref)]);
+let originalMalwareTable = Services.prefs.getCharPref(
+ "urlclassifier.malwareTable"
+);
+registerCleanupFunction(function () {
+ originals.forEach(([pref, val]) => Services.prefs.setBoolPref(pref, val));
+ Services.prefs.setCharPref(
+ "urlclassifier.malwareTable",
+ originalMalwareTable
+ );
+});
+
+// This test only opens the Preferences once, and then reloads the page
+// each time that it wants to test various preference combinations. We
+// only use one tab (instead of opening/closing for each test) for all
+// to help improve test times on debug builds.
+add_setup(async function () {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ registerCleanupFunction(async function () {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+});
+
+// test the download protection preference
+add_task(async function () {
+ async function checkPrefSwitch(val) {
+ Services.prefs.setBoolPref("browser.safebrowsing.downloads.enabled", val);
+
+ gBrowser.reload();
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let checkbox = doc.getElementById("blockDownloads");
+
+ let blockUncommon = doc.getElementById("blockUncommonUnwanted");
+ let checked = checkbox.checked;
+ is(checked, val, "downloads preference is initialized correctly");
+ // should be disabled when val is false (= pref is turned off)
+ is(
+ blockUncommon.hasAttribute("disabled"),
+ !val,
+ "block uncommon checkbox is set correctly"
+ );
+
+ // scroll the checkbox into view, otherwise the synthesizeMouseAtCenter will be ignored, and click it
+ checkbox.scrollIntoView();
+ EventUtils.synthesizeMouseAtCenter(
+ checkbox,
+ {},
+ gBrowser.selectedBrowser.contentWindow
+ );
+
+ // check that setting is now turned on or off
+ is(
+ Services.prefs.getBoolPref("browser.safebrowsing.downloads.enabled"),
+ !checked,
+ "safebrowsing.downloads preference is set correctly"
+ );
+
+ // check if the uncommon warning checkbox has updated
+ is(
+ blockUncommon.hasAttribute("disabled"),
+ val,
+ "block uncommon checkbox is set correctly"
+ );
+ }
+
+ await checkPrefSwitch(true);
+ await checkPrefSwitch(false);
+});
+
+requestLongerTimeout(2);
+// test the unwanted/uncommon software warning preference
+add_task(async function () {
+ async function checkPrefSwitch(val1, val2, isV2) {
+ Services.prefs.setBoolPref(
+ "browser.safebrowsing.downloads.remote.block_potentially_unwanted",
+ val1
+ );
+ Services.prefs.setBoolPref(
+ "browser.safebrowsing.downloads.remote.block_uncommon",
+ val2
+ );
+ let testMalwareTable = "goog-malware-" + (isV2 ? "shavar" : "proto");
+ testMalwareTable += ",test-malware-simple";
+ if (val1 && val2) {
+ testMalwareTable += ",goog-unwanted-" + (isV2 ? "shavar" : "proto");
+ testMalwareTable += ",moztest-unwanted-simple";
+ }
+ Services.prefs.setCharPref("urlclassifier.malwareTable", testMalwareTable);
+
+ gBrowser.reload();
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let checkbox = doc.getElementById("blockUncommonUnwanted");
+ let checked = checkbox.checked;
+ is(
+ checked,
+ val1 && val2,
+ "unwanted/uncommon preference is initialized correctly"
+ );
+
+ // scroll the checkbox into view, otherwise the synthesizeMouseAtCenter will be ignored, and click it
+ checkbox.scrollIntoView();
+ EventUtils.synthesizeMouseAtCenter(
+ checkbox,
+ {},
+ gBrowser.selectedBrowser.contentWindow
+ );
+
+ // check that both settings are now turned on or off
+ is(
+ Services.prefs.getBoolPref(
+ "browser.safebrowsing.downloads.remote.block_potentially_unwanted"
+ ),
+ !checked,
+ "block_potentially_unwanted is set correctly"
+ );
+ is(
+ Services.prefs.getBoolPref(
+ "browser.safebrowsing.downloads.remote.block_uncommon"
+ ),
+ !checked,
+ "block_uncommon is set correctly"
+ );
+
+ // when the preference is on, the malware table should include these ids
+ let malwareTable = Services.prefs
+ .getCharPref("urlclassifier.malwareTable")
+ .split(",");
+ if (isV2) {
+ is(
+ malwareTable.includes("goog-unwanted-shavar"),
+ !checked,
+ "malware table doesn't include goog-unwanted-shavar"
+ );
+ } else {
+ is(
+ malwareTable.includes("goog-unwanted-proto"),
+ !checked,
+ "malware table doesn't include goog-unwanted-proto"
+ );
+ }
+ is(
+ malwareTable.includes("moztest-unwanted-simple"),
+ !checked,
+ "malware table doesn't include moztest-unwanted-simple"
+ );
+ let sortedMalware = malwareTable.slice(0);
+ sortedMalware.sort();
+ Assert.deepEqual(
+ malwareTable,
+ sortedMalware,
+ "malware table has been sorted"
+ );
+ }
+
+ await checkPrefSwitch(true, true, false);
+ await checkPrefSwitch(false, true, false);
+ await checkPrefSwitch(true, false, false);
+ await checkPrefSwitch(false, false, false);
+ await checkPrefSwitch(true, true, true);
+ await checkPrefSwitch(false, true, true);
+ await checkPrefSwitch(true, false, true);
+ await checkPrefSwitch(false, false, true);
+});
diff --git a/browser/components/preferences/tests/browser_security-3.js b/browser/components/preferences/tests/browser_security-3.js
new file mode 100644
index 0000000000..75872d6629
--- /dev/null
+++ b/browser/components/preferences/tests/browser_security-3.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(async function () {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ registerCleanupFunction(async function () {
+ Services.prefs.unlockPref("browser.safebrowsing.phishing.enabled");
+ Services.prefs.unlockPref("browser.safebrowsing.malware.enabled");
+ Services.prefs.unlockPref("browser.safebrowsing.downloads.enabled");
+ Services.prefs.unlockPref(
+ "browser.safebrowsing.downloads.remote.block_potentially_unwanted"
+ );
+ Services.prefs.unlockPref(
+ "browser.safebrowsing.downloads.remote.block_uncommon"
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+});
+
+// This test just reloads the preferences page for the various tests.
+add_task(async function () {
+ Services.prefs.lockPref("browser.safebrowsing.phishing.enabled");
+ Services.prefs.lockPref("browser.safebrowsing.malware.enabled");
+ Services.prefs.lockPref("browser.safebrowsing.downloads.enabled");
+ Services.prefs.lockPref(
+ "browser.safebrowsing.downloads.remote.block_potentially_unwanted"
+ );
+ Services.prefs.lockPref(
+ "browser.safebrowsing.downloads.remote.block_uncommon"
+ );
+
+ gBrowser.reload();
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ is(
+ doc.getElementById("enableSafeBrowsing").disabled,
+ true,
+ "Safe browsing should be disabled"
+ );
+ is(
+ doc.getElementById("blockDownloads").disabled,
+ true,
+ "Block downloads should be disabled"
+ );
+ is(
+ doc.getElementById("blockUncommonUnwanted").disabled,
+ true,
+ "Block common unwanted should be disabled"
+ );
+
+ Services.prefs.unlockPref("browser.safebrowsing.phishing.enabled");
+ Services.prefs.unlockPref("browser.safebrowsing.malware.enabled");
+
+ gBrowser.reload();
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ doc = gBrowser.selectedBrowser.contentDocument;
+
+ let checkbox = doc.getElementById("enableSafeBrowsing");
+ checkbox.scrollIntoView();
+ EventUtils.synthesizeMouseAtCenter(
+ checkbox,
+ {},
+ gBrowser.selectedBrowser.contentWindow
+ );
+
+ is(
+ doc.getElementById("blockDownloads").disabled,
+ true,
+ "Block downloads should be disabled"
+ );
+ is(
+ doc.getElementById("blockUncommonUnwanted").disabled,
+ true,
+ "Block common unwanted should be disabled"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ checkbox,
+ {},
+ gBrowser.selectedBrowser.contentWindow
+ );
+
+ is(
+ doc.getElementById("blockDownloads").disabled,
+ true,
+ "Block downloads should be disabled"
+ );
+ is(
+ doc.getElementById("blockUncommonUnwanted").disabled,
+ true,
+ "Block common unwanted should be disabled"
+ );
+
+ Services.prefs.unlockPref("browser.safebrowsing.downloads.enabled");
+
+ gBrowser.reload();
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ doc = gBrowser.selectedBrowser.contentDocument;
+
+ checkbox = doc.getElementById("blockDownloads");
+ checkbox.scrollIntoView();
+ EventUtils.synthesizeMouseAtCenter(
+ checkbox,
+ {},
+ gBrowser.selectedBrowser.contentWindow
+ );
+
+ is(
+ doc.getElementById("blockUncommonUnwanted").disabled,
+ true,
+ "Block common unwanted should be disabled"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ checkbox,
+ {},
+ gBrowser.selectedBrowser.contentWindow
+ );
+
+ is(
+ doc.getElementById("blockUncommonUnwanted").disabled,
+ true,
+ "Block common unwanted should be disabled"
+ );
+});
diff --git a/browser/components/preferences/tests/browser_site_login_exceptions.js b/browser/components/preferences/tests/browser_site_login_exceptions.js
new file mode 100644
index 0000000000..508232b234
--- /dev/null
+++ b/browser/components/preferences/tests/browser_site_login_exceptions.js
@@ -0,0 +1,101 @@
+"use strict";
+const PERMISSIONS_URL =
+ "chrome://browser/content/preferences/dialogs/permissions.xhtml";
+
+var exceptionsDialog;
+
+add_task(async function openLoginExceptionsSubDialog() {
+ // ensure rememberSignons is off for this test;
+ ok(
+ !Services.prefs.getBoolPref("signon.rememberSignons"),
+ "Check initial value of signon.rememberSignons pref"
+ );
+
+ // Undo the save password change.
+ registerCleanupFunction(async function () {
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
+ let doc = content.document;
+ let savePasswordCheckBox = doc.getElementById("savePasswords");
+ if (savePasswordCheckBox.checked) {
+ savePasswordCheckBox.click();
+ }
+ });
+
+ gBrowser.removeCurrentTab();
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
+ let doc = content.document;
+ let savePasswordCheckBox = doc.getElementById("savePasswords");
+ Assert.ok(
+ !savePasswordCheckBox.checked,
+ "Save Password CheckBox should be unchecked by default"
+ );
+ savePasswordCheckBox.click();
+
+ let loginExceptionsButton = doc.getElementById("passwordExceptions");
+ loginExceptionsButton.click();
+ });
+
+ exceptionsDialog = await dialogOpened;
+});
+
+add_task(async function addALoginException() {
+ let doc = exceptionsDialog.document;
+
+ let richlistbox = doc.getElementById("permissionsBox");
+ Assert.equal(richlistbox.itemCount, 0, "Row count should initially be 0");
+
+ let inputBox = doc.getElementById("url");
+ inputBox.focus();
+
+ EventUtils.sendString("www.example.com", exceptionsDialog);
+
+ let btnBlock = doc.getElementById("btnBlock");
+ btnBlock.click();
+
+ await TestUtils.waitForCondition(() => richlistbox.itemCount == 2);
+
+ let expectedResult = ["http://www.example.com", "https://www.example.com"];
+ for (let website of expectedResult) {
+ let elements = richlistbox.getElementsByAttribute("origin", website);
+ is(elements.length, 1, "It should find only one coincidence");
+ }
+});
+
+add_task(async function deleteALoginException() {
+ let doc = exceptionsDialog.document;
+
+ let richlistbox = doc.getElementById("permissionsBox");
+ let currentItems = 2;
+ Assert.equal(
+ richlistbox.itemCount,
+ currentItems,
+ `Row count should initially be ${currentItems}`
+ );
+ richlistbox.focus();
+
+ while (richlistbox.itemCount) {
+ richlistbox.selectedIndex = 0;
+
+ if (AppConstants.platform == "macosx") {
+ EventUtils.synthesizeKey("KEY_Backspace");
+ } else {
+ EventUtils.synthesizeKey("KEY_Delete");
+ }
+
+ currentItems -= 1;
+
+ await TestUtils.waitForCondition(
+ () => richlistbox.itemCount == currentItems
+ );
+ is_element_visible(
+ content.gSubDialog._dialogs[0]._box,
+ "Subdialog is visible after deleting an element"
+ );
+ }
+});
diff --git a/browser/components/preferences/tests/browser_site_login_exceptions_policy.js b/browser/components/preferences/tests/browser_site_login_exceptions_policy.js
new file mode 100644
index 0000000000..10f5039c6f
--- /dev/null
+++ b/browser/components/preferences/tests/browser_site_login_exceptions_policy.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { EnterprisePolicyTesting } = ChromeUtils.importESModule(
+ "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
+);
+
+const PERMISSIONS_URL =
+ "chrome://browser/content/preferences/dialogs/permissions.xhtml";
+
+var exceptionsDialog;
+
+add_task(async function openLoginExceptionsSubDialog() {
+ // ensure rememberSignons is off for this test;
+ ok(
+ !Services.prefs.getBoolPref("signon.rememberSignons"),
+ "Check initial value of signon.rememberSignons pref"
+ );
+
+ // Undo the save password change.
+ registerCleanupFunction(async function () {
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
+ let doc = content.document;
+ let savePasswordCheckBox = doc.getElementById("savePasswords");
+ if (savePasswordCheckBox.checked) {
+ savePasswordCheckBox.click();
+ }
+ });
+
+ gBrowser.removeCurrentTab();
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson("");
+ });
+
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson({
+ policies: {
+ PasswordManagerExceptions: ["https://pwexception.example.com"],
+ },
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
+ let doc = content.document;
+ let savePasswordCheckBox = doc.getElementById("savePasswords");
+ savePasswordCheckBox.click();
+
+ let loginExceptionsButton = doc.getElementById("passwordExceptions");
+ loginExceptionsButton.click();
+ });
+
+ exceptionsDialog = await dialogOpened;
+
+ let doc = exceptionsDialog.document;
+
+ let richlistbox = doc.getElementById("permissionsBox");
+ Assert.equal(richlistbox.itemCount, 1, `Row count should initially be 1`);
+
+ richlistbox.focus();
+ richlistbox.selectedIndex = 0;
+ Assert.ok(doc.getElementById("removePermission").disabled);
+});
diff --git a/browser/components/preferences/tests/browser_spotlight.js b/browser/components/preferences/tests/browser_spotlight.js
new file mode 100644
index 0000000000..4a1aae7ec1
--- /dev/null
+++ b/browser/components/preferences/tests/browser_spotlight.js
@@ -0,0 +1,72 @@
+add_task(async function test_openPreferences_spotlight() {
+ for (let [arg, expectedPane, expectedHash, expectedSubcategory] of [
+ ["privacy-reports", "panePrivacy", "#privacy", "reports"],
+ ["privacy-address-autofill", "panePrivacy", "#privacy", "address-autofill"],
+ [
+ "privacy-credit-card-autofill",
+ "panePrivacy",
+ "#privacy",
+ "credit-card-autofill",
+ ],
+ ["privacy-form-autofill", "panePrivacy", "#privacy", "form-autofill"],
+ ["privacy-logins", "panePrivacy", "#privacy", "logins"],
+ [
+ "privacy-trackingprotection",
+ "panePrivacy",
+ "#privacy",
+ "trackingprotection",
+ ],
+ [
+ "privacy-permissions-block-popups",
+ "panePrivacy",
+ "#privacy",
+ "permissions-block-popups",
+ ],
+ ]) {
+ if (
+ arg == "privacy-credit-card-autofill" &&
+ Services.prefs.getCharPref(
+ "extensions.formautofill.creditCards.supported"
+ ) == "off"
+ ) {
+ continue;
+ }
+ if (
+ arg == "privacy-address-autofill" &&
+ Services.prefs.getCharPref(
+ "extensions.formautofill.addresses.supported"
+ ) == "off"
+ ) {
+ continue;
+ }
+
+ let prefs = await openPreferencesViaOpenPreferencesAPI(arg, {
+ leaveOpen: true,
+ });
+ is(prefs.selectedPane, expectedPane, "The right pane is selected");
+ let doc = gBrowser.contentDocument;
+ is(
+ doc.location.hash,
+ expectedHash,
+ "The subcategory should be removed from the URI"
+ );
+ await TestUtils.waitForCondition(
+ () => doc.querySelector(".spotlight"),
+ "Wait for the spotlight"
+ );
+ is(
+ doc.querySelector(".spotlight").getAttribute("data-subcategory"),
+ expectedSubcategory,
+ "The right subcategory is spotlighted"
+ );
+
+ doc.defaultView.spotlight(null);
+ is(
+ doc.querySelector(".spotlight"),
+ null,
+ "The spotlighted section is cleared"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+});
diff --git a/browser/components/preferences/tests/browser_statePartitioning_PBM_strings.js b/browser/components/preferences/tests/browser_statePartitioning_PBM_strings.js
new file mode 100644
index 0000000000..73d12a1bf9
--- /dev/null
+++ b/browser/components/preferences/tests/browser_statePartitioning_PBM_strings.js
@@ -0,0 +1,124 @@
+"use strict";
+
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const COOKIE_BEHAVIOR_PREF = "network.cookie.cookieBehavior";
+const COOKIE_BEHAVIOR_PBM_PREF = "network.cookie.cookieBehavior.pbmode";
+
+const CB_STRICT_FEATURES_PREF = "browser.contentblocking.features.strict";
+const FPI_PREF = "privacy.firstparty.isolate";
+
+async function testCookieBlockingInfoStandard(
+ cookieBehavior,
+ cookieBehaviorPBM,
+ isShown
+) {
+ let defaults = Services.prefs.getDefaultBranch("");
+ defaults.setIntPref(COOKIE_BEHAVIOR_PREF, cookieBehavior);
+ defaults.setIntPref(COOKIE_BEHAVIOR_PBM_PREF, cookieBehaviorPBM);
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let doc = gBrowser.contentDocument;
+
+ // Select standard mode.
+ let standardRadioOption = doc.getElementById("standardRadio");
+ standardRadioOption.click();
+
+ // Check the cookie blocking info for private windows for standard mode.
+ let elts = doc.querySelectorAll(
+ "#contentBlockingOptionStandard .extra-information-label.all-third-party-cookies-private-windows-option"
+ );
+ for (let elt of elts) {
+ is(
+ elt.hidden,
+ !isShown,
+ `The visibility of cookie blocking info for standard mode is correct`
+ );
+ }
+
+ gBrowser.removeCurrentTab();
+}
+
+async function testCookieBlockingInfoStrict(
+ contentBlockingStrictFeatures,
+ isShown
+) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [CB_STRICT_FEATURES_PREF, contentBlockingStrictFeatures],
+ [FPI_PREF, false],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let doc = gBrowser.contentDocument;
+
+ // Select strict mode.
+ let strictRadioOption = doc.getElementById("strictRadio");
+ strictRadioOption.click();
+
+ // Check the cookie blocking info for private windows for strict mode.
+ let elts = doc.querySelectorAll(
+ "#contentBlockingOptionStrict .extra-information-label.all-third-party-cookies-private-windows-option"
+ );
+ for (let elt of elts) {
+ is(
+ elt.hidden,
+ !isShown,
+ `The cookie blocking info is hidden for strict mode`
+ );
+ }
+
+ gBrowser.removeCurrentTab();
+}
+
+add_task(async function runTests() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[FPI_PREF, false]],
+ });
+
+ let defaults = Services.prefs.getDefaultBranch("");
+ let originalCookieBehavior = defaults.getIntPref(COOKIE_BEHAVIOR_PREF);
+ let originalCookieBehaviorPBM = defaults.getIntPref(COOKIE_BEHAVIOR_PBM_PREF);
+
+ // Test if the cookie blocking info for state partitioning in PBM is
+ // shown in standard mode if the regular cookieBehavior is
+ // BEHAVIOR_REJECT_TRACKER and the private cookieBehavior is
+ // BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
+ await testCookieBlockingInfoStandard(
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ true
+ );
+
+ // Test if the cookie blocking info is hidden in standard mode if both
+ // cookieBehaviors are the same.
+ await testCookieBlockingInfoStandard(
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ false
+ );
+
+ // Test if the cookie blocking info is hidden for strict mode if
+ // cookieBehaviors both are BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN in
+ // the strict feature value.
+ await testCookieBlockingInfoStrict(
+ "tp,tpPrivate,cookieBehavior5,cookieBehaviorPBM5,cm,fp,stp,emailTP,emailTPPrivate,lvl2,rp,rpTop,ocsp",
+ false
+ );
+
+ // Test if the cookie blocking info is shown for strict mode if the regular
+ // cookieBehavior is BEHAVIOR_REJECT_TRACKER and the private cookieBehavior is
+ // BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
+ await testCookieBlockingInfoStrict(
+ "tp,tpPrivate,cookieBehavior4,cookieBehaviorPBM5,cm,fp,stp,emailTP,emailTPPrivate,lvl2,rp,rpTop,ocsp",
+ true
+ );
+
+ defaults.setIntPref(COOKIE_BEHAVIOR_PREF, originalCookieBehavior);
+ defaults.setIntPref(COOKIE_BEHAVIOR_PBM_PREF, originalCookieBehaviorPBM);
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/preferences/tests/browser_statePartitioning_strings.js b/browser/components/preferences/tests/browser_statePartitioning_strings.js
new file mode 100644
index 0000000000..aed7e26977
--- /dev/null
+++ b/browser/components/preferences/tests/browser_statePartitioning_strings.js
@@ -0,0 +1,79 @@
+"use strict";
+
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const CB_STRICT_FEATURES_PREF = "browser.contentblocking.features.strict";
+const CB_STRICT_FEATURES_VALUE = "tp,tpPrivate,cookieBehavior5,cm,fp,stp,lvl2";
+const FPI_PREF = "privacy.firstparty.isolate";
+const COOKIE_BEHAVIOR_PREF = "network.cookie.cookieBehavior";
+const COOKIE_BEHAVIOR_VALUE = 5;
+
+async function testStrings() {
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ let doc = gBrowser.contentDocument;
+
+ // Check the cookie blocking info strings
+ let elts = doc.querySelectorAll(
+ ".extra-information-label.cross-site-cookies-option"
+ );
+ for (let elt of elts) {
+ ok(!elt.hidden, "The new cross-site cookies info label is visible");
+ }
+
+ // Check the learn more strings
+ elts = doc.querySelectorAll(
+ ".tail-with-learn-more.content-blocking-warning-description"
+ );
+ for (let elt of elts) {
+ let id = doc.l10n.getAttributes(elt).id;
+ is(
+ id,
+ "content-blocking-and-isolating-etp-warning-description-2",
+ "The correct warning description string is in use"
+ );
+ }
+
+ // Check the cookie blocking mode menu option string
+ let elt = doc.querySelector("#isolateCookiesSocialMedia");
+ let id = doc.l10n.getAttributes(elt).id;
+ is(
+ id,
+ "sitedata-option-block-cross-site-cookies",
+ "The correct string is in use for the cookie blocking option"
+ );
+
+ // Check the FPI warning is hidden with FPI off
+ let warningElt = doc.getElementById("fpiIncompatibilityWarning");
+ is(warningElt.hidden, true, "The FPI warning is hidden");
+
+ gBrowser.removeCurrentTab();
+
+ // Check the FPI warning is shown only if MVP UI is enabled when FPI is on
+ await SpecialPowers.pushPrefEnv({ set: [[FPI_PREF, true]] });
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ doc = gBrowser.contentDocument;
+ warningElt = doc.getElementById("fpiIncompatibilityWarning");
+ ok(!warningElt.hidden, `The FPI warning is visible`);
+ await SpecialPowers.popPrefEnv();
+
+ gBrowser.removeCurrentTab();
+}
+
+add_task(async function runTests() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [CB_STRICT_FEATURES_PREF, CB_STRICT_FEATURES_VALUE],
+ [FPI_PREF, false],
+ ],
+ });
+ let defaults = Services.prefs.getDefaultBranch("");
+ let originalCookieBehavior = defaults.getIntPref(COOKIE_BEHAVIOR_PREF);
+ defaults.setIntPref(COOKIE_BEHAVIOR_PREF, COOKIE_BEHAVIOR_VALUE);
+ registerCleanupFunction(() => {
+ defaults.setIntPref(COOKIE_BEHAVIOR_PREF, originalCookieBehavior);
+ });
+
+ await testStrings();
+});
diff --git a/browser/components/preferences/tests/browser_subdialogs.js b/browser/components/preferences/tests/browser_subdialogs.js
new file mode 100644
index 0000000000..9342575886
--- /dev/null
+++ b/browser/components/preferences/tests/browser_subdialogs.js
@@ -0,0 +1,639 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests for the sub-dialog infrastructure, not for actual sub-dialog functionality.
+ */
+
+const gDialogURL = getRootDirectory(gTestPath) + "subdialog.xhtml";
+const gDialogURL2 = getRootDirectory(gTestPath) + "subdialog2.xhtml";
+
+function open_subdialog_and_test_generic_start_state(
+ browser,
+ domcontentloadedFn,
+ url = gDialogURL
+) {
+ let domcontentloadedFnStr = domcontentloadedFn
+ ? "(" + domcontentloadedFn.toString() + ")()"
+ : "";
+ return SpecialPowers.spawn(
+ browser,
+ [{ url, domcontentloadedFnStr }],
+ async function (args) {
+ let rv = { acceptCount: 0 };
+ let win = content.window;
+ content.gSubDialog.open(args.url, undefined, rv);
+ let subdialog = content.gSubDialog._topDialog;
+
+ info("waiting for subdialog DOMFrameContentLoaded");
+ let dialogOpenPromise;
+ await new Promise(resolve => {
+ win.addEventListener(
+ "DOMFrameContentLoaded",
+ function frameContentLoaded(ev) {
+ // We can get events for loads in other frames, so we have to filter
+ // those out.
+ if (ev.target != subdialog._frame) {
+ return;
+ }
+ win.removeEventListener(
+ "DOMFrameContentLoaded",
+ frameContentLoaded
+ );
+ dialogOpenPromise = ContentTaskUtils.waitForEvent(
+ subdialog._overlay,
+ "dialogopen"
+ );
+ resolve();
+ },
+ { capture: true }
+ );
+ });
+ let result;
+ if (args.domcontentloadedFnStr) {
+ // eslint-disable-next-line no-eval
+ result = eval(args.domcontentloadedFnStr);
+ }
+
+ info("waiting for subdialog load");
+ await dialogOpenPromise;
+ info("subdialog window is loaded");
+
+ let expectedStyleSheetURLs = subdialog._injectedStyleSheets.slice(0);
+ for (let styleSheet of subdialog._frame.contentDocument.styleSheets) {
+ let index = expectedStyleSheetURLs.indexOf(styleSheet.href);
+ if (index >= 0) {
+ expectedStyleSheetURLs.splice(index, 1);
+ }
+ }
+
+ Assert.ok(
+ !!subdialog._frame.contentWindow,
+ "The dialog should be non-null"
+ );
+ Assert.notEqual(
+ subdialog._frame.contentWindow.location.toString(),
+ "about:blank",
+ "Subdialog URL should not be about:blank"
+ );
+ Assert.equal(
+ win.getComputedStyle(subdialog._overlay).visibility,
+ "visible",
+ "Overlay should be visible"
+ );
+ Assert.equal(
+ expectedStyleSheetURLs.length,
+ 0,
+ "No stylesheets that were expected are missing"
+ );
+ return result;
+ }
+ );
+}
+
+async function close_subdialog_and_test_generic_end_state(
+ browser,
+ closingFn,
+ closingButton,
+ acceptCount,
+ options
+) {
+ let getDialogsCount = () => {
+ return SpecialPowers.spawn(
+ browser,
+ [],
+ () => content.window.gSubDialog._dialogs.length
+ );
+ };
+ let getStackChildrenCount = () => {
+ return SpecialPowers.spawn(
+ browser,
+ [],
+ () => content.window.gSubDialog._dialogStack.children.length
+ );
+ };
+ let dialogclosingPromise = SpecialPowers.spawn(
+ browser,
+ [{ closingButton, acceptCount }],
+ async function (expectations) {
+ let win = content.window;
+ let subdialog = win.gSubDialog._topDialog;
+ let frame = subdialog._frame;
+
+ let frameWinUnload = ContentTaskUtils.waitForEvent(
+ frame.contentWindow,
+ "unload",
+ true
+ );
+
+ let actualAcceptCount;
+ info("waiting for dialogclosing");
+ info("URI " + frame.currentURI?.spec);
+ let closingEvent = await ContentTaskUtils.waitForEvent(
+ frame.contentWindow,
+ "dialogclosing",
+ true,
+ () => {
+ actualAcceptCount = frame.contentWindow?.arguments[0].acceptCount;
+ return true;
+ }
+ );
+
+ info("Waiting for subdialog unload");
+ await frameWinUnload;
+
+ let contentClosingButton = closingEvent.detail.button;
+
+ Assert.notEqual(
+ win.getComputedStyle(subdialog._overlay).visibility,
+ "visible",
+ "overlay is not visible"
+ );
+ Assert.equal(
+ frame.getAttribute("style"),
+ "",
+ "inline styles should be cleared"
+ );
+ Assert.equal(
+ contentClosingButton,
+ expectations.closingButton,
+ "closing event should indicate button was '" +
+ expectations.closingButton +
+ "'"
+ );
+ Assert.equal(
+ actualAcceptCount,
+ expectations.acceptCount,
+ "should be 1 if accepted, 0 if canceled, undefined if closed w/out button"
+ );
+ }
+ );
+ let initialDialogsCount = await getDialogsCount();
+ let initialStackChildrenCount = await getStackChildrenCount();
+ if (options && options.runClosingFnOutsideOfContentTask) {
+ await closingFn();
+ } else {
+ SpecialPowers.spawn(browser, [], closingFn);
+ }
+
+ await dialogclosingPromise;
+ let endDialogsCount = await getDialogsCount();
+ let endStackChildrenCount = await getStackChildrenCount();
+ Assert.equal(
+ initialDialogsCount - 1,
+ endDialogsCount,
+ "dialog count should decrease by 1"
+ );
+ Assert.equal(
+ initialStackChildrenCount - 1,
+ endStackChildrenCount,
+ "stack children count should decrease by 1"
+ );
+}
+
+let tab;
+
+add_task(async function test_initialize() {
+ tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:preferences"
+ );
+});
+
+add_task(
+ async function check_titlebar_focus_returnval_titlechanges_accepting() {
+ await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ let domtitlechangedPromise = BrowserTestUtils.waitForEvent(
+ tab.linkedBrowser,
+ "DOMTitleChanged"
+ );
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let dialog = content.window.gSubDialog._topDialog;
+ let dialogWin = dialog._frame.contentWindow;
+ let dialogTitleElement = dialog._titleElement;
+ Assert.equal(
+ dialogTitleElement.textContent,
+ "Sample sub-dialog",
+ "Title should be correct initially"
+ );
+ Assert.equal(
+ dialogWin.document.activeElement.value,
+ "Default text",
+ "Textbox with correct text is focused"
+ );
+ dialogWin.document.title = "Updated title";
+ });
+
+ info("waiting for DOMTitleChanged event");
+ await domtitlechangedPromise;
+
+ SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let dialogTitleElement =
+ content.window.gSubDialog._topDialog._titleElement;
+ Assert.equal(
+ dialogTitleElement.textContent,
+ "Updated title",
+ "subdialog should have updated title"
+ );
+ });
+
+ // Accept the dialog
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ content.window.gSubDialog._topDialog._frame.contentDocument
+ .getElementById("subDialog")
+ .acceptDialog();
+ },
+ "accept",
+ 1
+ );
+ }
+);
+
+add_task(async function check_canceling_dialog() {
+ await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ content.window.gSubDialog._topDialog._frame.contentDocument
+ .getElementById("subDialog")
+ .cancelDialog();
+ },
+ "cancel",
+ 0
+ );
+});
+
+add_task(async function check_reopening_dialog() {
+ await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+ info("opening another dialog which will close the first");
+ await open_subdialog_and_test_generic_start_state(
+ tab.linkedBrowser,
+ "",
+ gDialogURL2
+ );
+
+ SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let win = content.window;
+ let dialogs = win.gSubDialog._dialogs;
+ let lowerDialog = dialogs[0];
+ let topDialog = dialogs[1];
+ Assert.equal(dialogs.length, 2, "There should be two visible dialogs");
+ Assert.equal(
+ win.getComputedStyle(topDialog._overlay).visibility,
+ "visible",
+ "The top dialog should be visible"
+ );
+ Assert.equal(
+ win.getComputedStyle(lowerDialog._overlay).visibility,
+ "visible",
+ "The lower dialog should be visible"
+ );
+ Assert.equal(
+ win.getComputedStyle(topDialog._overlay).backgroundColor,
+ "rgba(0, 0, 0, 0.5)",
+ "The top dialog should have a semi-transparent overlay"
+ );
+ Assert.equal(
+ win.getComputedStyle(lowerDialog._overlay).backgroundColor,
+ "rgba(0, 0, 0, 0)",
+ "The lower dialog should not have an overlay"
+ );
+ });
+
+ info("closing two dialogs");
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ content.window.gSubDialog._topDialog._frame.contentDocument
+ .getElementById("subDialog")
+ .acceptDialog();
+ },
+ "accept",
+ 1
+ );
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ content.window.gSubDialog._topDialog._frame.contentDocument
+ .getElementById("subDialog")
+ .acceptDialog();
+ },
+ "accept",
+ 1
+ );
+});
+
+add_task(async function check_opening_while_closing() {
+ await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+ info("closing");
+ content.window.gSubDialog._topDialog.close();
+ info("reopening immediately after calling .close()");
+ await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ content.window.gSubDialog._topDialog._frame.contentDocument
+ .getElementById("subDialog")
+ .acceptDialog();
+ },
+ "accept",
+ 1
+ );
+});
+
+add_task(async function window_close_on_dialog() {
+ await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ content.window.gSubDialog._topDialog._frame.contentWindow.close();
+ },
+ null,
+ 0
+ );
+});
+
+add_task(async function click_close_button_on_dialog() {
+ await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ return BrowserTestUtils.synthesizeMouseAtCenter(
+ ".dialogClose",
+ {},
+ tab.linkedBrowser
+ );
+ },
+ null,
+ 0,
+ { runClosingFnOutsideOfContentTask: true }
+ );
+});
+
+add_task(async function background_click_should_close_dialog() {
+ await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ // Clicking on an inactive part of dialog itself should not close the dialog.
+ // Click the dialog title bar here to make sure nothing happens.
+ info("clicking the dialog title bar");
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ ".dialogTitle",
+ {},
+ tab.linkedBrowser
+ );
+
+ // Close the dialog by clicking on the overlay background. Simulate a click
+ // at point (2,2) instead of (0,0) so we are sure we're clicking on the
+ // overlay background instead of some boundary condition that a real user
+ // would never click.
+ info("clicking the overlay background");
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ return BrowserTestUtils.synthesizeMouseAtPoint(
+ 2,
+ 2,
+ {},
+ tab.linkedBrowser
+ );
+ },
+ null,
+ 0,
+ { runClosingFnOutsideOfContentTask: true }
+ );
+});
+
+add_task(async function escape_should_close_dialog() {
+ await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ return BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, tab.linkedBrowser);
+ },
+ "cancel",
+ 0,
+ { runClosingFnOutsideOfContentTask: true }
+ );
+});
+
+add_task(async function correct_width_and_height_should_be_used_for_dialog() {
+ await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ function fuzzyEqual(value, expectedValue, fuzz, msg) {
+ Assert.greaterOrEqual(expectedValue + fuzz, value, msg);
+ Assert.lessOrEqual(expectedValue - fuzz, value, msg);
+ }
+ let topDialog = content.gSubDialog._topDialog;
+ let frameStyle = content.getComputedStyle(topDialog._frame);
+ let dialogStyle = topDialog.frameContentWindow.getComputedStyle(
+ topDialog.frameContentWindow.document.documentElement
+ );
+ let fontSize = parseFloat(dialogStyle.fontSize);
+ let height = parseFloat(frameStyle.height);
+ let width = parseFloat(frameStyle.width);
+
+ fuzzyEqual(
+ width,
+ fontSize * 32,
+ 2,
+ "Width should be set on the frame from the dialog"
+ );
+ fuzzyEqual(
+ height,
+ fontSize * 5,
+ 2,
+ "Height should be set on the frame from the dialog"
+ );
+ });
+
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ content.window.gSubDialog._topDialog._frame.contentWindow.close();
+ },
+ null,
+ 0
+ );
+});
+
+add_task(
+ async function wrapped_text_in_dialog_should_have_expected_scrollHeight() {
+ let oldHeight = await open_subdialog_and_test_generic_start_state(
+ tab.linkedBrowser,
+ function domcontentloadedFn() {
+ let frame = content.window.gSubDialog._topDialog._frame;
+ let doc = frame.contentDocument;
+ let scrollHeight = doc.documentElement.scrollHeight;
+ doc.documentElement.style.removeProperty("height");
+ doc.getElementById("desc").textContent = `
+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
+ laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
+ architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
+ sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
+ laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
+ architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
+ sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
+ laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
+ architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
+ sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
+ voluptatem sequi nesciunt.`;
+ return scrollHeight;
+ }
+ );
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [oldHeight],
+ async function (contentOldHeight) {
+ function fuzzyEqual(value, expectedValue, fuzz, msg) {
+ Assert.greaterOrEqual(expectedValue + fuzz, value, msg);
+ Assert.lessOrEqual(expectedValue - fuzz, value, msg);
+ }
+ let topDialog = content.gSubDialog._topDialog;
+ let frame = topDialog._frame;
+ let frameStyle = content.getComputedStyle(frame);
+ let docEl = frame.contentDocument.documentElement;
+ let dialogStyle = topDialog.frameContentWindow.getComputedStyle(docEl);
+ let fontSize = parseFloat(dialogStyle.fontSize);
+ let height = parseFloat(frameStyle.height);
+ let width = parseFloat(frameStyle.width);
+
+ fuzzyEqual(
+ width,
+ 32 * fontSize,
+ 2,
+ "Width should be set on the frame from the dialog"
+ );
+ Assert.ok(
+ docEl.scrollHeight > contentOldHeight,
+ "Content height increased (from " +
+ contentOldHeight +
+ " to " +
+ docEl.scrollHeight +
+ ")."
+ );
+ fuzzyEqual(
+ height,
+ docEl.scrollHeight,
+ 2,
+ "Height on the frame should be higher now. " +
+ "This test may fail on certain screen resoluition. " +
+ "See bug 1420576 and bug 1205717."
+ );
+ }
+ );
+
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ content.window.gSubDialog._topDialog._frame.contentWindow.window.close();
+ },
+ null,
+ 0
+ );
+ }
+);
+
+add_task(async function dialog_too_tall_should_get_reduced_in_height() {
+ await open_subdialog_and_test_generic_start_state(
+ tab.linkedBrowser,
+ function domcontentloadedFn() {
+ let frame = content.window.gSubDialog._topDialog._frame;
+ frame.contentDocument.documentElement.style.height = "100000px";
+ }
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ function fuzzyEqual(value, expectedValue, fuzz, msg) {
+ Assert.greaterOrEqual(expectedValue + fuzz, value, msg);
+ Assert.lessOrEqual(expectedValue - fuzz, value, msg);
+ }
+ let topDialog = content.gSubDialog._topDialog;
+ let frame = topDialog._frame;
+ let frameStyle = content.getComputedStyle(frame);
+ let dialogStyle = topDialog.frameContentWindow.getComputedStyle(
+ frame.contentDocument.documentElement
+ );
+ let fontSize = parseFloat(dialogStyle.fontSize);
+ let height = parseFloat(frameStyle.height);
+ let width = parseFloat(frameStyle.width);
+ fuzzyEqual(
+ width,
+ 32 * fontSize,
+ 2,
+ "Width should be set on the frame from the dialog"
+ );
+ Assert.less(
+ height,
+ content.window.innerHeight,
+ "Height on the frame should be smaller than window's innerHeight"
+ );
+ });
+
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ content.window.gSubDialog._topDialog._frame.contentWindow.window.close();
+ },
+ null,
+ 0
+ );
+});
+
+add_task(
+ async function scrollWidth_and_scrollHeight_from_subdialog_should_size_the_browser() {
+ await open_subdialog_and_test_generic_start_state(
+ tab.linkedBrowser,
+ function domcontentloadedFn() {
+ let frame = content.window.gSubDialog._topDialog._frame;
+ frame.contentDocument.documentElement.style.removeProperty("height");
+ frame.contentDocument.documentElement.style.removeProperty("width");
+ }
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let frame = content.window.gSubDialog._topDialog._frame;
+ Assert.ok(
+ frame.style.width.endsWith("px"),
+ "Width (" +
+ frame.style.width +
+ ") should be set to a px value of the scrollWidth from the dialog"
+ );
+ let cs = content.getComputedStyle(frame);
+ Assert.stringMatches(
+ cs.getPropertyValue("--subdialog-inner-height"),
+ /px$/,
+ "Height (" +
+ cs.getPropertyValue("--subdialog-inner-height") +
+ ") should be set to a px value of the scrollHeight from the dialog"
+ );
+ });
+
+ await close_subdialog_and_test_generic_end_state(
+ tab.linkedBrowser,
+ function () {
+ content.window.gSubDialog._topDialog._frame.contentWindow.window.close();
+ },
+ null,
+ 0
+ );
+ }
+);
+
+add_task(async function test_shutdown() {
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/preferences/tests/browser_sync_chooseWhatToSync.js b/browser/components/preferences/tests/browser_sync_chooseWhatToSync.js
new file mode 100644
index 0000000000..b36d9ecea3
--- /dev/null
+++ b/browser/components/preferences/tests/browser_sync_chooseWhatToSync.js
@@ -0,0 +1,178 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { UIState } = ChromeUtils.importESModule(
+ "resource://services-sync/UIState.sys.mjs"
+);
+
+// This obj will be used in both tests
+// First test makes sure accepting the preferences matches these values
+// Second test makes sure the cancel dialog STILL matches these values
+const syncPrefs = {
+ "services.sync.engine.addons": false,
+ "services.sync.engine.bookmarks": true,
+ "services.sync.engine.history": true,
+ "services.sync.engine.tabs": false,
+ "services.sync.engine.prefs": false,
+ "services.sync.engine.passwords": false,
+ "services.sync.engine.addresses": false,
+ "services.sync.engine.creditcards": false,
+};
+
+add_setup(async () => {
+ UIState._internal.notifyStateUpdated = () => {};
+ const origNotifyStateUpdated = UIState._internal.notifyStateUpdated;
+ const origGet = UIState.get;
+ UIState.get = () => {
+ return { status: UIState.STATUS_SIGNED_IN, email: "foo@bar.com" };
+ };
+
+ registerCleanupFunction(() => {
+ UIState._internal.notifyStateUpdated = origNotifyStateUpdated;
+ UIState.get = origGet;
+ });
+});
+
+/**
+ * We don't actually enable sync here, but we just check that the preferences are correct
+ * when the callback gets hit (accepting/cancelling the dialog)
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=1584132.
+ */
+
+add_task(async function testDialogAccept() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["identity.fxaccounts.enabled", true]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ // This will check if the callback was actually called during the test
+ let callbackCalled = false;
+
+ // Enabling all the sync UI is painful in tests, so we just open the dialog manually
+ let syncWindow = await openAndLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml",
+ null,
+ {},
+ () => {
+ for (const [prefKey, prefValue] of Object.entries(syncPrefs)) {
+ Assert.equal(
+ Services.prefs.getBoolPref(prefKey),
+ prefValue,
+ `${prefValue} is expected value`
+ );
+ }
+ callbackCalled = true;
+ }
+ );
+
+ Assert.ok(syncWindow, "Choose what to sync window opened");
+ let syncChooseDialog =
+ syncWindow.document.getElementById("syncChooseOptions");
+ let syncCheckboxes = syncChooseDialog.querySelectorAll(
+ "checkbox[preference]"
+ );
+
+ // Adjust the checkbox values to the expectedValues in the list
+ [...syncCheckboxes].forEach(checkbox => {
+ if (syncPrefs[checkbox.getAttribute("preference")] !== checkbox.checked) {
+ checkbox.click();
+ }
+ });
+
+ syncChooseDialog.acceptDialog();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ Assert.ok(callbackCalled, "Accept callback was called");
+});
+
+add_task(async function testDialogCancel() {
+ const cancelSyncPrefs = {
+ "services.sync.engine.addons": true,
+ "services.sync.engine.bookmarks": false,
+ "services.sync.engine.history": true,
+ "services.sync.engine.tabs": true,
+ "services.sync.engine.prefs": false,
+ "services.sync.engine.passwords": true,
+ "services.sync.engine.addresses": true,
+ "services.sync.engine.creditcards": false,
+ };
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["identity.fxaccounts.enabled", true]],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ // This will check if the callback was actually called during the test
+ let callbackCalled = false;
+
+ // Enabling all the sync UI is painful in tests, so we just open the dialog manually
+ let syncWindow = await openAndLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml",
+ null,
+ {},
+ () => {
+ // We want to test against our previously accepted values in the last test
+ for (const [prefKey, prefValue] of Object.entries(syncPrefs)) {
+ Assert.equal(
+ Services.prefs.getBoolPref(prefKey),
+ prefValue,
+ `${prefValue} is expected value`
+ );
+ }
+ callbackCalled = true;
+ }
+ );
+
+ ok(syncWindow, "Choose what to sync window opened");
+ let syncChooseDialog =
+ syncWindow.document.getElementById("syncChooseOptions");
+ let syncCheckboxes = syncChooseDialog.querySelectorAll(
+ "checkbox[preference]"
+ );
+
+ // This time we're adjusting to the cancel list
+ [...syncCheckboxes].forEach(checkbox => {
+ if (
+ cancelSyncPrefs[checkbox.getAttribute("preference")] !== checkbox.checked
+ ) {
+ checkbox.click();
+ }
+ });
+
+ syncChooseDialog.cancelDialog();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ Assert.ok(callbackCalled, "Cancel callback was called");
+});
+
+/**
+ * Tests that this subdialog can be opened via
+ * about:preferences?action=choose-what-to-sync#sync
+ */
+add_task(async function testDialogLaunchFromURI() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["identity.fxaccounts.enabled", true]],
+ });
+
+ let dialogEventPromise = BrowserTestUtils.waitForEvent(
+ window,
+ "dialogopen",
+ true
+ );
+ await BrowserTestUtils.withNewTab(
+ "about:preferences?action=choose-what-to-sync#sync",
+ async browser => {
+ let dialogEvent = await dialogEventPromise;
+ Assert.equal(
+ dialogEvent.detail.dialog._frame.contentWindow.location,
+ "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml"
+ );
+ }
+ );
+});
diff --git a/browser/components/preferences/tests/browser_sync_disabled.js b/browser/components/preferences/tests/browser_sync_disabled.js
new file mode 100644
index 0000000000..1f8518a1e4
--- /dev/null
+++ b/browser/components/preferences/tests/browser_sync_disabled.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test that we don't show sync pane when it's disabled.
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=1536752.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["identity.fxaccounts.enabled", false]],
+ });
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+ ok(
+ gBrowser.contentDocument.getElementById("category-sync").hidden,
+ "sync category hidden"
+ );
+
+ // Check that we don't get any results in sync when searching:
+ await evaluateSearchResults("sync", "no-results-message");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/browser_sync_pairing.js b/browser/components/preferences/tests/browser_sync_pairing.js
new file mode 100644
index 0000000000..6491007a38
--- /dev/null
+++ b/browser/components/preferences/tests/browser_sync_pairing.js
@@ -0,0 +1,149 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { UIState } = ChromeUtils.importESModule(
+ "resource://services-sync/UIState.sys.mjs"
+);
+const { FxAccountsPairingFlow } = ChromeUtils.importESModule(
+ "resource://gre/modules/FxAccountsPairing.sys.mjs"
+);
+
+// Use sinon for mocking.
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+let flowCounter = 0;
+
+add_setup(async function () {
+ Services.prefs.setBoolPref("identity.fxaccounts.pairing.enabled", true);
+ // Sync start-up might interfere with our tests, don't let UIState send UI updates.
+ const origNotifyStateUpdated = UIState._internal.notifyStateUpdated;
+ UIState._internal.notifyStateUpdated = () => {};
+
+ const origGet = UIState.get;
+ UIState.get = () => {
+ return { status: UIState.STATUS_SIGNED_IN, email: "foo@bar.com" };
+ };
+
+ const origStart = FxAccountsPairingFlow.start;
+ FxAccountsPairingFlow.start = ({ emitter: e }) => {
+ return `https://foo.bar/${flowCounter++}`;
+ };
+
+ registerCleanupFunction(() => {
+ UIState._internal.notifyStateUpdated = origNotifyStateUpdated;
+ UIState.get = origGet;
+ FxAccountsPairingFlow.start = origStart;
+ });
+});
+
+add_task(async function testShowsQRCode() {
+ await runWithPairingDialog(async win => {
+ let doc = win.document;
+ let qrContainer = doc.getElementById("qrContainer");
+ let qrWrapper = doc.getElementById("qrWrapper");
+
+ await TestUtils.waitForCondition(
+ () => qrWrapper.getAttribute("pairing-status") == "ready"
+ );
+
+ // Verify that a QRcode is being shown.
+ Assert.ok(
+ qrContainer.style.backgroundImage.startsWith(
+ `url("data:image/gif;base64,R0lGODdhOgA6AIAAAAAAAP///ywAAAAAOgA6AAAC/4yPqcvtD6OctNqLs968+w+G4gKU5nkiJYO2JuW6KsDGKEw3a7AbPZ+r4Ry7nzFIQkKKN6Avlzowo78`
+ )
+ );
+
+ // Close the dialog.
+ let promiseUnloaded = BrowserTestUtils.waitForEvent(win, "unload");
+ gBrowser.contentDocument.querySelector(".dialogClose").click();
+
+ info("waiting for dialog to unload");
+ await promiseUnloaded;
+ });
+});
+
+add_task(async function testCantShowQrCode() {
+ const origStart = FxAccountsPairingFlow.start;
+ FxAccountsPairingFlow.start = async () => {
+ throw new Error("boom");
+ };
+ await runWithPairingDialog(async win => {
+ let doc = win.document;
+ let qrWrapper = doc.getElementById("qrWrapper");
+
+ await TestUtils.waitForCondition(
+ () => qrWrapper.getAttribute("pairing-status") == "error"
+ );
+
+ // Close the dialog.
+ let promiseUnloaded = BrowserTestUtils.waitForEvent(win, "unload");
+ gBrowser.contentDocument.querySelector(".dialogClose").click();
+
+ info("waiting for dialog to unload");
+ await promiseUnloaded;
+ });
+ FxAccountsPairingFlow.start = origStart;
+});
+
+add_task(async function testSwitchToWebContent() {
+ await runWithPairingDialog(async win => {
+ let doc = win.document;
+ let qrWrapper = doc.getElementById("qrWrapper");
+
+ await TestUtils.waitForCondition(
+ () => qrWrapper.getAttribute("pairing-status") == "ready"
+ );
+
+ const spySwitchURL = sinon.spy(win.gFxaPairDeviceDialog, "_switchToUrl");
+ const emitter = win.gFxaPairDeviceDialog._emitter;
+ emitter.emit("view:SwitchToWebContent", "about:robots");
+
+ Assert.equal(spySwitchURL.callCount, 1);
+ });
+});
+
+add_task(async function testError() {
+ await runWithPairingDialog(async win => {
+ let doc = win.document;
+ let qrWrapper = doc.getElementById("qrWrapper");
+
+ await TestUtils.waitForCondition(
+ () => qrWrapper.getAttribute("pairing-status") == "ready"
+ );
+
+ const emitter = win.gFxaPairDeviceDialog._emitter;
+ emitter.emit("view:Error");
+
+ await TestUtils.waitForCondition(
+ () => qrWrapper.getAttribute("pairing-status") == "error"
+ );
+
+ // Close the dialog.
+ let promiseUnloaded = BrowserTestUtils.waitForEvent(win, "unload");
+ gBrowser.contentDocument.querySelector(".dialogClose").click();
+
+ info("waiting for dialog to unload");
+ await promiseUnloaded;
+ });
+});
+
+async function runWithPairingDialog(test) {
+ await openPreferencesViaOpenPreferencesAPI("paneSync", { leaveOpen: true });
+
+ let promiseSubDialogLoaded = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/fxaPairDevice.xhtml"
+ );
+ gBrowser.contentWindow.gSyncPane.pairAnotherDevice();
+
+ let win = await promiseSubDialogLoaded;
+
+ await test(win);
+
+ sinon.restore();
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}
diff --git a/browser/components/preferences/tests/browser_warning_permanent_private_browsing.js b/browser/components/preferences/tests/browser_warning_permanent_private_browsing.js
new file mode 100644
index 0000000000..8d1fa3c80b
--- /dev/null
+++ b/browser/components/preferences/tests/browser_warning_permanent_private_browsing.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function checkForPrompt(prefVal) {
+ return async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.history.custom", true],
+ ["browser.privatebrowsing.autostart", !prefVal],
+ ],
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
+ leaveOpen: true,
+ });
+ let doc = gBrowser.contentDocument;
+ is(
+ doc.getElementById("historyMode").value,
+ "custom",
+ "Expect custom history mode"
+ );
+
+ // Stub out the prompt method as an easy way to check it was shown. We throw away
+ // the tab straight after so don't need to bother restoring it.
+ let promptFired = false;
+ doc.defaultView.confirmRestartPrompt = () => {
+ promptFired = true;
+ return doc.defaultView.CONFIRM_RESTART_PROMPT_RESTART_NOW;
+ };
+ // Tick the checkbox and pretend the user did it:
+ let checkbox = doc.getElementById("privateBrowsingAutoStart");
+ checkbox.checked = prefVal;
+ checkbox.doCommand();
+
+ // Now the prompt should have shown.
+ ok(
+ promptFired,
+ `Expect a prompt when turning permanent private browsing ${
+ prefVal ? "on" : "off"
+ }!`
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ };
+}
+
+/**
+ * Check we show the prompt if the permanent private browsing pref is false
+ * and we flip the checkbox to true.
+ */
+add_task(checkForPrompt(true));
+
+/**
+ * Check it works in the other direction:
+ */
+add_task(checkForPrompt(false));
diff --git a/browser/components/preferences/tests/empty_pdf_file.pdf b/browser/components/preferences/tests/empty_pdf_file.pdf
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/browser/components/preferences/tests/empty_pdf_file.pdf
diff --git a/browser/components/preferences/tests/engine1/manifest.json b/browser/components/preferences/tests/engine1/manifest.json
new file mode 100644
index 0000000000..5fa44ea692
--- /dev/null
+++ b/browser/components/preferences/tests/engine1/manifest.json
@@ -0,0 +1,27 @@
+{
+ "name": "engine1",
+ "manifest_version": 2,
+ "version": "1.0",
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "engine1@search.mozilla.org"
+ }
+ },
+ "hidden": true,
+ "description": "A small test engine",
+ "icons": {
+ "16": "favicon.ico"
+ },
+ "chrome_settings_overrides": {
+ "search_provider": {
+ "name": "engine1",
+ "search_url": "https://1.example.com/search",
+ "params": [
+ {
+ "name": "q",
+ "value": "{searchTerms}"
+ }
+ ]
+ }
+ }
+}
diff --git a/browser/components/preferences/tests/engine2/manifest.json b/browser/components/preferences/tests/engine2/manifest.json
new file mode 100644
index 0000000000..7ab094198b
--- /dev/null
+++ b/browser/components/preferences/tests/engine2/manifest.json
@@ -0,0 +1,27 @@
+{
+ "name": "engine2",
+ "manifest_version": 2,
+ "version": "1.0",
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "engine2@search.mozilla.org"
+ }
+ },
+ "hidden": true,
+ "description": "A small test engine",
+ "icons": {
+ "16": "favicon.ico"
+ },
+ "chrome_settings_overrides": {
+ "search_provider": {
+ "name": "engine2",
+ "search_url": "https://2.example.com/search",
+ "params": [
+ {
+ "name": "q",
+ "value": "{searchTerms}"
+ }
+ ]
+ }
+ }
+}
diff --git a/browser/components/preferences/tests/head.js b/browser/components/preferences/tests/head.js
new file mode 100644
index 0000000000..3eb126e1ae
--- /dev/null
+++ b/browser/components/preferences/tests/head.js
@@ -0,0 +1,334 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+const kDefaultWait = 2000;
+
+function is_element_visible(aElement, aMsg) {
+ isnot(aElement, null, "Element should not be null, when checking visibility");
+ ok(!BrowserTestUtils.is_hidden(aElement), aMsg);
+}
+
+function is_element_hidden(aElement, aMsg) {
+ isnot(aElement, null, "Element should not be null, when checking visibility");
+ ok(BrowserTestUtils.is_hidden(aElement), aMsg);
+}
+
+function open_preferences(aCallback) {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:preferences");
+ let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ newTabBrowser.addEventListener(
+ "Initialized",
+ function () {
+ aCallback(gBrowser.contentWindow);
+ },
+ { capture: true, once: true }
+ );
+}
+
+function openAndLoadSubDialog(
+ aURL,
+ aFeatures = null,
+ aParams = null,
+ aClosingCallback = null
+) {
+ let promise = promiseLoadSubDialog(aURL);
+ content.gSubDialog.open(
+ aURL,
+ { features: aFeatures, closingCallback: aClosingCallback },
+ aParams
+ );
+ return promise;
+}
+
+function promiseLoadSubDialog(aURL) {
+ return new Promise((resolve, reject) => {
+ content.gSubDialog._dialogStack.addEventListener(
+ "dialogopen",
+ function dialogopen(aEvent) {
+ if (
+ aEvent.detail.dialog._frame.contentWindow.location == "about:blank"
+ ) {
+ return;
+ }
+ content.gSubDialog._dialogStack.removeEventListener(
+ "dialogopen",
+ dialogopen
+ );
+
+ is(
+ aEvent.detail.dialog._frame.contentWindow.location.toString(),
+ aURL,
+ "Check the proper URL is loaded"
+ );
+
+ // Check visibility
+ is_element_visible(aEvent.detail.dialog._overlay, "Overlay is visible");
+
+ // Check that stylesheets were injected
+ let expectedStyleSheetURLs =
+ aEvent.detail.dialog._injectedStyleSheets.slice(0);
+ for (let styleSheet of aEvent.detail.dialog._frame.contentDocument
+ .styleSheets) {
+ let i = expectedStyleSheetURLs.indexOf(styleSheet.href);
+ if (i >= 0) {
+ info("found " + styleSheet.href);
+ expectedStyleSheetURLs.splice(i, 1);
+ }
+ }
+ is(
+ expectedStyleSheetURLs.length,
+ 0,
+ "All expectedStyleSheetURLs should have been found"
+ );
+
+ // Wait for the next event tick to make sure the remaining part of the
+ // testcase runs after the dialog gets ready for input.
+ executeSoon(() => resolve(aEvent.detail.dialog._frame.contentWindow));
+ }
+ );
+ });
+}
+
+async function openPreferencesViaOpenPreferencesAPI(aPane, aOptions) {
+ let finalPaneEvent = Services.prefs.getBoolPref("identity.fxaccounts.enabled")
+ ? "sync-pane-loaded"
+ : "privacy-pane-loaded";
+ let finalPrefPaneLoaded = TestUtils.topicObserved(finalPaneEvent, () => true);
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
+ openPreferences(aPane, aOptions);
+ let newTabBrowser = gBrowser.selectedBrowser;
+
+ if (!newTabBrowser.contentWindow) {
+ await BrowserTestUtils.waitForEvent(newTabBrowser, "Initialized", true);
+ await BrowserTestUtils.waitForEvent(newTabBrowser.contentWindow, "load");
+ await finalPrefPaneLoaded;
+ }
+
+ let win = gBrowser.contentWindow;
+ let selectedPane = win.history.state;
+ if (!aOptions || !aOptions.leaveOpen) {
+ gBrowser.removeCurrentTab();
+ }
+ return { selectedPane };
+}
+
+async function runSearchInput(input) {
+ let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+ searchInput.focus();
+ let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.contentWindow,
+ "PreferencesSearchCompleted",
+ evt => evt.detail == input
+ );
+ EventUtils.sendString(input);
+ await searchCompletedPromise;
+}
+
+async function evaluateSearchResults(
+ keyword,
+ searchResults,
+ includeExperiments = false
+) {
+ searchResults = Array.isArray(searchResults)
+ ? searchResults
+ : [searchResults];
+ searchResults.push("header-searchResults");
+
+ await runSearchInput(keyword);
+
+ let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
+ for (let i = 0; i < mainPrefTag.childElementCount; i++) {
+ let child = mainPrefTag.children[i];
+ if (!includeExperiments && child.id?.startsWith("pane-experimental")) {
+ continue;
+ }
+ if (searchResults.includes(child.id)) {
+ is_element_visible(child, `${child.id} should be in search results`);
+ } else if (child.id) {
+ is_element_hidden(child, `${child.id} should not be in search results`);
+ }
+ }
+}
+
+function waitForMutation(target, opts, cb) {
+ return new Promise(resolve => {
+ let observer = new MutationObserver(() => {
+ if (!cb || cb(target)) {
+ observer.disconnect();
+ resolve();
+ }
+ });
+ observer.observe(target, opts);
+ });
+}
+
+// Used to add sample experimental features for testing. To use, create
+// a DefinitionServer, then call addDefinition as needed.
+class DefinitionServer {
+ constructor(definitionOverrides = []) {
+ let { HttpServer } = ChromeUtils.import(
+ "resource://testing-common/httpd.js"
+ );
+
+ this.server = new HttpServer();
+ this.server.registerPathHandler("/definitions.json", this);
+ this.definitions = {};
+
+ for (const override of definitionOverrides) {
+ this.addDefinition(override);
+ }
+
+ this.server.start();
+ registerCleanupFunction(
+ () => new Promise(resolve => this.server.stop(resolve))
+ );
+ }
+
+ // for nsIHttpRequestHandler
+ handle(request, response) {
+ response.write(JSON.stringify(this.definitions));
+ }
+
+ get definitionsUrl() {
+ const { primaryScheme, primaryHost, primaryPort } = this.server.identity;
+ return `${primaryScheme}://${primaryHost}:${primaryPort}/definitions.json`;
+ }
+
+ addDefinition(overrides = {}) {
+ const definition = {
+ id: "test-feature",
+ // These l10n IDs are just random so we have some text to display
+ title: "experimental-features-media-jxl",
+ description: "pane-experimental-description2",
+ restartRequired: false,
+ type: "boolean",
+ preference: "test.feature",
+ defaultValue: false,
+ isPublic: false,
+ ...overrides,
+ };
+ // convert targeted values, used by fromId
+ definition.isPublic = { default: definition.isPublic };
+ definition.defaultValue = { default: definition.defaultValue };
+ this.definitions[definition.id] = definition;
+ return definition;
+ }
+}
+
+/**
+ * Creates observer that waits for and then compares all perm-changes with the observances in order.
+ * @param {Array} observances permission changes to observe (order is important)
+ * @returns {Promise} Promise object that resolves once all permission changes have been observed
+ */
+function createObserveAllPromise(observances) {
+ // Create new promise that resolves once all items
+ // in observances array have been observed.
+ return new Promise(resolve => {
+ let permObserver = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic != "perm-changed") {
+ return;
+ }
+
+ if (!observances.length) {
+ // See bug 1063410
+ return;
+ }
+
+ let permission = aSubject.QueryInterface(Ci.nsIPermission);
+ let expected = observances.shift();
+
+ info(
+ `observed perm-changed for ${permission.principal.origin} (remaining ${observances.length})`
+ );
+
+ is(aData, expected.data, "type of message should be the same");
+ for (let prop of ["type", "capability", "expireType"]) {
+ if (expected[prop]) {
+ is(
+ permission[prop],
+ expected[prop],
+ `property: "${prop}" should be equal (${permission.principal.origin})`
+ );
+ }
+ }
+
+ if (expected.origin) {
+ is(
+ permission.principal.origin,
+ expected.origin,
+ `property: "origin" should be equal (${permission.principal.origin})`
+ );
+ }
+
+ if (!observances.length) {
+ Services.obs.removeObserver(permObserver, "perm-changed");
+ executeSoon(resolve);
+ }
+ },
+ };
+ Services.obs.addObserver(permObserver, "perm-changed");
+ });
+}
+
+/**
+ * Waits for preference to be set and asserts the value.
+ * @param {string} pref - Preference key.
+ * @param {*} expectedValue - Expected value of the preference.
+ * @param {string} message - Assertion message.
+ */
+async function waitForAndAssertPrefState(pref, expectedValue, message) {
+ await TestUtils.waitForPrefChange(pref, value => {
+ if (value != expectedValue) {
+ return false;
+ }
+ is(value, expectedValue, message);
+ return true;
+ });
+}
+
+/**
+ * The Relay promo is not shown for distributions with a custom FxA instance,
+ * since Relay requires an account on our own server. These prefs are set to a
+ * dummy address by the test harness, filling the prefs with a "user value."
+ * This temporarily sets the default value equal to the dummy value, so that
+ * Firefox thinks we've configured the correct FxA server.
+ * @returns {Promise<MockFxAUtilityFunctions>} { mock, unmock }
+ */
+async function mockDefaultFxAInstance() {
+ /**
+ * @typedef {Object} MockFxAUtilityFunctions
+ * @property {function():void} mock - Makes the dummy values default, creating
+ * the illusion of a production FxA instance.
+ * @property {function():void} unmock - Restores the true defaults, creating
+ * the illusion of a custom FxA instance.
+ */
+
+ const defaultPrefs = Services.prefs.getDefaultBranch("");
+ const userPrefs = Services.prefs.getBranch("");
+ const realAuth = defaultPrefs.getCharPref("identity.fxaccounts.auth.uri");
+ const realRoot = defaultPrefs.getCharPref("identity.fxaccounts.remote.root");
+ const mockAuth = userPrefs.getCharPref("identity.fxaccounts.auth.uri");
+ const mockRoot = userPrefs.getCharPref("identity.fxaccounts.remote.root");
+ const mock = () => {
+ defaultPrefs.setCharPref("identity.fxaccounts.auth.uri", mockAuth);
+ defaultPrefs.setCharPref("identity.fxaccounts.remote.root", mockRoot);
+ userPrefs.clearUserPref("identity.fxaccounts.auth.uri");
+ userPrefs.clearUserPref("identity.fxaccounts.remote.root");
+ };
+ const unmock = () => {
+ defaultPrefs.setCharPref("identity.fxaccounts.auth.uri", realAuth);
+ defaultPrefs.setCharPref("identity.fxaccounts.remote.root", realRoot);
+ userPrefs.setCharPref("identity.fxaccounts.auth.uri", mockAuth);
+ userPrefs.setCharPref("identity.fxaccounts.remote.root", mockRoot);
+ };
+
+ mock();
+ registerCleanupFunction(unmock);
+
+ return { mock, unmock };
+}
diff --git a/browser/components/preferences/tests/privacypane_tests_perwindow.js b/browser/components/preferences/tests/privacypane_tests_perwindow.js
new file mode 100644
index 0000000000..8c376d1705
--- /dev/null
+++ b/browser/components/preferences/tests/privacypane_tests_perwindow.js
@@ -0,0 +1,388 @@
+// This file gets imported into the same scope as head.js.
+/* import-globals-from head.js */
+
+async function runTestOnPrivacyPrefPane(testFunc) {
+ info("runTestOnPrivacyPrefPane entered");
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:preferences",
+ true,
+ true
+ );
+ let browser = tab.linkedBrowser;
+ info("loaded about:preferences");
+ await browser.contentWindow.gotoPref("panePrivacy");
+ info("viewing privacy pane, executing testFunc");
+ await testFunc(browser.contentWindow);
+ BrowserTestUtils.removeTab(tab);
+}
+
+function controlChanged(element) {
+ element.doCommand();
+}
+
+// We can only test the panes that don't trigger a preference update
+function test_pane_visibility(win) {
+ let modes = {
+ remember: "historyRememberPane",
+ custom: "historyCustomPane",
+ };
+
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let historypane = win.document.getElementById("historyPane");
+ ok(historypane, "history mode pane should exist");
+
+ for (let mode in modes) {
+ historymode.value = mode;
+ controlChanged(historymode);
+ is(
+ historypane.selectedPanel,
+ win.document.getElementById(modes[mode]),
+ "The correct pane should be selected for the " + mode + " mode"
+ );
+ is_element_visible(
+ historypane.selectedPanel,
+ "Correct pane should be visible for the " + mode + " mode"
+ );
+ }
+}
+
+function test_dependent_elements(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let pbautostart = win.document.getElementById("privateBrowsingAutoStart");
+ ok(pbautostart, "the private browsing auto-start checkbox should exist");
+ let controls = [
+ win.document.getElementById("rememberHistory"),
+ win.document.getElementById("rememberForms"),
+ win.document.getElementById("deleteOnClose"),
+ win.document.getElementById("alwaysClear"),
+ ];
+ controls.forEach(function (control) {
+ ok(control, "the dependent controls should exist");
+ });
+ let independents = [
+ win.document.getElementById("contentBlockingBlockCookiesCheckbox"),
+ ];
+ independents.forEach(function (control) {
+ ok(control, "the independent controls should exist");
+ });
+ let cookieexceptions = win.document.getElementById("cookieExceptions");
+ ok(cookieexceptions, "the cookie exceptions button should exist");
+ let deleteOnCloseCheckbox = win.document.getElementById("deleteOnClose");
+ ok(deleteOnCloseCheckbox, "the delete on close checkbox should exist");
+ let alwaysclear = win.document.getElementById("alwaysClear");
+ ok(alwaysclear, "the clear data on close checkbox should exist");
+ let rememberhistory = win.document.getElementById("rememberHistory");
+ ok(rememberhistory, "the remember history checkbox should exist");
+ let rememberforms = win.document.getElementById("rememberForms");
+ ok(rememberforms, "the remember forms checkbox should exist");
+ let alwaysclearsettings = win.document.getElementById("clearDataSettings");
+ ok(alwaysclearsettings, "the clear data settings button should exist");
+
+ function expect_disabled(disabled) {
+ controls.forEach(function (control) {
+ is(
+ control.disabled,
+ disabled,
+ control.getAttribute("id") +
+ " should " +
+ (disabled ? "" : "not ") +
+ "be disabled"
+ );
+ });
+ if (disabled) {
+ ok(
+ !alwaysclear.checked,
+ "the clear data on close checkbox value should be as expected"
+ );
+ ok(
+ !rememberhistory.checked,
+ "the remember history checkbox value should be as expected"
+ );
+ ok(
+ !rememberforms.checked,
+ "the remember forms checkbox value should be as expected"
+ );
+ }
+ }
+ function check_independents(expected) {
+ independents.forEach(function (control) {
+ is(
+ control.disabled,
+ expected,
+ control.getAttribute("id") +
+ " should " +
+ (expected ? "" : "not ") +
+ "be disabled"
+ );
+ });
+
+ ok(
+ !cookieexceptions.disabled,
+ "the cookie exceptions button should never be disabled"
+ );
+ ok(
+ alwaysclearsettings.disabled,
+ "the clear data settings button should always be disabled"
+ );
+ }
+
+ // controls should only change in custom mode
+ historymode.value = "remember";
+ controlChanged(historymode);
+ expect_disabled(false);
+ check_independents(false);
+
+ // setting the mode to custom shouldn't change anything
+ historymode.value = "custom";
+ controlChanged(historymode);
+ expect_disabled(false);
+ check_independents(false);
+}
+
+function test_dependent_cookie_elements(win) {
+ let deleteOnCloseCheckbox = win.document.getElementById("deleteOnClose");
+ let deleteOnCloseNote = win.document.getElementById("deleteOnCloseNote");
+ let blockCookiesMenu = win.document.getElementById("blockCookiesMenu");
+
+ let controls = [blockCookiesMenu, deleteOnCloseCheckbox];
+ controls.forEach(function (control) {
+ ok(control, "the dependent cookie controls should exist");
+ });
+ let blockCookiesCheckbox = win.document.getElementById(
+ "contentBlockingBlockCookiesCheckbox"
+ );
+ ok(blockCookiesCheckbox, "the block cookies checkbox should exist");
+
+ function expect_disabled(disabled, c = controls) {
+ c.forEach(function (control) {
+ is(
+ control.disabled,
+ disabled,
+ control.getAttribute("id") +
+ " should " +
+ (disabled ? "" : "not ") +
+ "be disabled"
+ );
+ });
+ }
+
+ blockCookiesCheckbox.checked = true;
+ controlChanged(blockCookiesCheckbox);
+ expect_disabled(false);
+
+ blockCookiesCheckbox.checked = false;
+ controlChanged(blockCookiesCheckbox);
+ expect_disabled(true, [blockCookiesMenu]);
+ expect_disabled(false, [deleteOnCloseCheckbox]);
+ is_element_hidden(
+ deleteOnCloseNote,
+ "The notice for delete on close in permanent private browsing mode should be hidden."
+ );
+
+ blockCookiesMenu.value = "always";
+ controlChanged(blockCookiesMenu);
+ expect_disabled(true, [deleteOnCloseCheckbox]);
+ expect_disabled(false, [blockCookiesMenu]);
+ is_element_hidden(
+ deleteOnCloseNote,
+ "The notice for delete on close in permanent private browsing mode should be hidden."
+ );
+
+ if (win.contentBlockingCookiesAndSiteDataRejectTrackersEnabled) {
+ blockCookiesMenu.value = "trackers";
+ } else {
+ blockCookiesMenu.value = "unvisited";
+ }
+ controlChanged(blockCookiesMenu);
+ expect_disabled(false);
+
+ let historymode = win.document.getElementById("historyMode");
+
+ // The History mode setting for "never remember history" should still
+ // disable the "keep cookies until..." menu.
+ historymode.value = "dontremember";
+ controlChanged(historymode);
+ expect_disabled(true, [deleteOnCloseCheckbox]);
+ is_element_visible(
+ deleteOnCloseNote,
+ "The notice for delete on close in permanent private browsing mode should be visible."
+ );
+ expect_disabled(false, [blockCookiesMenu]);
+
+ historymode.value = "remember";
+ controlChanged(historymode);
+ expect_disabled(false);
+ is_element_hidden(
+ deleteOnCloseNote,
+ "The notice for delete on close in permanent private browsing mode should be hidden."
+ );
+}
+
+function test_dependent_clearonclose_elements(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let pbautostart = win.document.getElementById("privateBrowsingAutoStart");
+ ok(pbautostart, "the private browsing auto-start checkbox should exist");
+ let alwaysclear = win.document.getElementById("alwaysClear");
+ ok(alwaysclear, "the clear data on close checkbox should exist");
+ let alwaysclearsettings = win.document.getElementById("clearDataSettings");
+ ok(alwaysclearsettings, "the clear data settings button should exist");
+
+ function expect_disabled(disabled) {
+ is(
+ alwaysclearsettings.disabled,
+ disabled,
+ "the clear data settings should " +
+ (disabled ? "" : "not ") +
+ "be disabled"
+ );
+ }
+
+ historymode.value = "custom";
+ controlChanged(historymode);
+ pbautostart.checked = false;
+ controlChanged(pbautostart);
+ alwaysclear.checked = false;
+ controlChanged(alwaysclear);
+ expect_disabled(true);
+
+ alwaysclear.checked = true;
+ controlChanged(alwaysclear);
+ expect_disabled(false);
+
+ alwaysclear.checked = false;
+ controlChanged(alwaysclear);
+ expect_disabled(true);
+}
+
+async function test_dependent_prefs(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let controls = [
+ win.document.getElementById("rememberHistory"),
+ win.document.getElementById("rememberForms"),
+ ];
+ controls.forEach(function (control) {
+ ok(control, "the micro-management controls should exist");
+ });
+
+ function expect_checked(checked) {
+ controls.forEach(function (control) {
+ is(
+ control.checked,
+ checked,
+ control.getAttribute("id") +
+ " should " +
+ (checked ? "" : "not ") +
+ "be checked"
+ );
+ });
+ }
+
+ // controls should be checked in remember mode
+ historymode.value = "remember";
+ controlChanged(historymode);
+ // Initial updates from prefs are not sync, so wait:
+ await TestUtils.waitForCondition(
+ () => controls[0].getAttribute("checked") == "true"
+ );
+ expect_checked(true);
+
+ // even if they're unchecked in custom mode
+ historymode.value = "custom";
+ controlChanged(historymode);
+ controls.forEach(function (control) {
+ control.checked = false;
+ controlChanged(control);
+ });
+ expect_checked(false);
+ historymode.value = "remember";
+ controlChanged(historymode);
+ expect_checked(true);
+}
+
+function test_historymode_retention(mode, expect) {
+ return function test_historymode_retention_fn(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+
+ if (
+ (historymode.value == "remember" && mode == "dontremember") ||
+ (historymode.value == "dontremember" && mode == "remember") ||
+ (historymode.value == "custom" && mode == "dontremember")
+ ) {
+ return;
+ }
+
+ if (expect !== undefined) {
+ is(
+ historymode.value,
+ expect,
+ "history mode is expected to remain " + expect
+ );
+ }
+
+ historymode.value = mode;
+ controlChanged(historymode);
+ };
+}
+
+function test_custom_retention(controlToChange, expect, valueIncrement) {
+ return function test_custom_retention_fn(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+
+ if (expect !== undefined) {
+ is(
+ historymode.value,
+ expect,
+ "history mode is expected to remain " + expect
+ );
+ }
+
+ historymode.value = "custom";
+ controlChanged(historymode);
+
+ controlToChange = win.document.getElementById(controlToChange);
+ ok(controlToChange, "the control to change should exist");
+ switch (controlToChange.localName) {
+ case "checkbox":
+ controlToChange.checked = !controlToChange.checked;
+ break;
+ case "menulist":
+ controlToChange.value = valueIncrement;
+ break;
+ }
+ controlChanged(controlToChange);
+ };
+}
+
+const gPrefCache = new Map();
+
+function cache_preferences(win) {
+ let prefs = win.Preferences.getAll();
+ for (let pref of prefs) {
+ gPrefCache.set(pref.id, pref.value);
+ }
+}
+
+function reset_preferences(win) {
+ let prefs = win.Preferences.getAll();
+ // Avoid assigning undefined, which means clearing a "user"/test pref value
+ for (let pref of prefs) {
+ if (gPrefCache.has(pref.id)) {
+ pref.value = gPrefCache.get(pref.id);
+ }
+ }
+}
+
+function run_test_subset(subset) {
+ info("subset: " + Array.from(subset, x => x.name).join(",") + "\n");
+ let tests = [cache_preferences, ...subset, reset_preferences];
+ for (let test of tests) {
+ add_task(runTestOnPrivacyPrefPane.bind(undefined, test));
+ }
+}
diff --git a/browser/components/preferences/tests/siteData/browser.ini b/browser/components/preferences/tests/siteData/browser.ini
new file mode 100644
index 0000000000..deb8e82f7d
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/browser.ini
@@ -0,0 +1,22 @@
+[DEFAULT]
+support-files =
+ head.js
+ site_data_test.html
+ service_worker_test.html
+ service_worker_test.js
+ offline/offline.html
+ offline/manifest.appcache
+
+[browser_clearSiteData.js]
+[browser_siteData.js]
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_siteData2.js]
+skip-if =
+ win10_2004 && (!debug && !asan) # Bug 1669937
+ win11_2009 && (!debug && !asan) # Bug 1797751
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+ apple_catalina && debug # Bug 1775910
+[browser_siteData3.js]
+[browser_siteData_multi_select.js]
+skip-if = tsan # Bug 1683730
diff --git a/browser/components/preferences/tests/siteData/browser_clearSiteData.js b/browser/components/preferences/tests/siteData/browser_clearSiteData.js
new file mode 100644
index 0000000000..217656707e
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/browser_clearSiteData.js
@@ -0,0 +1,219 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+async function testClearData(clearSiteData, clearCache) {
+ PermissionTestUtils.add(
+ TEST_QUOTA_USAGE_ORIGIN,
+ "persistent-storage",
+ Services.perms.ALLOW_ACTION
+ );
+
+ // Open a test site which saves into appcache.
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_OFFLINE_URL);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // Fill indexedDB with test data.
+ // Don't wait for the page to load, to register the content event handler as quickly as possible.
+ // If this test goes intermittent, we might have to tell the page to wait longer before
+ // firing the event.
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_QUOTA_USAGE_URL, false);
+ await BrowserTestUtils.waitForContentEvent(
+ gBrowser.selectedBrowser,
+ "test-indexedDB-done",
+ false,
+ null,
+ true
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // Register some service workers.
+ await loadServiceWorkerTestPage(TEST_SERVICE_WORKER_URL);
+ await promiseServiceWorkerRegisteredFor(TEST_SERVICE_WORKER_URL);
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ // Test the initial states.
+ let cacheUsage = await SiteDataManager.getCacheSize();
+ let quotaUsage = await SiteDataTestUtils.getQuotaUsage(
+ TEST_QUOTA_USAGE_ORIGIN
+ );
+ let totalUsage = await SiteDataManager.getTotalUsage();
+ Assert.greater(cacheUsage, 0, "The cache usage should not be 0");
+ Assert.greater(quotaUsage, 0, "The quota usage should not be 0");
+ Assert.greater(totalUsage, 0, "The total usage should not be 0");
+
+ let initialSizeLabelValue = await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let sizeLabel = content.document.getElementById("totalSiteDataSize");
+ return sizeLabel.textContent;
+ }
+ );
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let clearSiteDataButton = doc.getElementById("clearSiteDataButton");
+
+ let dialogOpened = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/clearSiteData.xhtml"
+ );
+ clearSiteDataButton.doCommand();
+ let dialogWin = await dialogOpened;
+
+ // Convert the usage numbers in the same way the UI does it to assert
+ // that they're displayed in the dialog.
+ let [convertedTotalUsage] = DownloadUtils.convertByteUnits(totalUsage);
+ // For cache we just assert that the right unit (KB, probably) is displayed,
+ // since we've had cache intermittently changing under our feet.
+ let [, convertedCacheUnit] = DownloadUtils.convertByteUnits(cacheUsage);
+
+ let clearSiteDataCheckbox =
+ dialogWin.document.getElementById("clearSiteData");
+ let clearCacheCheckbox = dialogWin.document.getElementById("clearCache");
+ // The usage details are filled asynchronously, so we assert that they're present by
+ // waiting for them to be filled in.
+ await Promise.all([
+ TestUtils.waitForCondition(
+ () =>
+ clearSiteDataCheckbox.label &&
+ clearSiteDataCheckbox.label.includes(convertedTotalUsage),
+ "Should show the quota usage"
+ ),
+ TestUtils.waitForCondition(
+ () =>
+ clearCacheCheckbox.label &&
+ clearCacheCheckbox.label.includes(convertedCacheUnit),
+ "Should show the cache usage"
+ ),
+ ]);
+
+ // Check the boxes according to our test input.
+ clearSiteDataCheckbox.checked = clearSiteData;
+ clearCacheCheckbox.checked = clearCache;
+
+ // Some additional promises/assertions to wait for
+ // when deleting site data.
+ let acceptPromise;
+ let updatePromise;
+ let cookiesClearedPromise;
+ if (clearSiteData) {
+ acceptPromise = BrowserTestUtils.promiseAlertDialogOpen("accept");
+ updatePromise = promiseSiteDataManagerSitesUpdated();
+ cookiesClearedPromise = promiseCookiesCleared();
+ }
+
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialogWin, "unload");
+
+ let clearButton = dialogWin.document
+ .querySelector("dialog")
+ .getButton("accept");
+ if (!clearSiteData && !clearCache) {
+ // Simulate user input on one of the checkboxes to trigger the event listener for
+ // disabling the clearButton.
+ clearCacheCheckbox.doCommand();
+ // Check that the clearButton gets disabled by unchecking both options.
+ await TestUtils.waitForCondition(
+ () => clearButton.disabled,
+ "Clear button should be disabled"
+ );
+ let cancelButton = dialogWin.document
+ .querySelector("dialog")
+ .getButton("cancel");
+ // Cancel, since we can't delete anything.
+ cancelButton.click();
+ } else {
+ // Delete stuff!
+ clearButton.click();
+ }
+
+ // For site data we display an extra warning dialog, make sure
+ // to accept it.
+ if (clearSiteData) {
+ await acceptPromise;
+ }
+
+ await dialogClosed;
+
+ if (clearCache) {
+ TestUtils.waitForCondition(async function () {
+ let usage = await SiteDataManager.getCacheSize();
+ return usage == 0;
+ }, "The cache usage should be removed");
+ } else {
+ Assert.greater(
+ await SiteDataManager.getCacheSize(),
+ 0,
+ "The cache usage should not be 0"
+ );
+ }
+
+ if (clearSiteData) {
+ await updatePromise;
+ await cookiesClearedPromise;
+ await promiseServiceWorkersCleared();
+
+ TestUtils.waitForCondition(async function () {
+ let usage = await SiteDataManager.getTotalUsage();
+ return usage == 0;
+ }, "The total usage should be removed");
+ } else {
+ quotaUsage = await SiteDataTestUtils.getQuotaUsage(TEST_QUOTA_USAGE_ORIGIN);
+ totalUsage = await SiteDataManager.getTotalUsage();
+ Assert.greater(quotaUsage, 0, "The quota usage should not be 0");
+ Assert.greater(totalUsage, 0, "The total usage should not be 0");
+ }
+
+ if (clearCache || clearSiteData) {
+ // Check that the size label in about:preferences updates after we cleared data.
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [{ initialSizeLabelValue }],
+ async function (opts) {
+ let sizeLabel = content.document.getElementById("totalSiteDataSize");
+ await ContentTaskUtils.waitForCondition(
+ () => sizeLabel.textContent != opts.initialSizeLabelValue,
+ "Site data size label should have updated."
+ );
+ }
+ );
+ }
+
+ let permission = PermissionTestUtils.getPermissionObject(
+ TEST_QUOTA_USAGE_ORIGIN,
+ "persistent-storage"
+ );
+ is(
+ clearSiteData ? permission : permission.capability,
+ clearSiteData ? null : Services.perms.ALLOW_ACTION,
+ "Should have the correct permission state."
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await SiteDataManager.removeAll();
+}
+
+// Test opening the "Clear All Data" dialog and cancelling.
+add_task(async function () {
+ await testClearData(false, false);
+});
+
+// Test opening the "Clear All Data" dialog and removing all site data.
+add_task(async function () {
+ await testClearData(true, false);
+});
+
+// Test opening the "Clear All Data" dialog and removing all cache.
+add_task(async function () {
+ await testClearData(false, true);
+});
+
+// Test opening the "Clear All Data" dialog and removing everything.
+add_task(async function () {
+ await testClearData(true, true);
+});
diff --git a/browser/components/preferences/tests/siteData/browser_siteData.js b/browser/components/preferences/tests/siteData/browser_siteData.js
new file mode 100644
index 0000000000..d3a73d6f4b
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/browser_siteData.js
@@ -0,0 +1,400 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function getPersistentStoragePermStatus(origin) {
+ let uri = Services.io.newURI(origin);
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ return Services.perms.testExactPermissionFromPrincipal(
+ principal,
+ "persistent-storage"
+ );
+}
+
+// Test listing site using quota usage or site using appcache
+// This is currently disabled because of bug 1414751.
+add_task(async function () {
+ // Open a test site which would save into appcache
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_OFFLINE_URL);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // Open a test site which would save into quota manager
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_QUOTA_USAGE_URL);
+ await BrowserTestUtils.waitForContentEvent(
+ gBrowser.selectedBrowser,
+ "test-indexedDB-done",
+ false,
+ null,
+ true
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ let updatedPromise = promiseSiteDataManagerSitesUpdated();
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await updatedPromise;
+ await openSiteDataSettingsDialog();
+ let dialog = content.gSubDialog._topDialog;
+ let dialogFrame = dialog._frame;
+ let frameDoc = dialogFrame.contentDocument;
+
+ let siteItems = frameDoc.getElementsByTagName("richlistitem");
+ is(siteItems.length, 2, "Should list sites using quota usage or appcache");
+
+ let appcacheSite = frameDoc.querySelector(
+ `richlistitem[host="${TEST_OFFLINE_HOST}"]`
+ );
+ ok(appcacheSite, "Should list site using appcache");
+
+ let qoutaUsageSite = frameDoc.querySelector(
+ `richlistitem[host="${TEST_QUOTA_USAGE_HOST}"]`
+ );
+ ok(qoutaUsageSite, "Should list site using quota usage");
+
+ // Always remember to clean up
+ await new Promise(resolve => {
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ TEST_QUOTA_USAGE_ORIGIN
+ );
+ let request = Services.qms.clearStoragesForPrincipal(
+ principal,
+ null,
+ null,
+ true
+ );
+ request.callback = resolve;
+ });
+
+ await SiteDataManager.removeAll();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}).skip(); // Bug 1414751
+
+// Test buttons are disabled and loading message shown while updating sites
+add_task(async function () {
+ let updatedPromise = promiseSiteDataManagerSitesUpdated();
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await updatedPromise;
+ let cacheSize = await SiteDataManager.getCacheSize();
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let clearBtn = doc.getElementById("clearSiteDataButton");
+ let settingsButton = doc.getElementById("siteDataSettings");
+ let totalSiteDataSizeLabel = doc.getElementById("totalSiteDataSize");
+ is(
+ clearBtn.disabled,
+ false,
+ "Should enable clear button after sites updated"
+ );
+ is(
+ settingsButton.disabled,
+ false,
+ "Should enable settings button after sites updated"
+ );
+ await SiteDataManager.getTotalUsage().then(usage => {
+ let [value, unit] = DownloadUtils.convertByteUnits(usage + cacheSize);
+ Assert.deepEqual(
+ doc.l10n.getAttributes(totalSiteDataSizeLabel),
+ {
+ id: "sitedata-total-size",
+ args: { value, unit },
+ },
+ "Should show the right total site data size"
+ );
+ });
+
+ Services.obs.notifyObservers(null, "sitedatamanager:updating-sites");
+ is(
+ clearBtn.disabled,
+ true,
+ "Should disable clear button while updating sites"
+ );
+ is(
+ settingsButton.disabled,
+ true,
+ "Should disable settings button while updating sites"
+ );
+ Assert.deepEqual(
+ doc.l10n.getAttributes(totalSiteDataSizeLabel),
+ {
+ id: "sitedata-total-size-calculating",
+ args: null,
+ },
+ "Should show the loading message while updating"
+ );
+
+ Services.obs.notifyObservers(null, "sitedatamanager:sites-updated");
+ is(
+ clearBtn.disabled,
+ false,
+ "Should enable clear button after sites updated"
+ );
+ is(
+ settingsButton.disabled,
+ false,
+ "Should enable settings button after sites updated"
+ );
+ cacheSize = await SiteDataManager.getCacheSize();
+ await SiteDataManager.getTotalUsage().then(usage => {
+ let [value, unit] = DownloadUtils.convertByteUnits(usage + cacheSize);
+ Assert.deepEqual(
+ doc.l10n.getAttributes(totalSiteDataSizeLabel),
+ {
+ id: "sitedata-total-size",
+ args: { value, unit },
+ },
+ "Should show the right total site data size"
+ );
+ });
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Test clearing service worker through the settings panel
+add_task(async function () {
+ // Register a test service worker
+ await loadServiceWorkerTestPage(TEST_SERVICE_WORKER_URL);
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ // Test the initial states
+ await promiseServiceWorkerRegisteredFor(TEST_SERVICE_WORKER_URL);
+ // Open the Site Data Settings panel and remove the site
+ await openSiteDataSettingsDialog();
+ let acceptRemovePromise = BrowserTestUtils.promiseAlertDialogOpen("accept");
+ let updatePromise = promiseSiteDataManagerSitesUpdated();
+ SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [{ TEST_OFFLINE_HOST }],
+ args => {
+ let host = args.TEST_OFFLINE_HOST;
+ let frameDoc = content.gSubDialog._topDialog._frame.contentDocument;
+ let sitesList = frameDoc.getElementById("sitesList");
+ let site = sitesList.querySelector(`richlistitem[host="${host}"]`);
+ if (site) {
+ let removeBtn = frameDoc.getElementById("removeSelected");
+ let saveBtn = frameDoc.querySelector("dialog").getButton("accept");
+ site.click();
+ removeBtn.doCommand();
+ saveBtn.doCommand();
+ } else {
+ ok(false, `Should have one site of ${host}`);
+ }
+ }
+ );
+ await acceptRemovePromise;
+ await updatePromise;
+ await promiseServiceWorkersCleared();
+ await SiteDataManager.removeAll();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Test showing and removing sites with cookies.
+add_task(async function () {
+ // Add some test cookies.
+ let uri = Services.io.newURI("https://example.com");
+ let uri2 = Services.io.newURI("https://example.org");
+ Services.cookies.add(
+ uri.host,
+ uri.pathQueryRef,
+ "test1",
+ "1",
+ false,
+ false,
+ false,
+ Date.now() + 1000 * 60 * 60,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ Services.cookies.add(
+ uri.host,
+ uri.pathQueryRef,
+ "test2",
+ "2",
+ false,
+ false,
+ false,
+ Date.now() + 1000 * 60 * 60,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ Services.cookies.add(
+ uri2.host,
+ uri2.pathQueryRef,
+ "test1",
+ "1",
+ false,
+ false,
+ false,
+ Date.now() + 1000 * 60 * 60,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+
+ // Ensure that private browsing cookies are ignored.
+ Services.cookies.add(
+ uri.host,
+ uri.pathQueryRef,
+ "test3",
+ "3",
+ false,
+ false,
+ false,
+ Date.now() + 1000 * 60 * 60,
+ { privateBrowsingId: 1 },
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+
+ // Get the exact creation date from the cookies (to avoid intermittents
+ // from minimal time differences, since we round up to minutes).
+ let cookies1 = Services.cookies.getCookiesFromHost(uri.host, {});
+ let cookies2 = Services.cookies.getCookiesFromHost(uri2.host, {});
+ // We made two valid cookies for example.com.
+ let cookie1 = cookies1[1];
+ let cookie2 = cookies2[0];
+
+ let fullFormatter = new Services.intl.DateTimeFormat(undefined, {
+ dateStyle: "short",
+ timeStyle: "short",
+ });
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+
+ // Open the site data manager and remove one site.
+ await openSiteDataSettingsDialog();
+ let creationDate1 = new Date(cookie1.lastAccessed / 1000);
+ let creationDate1Formatted = fullFormatter.format(creationDate1);
+ let creationDate2 = new Date(cookie2.lastAccessed / 1000);
+ let creationDate2Formatted = fullFormatter.format(creationDate2);
+ let removeDialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ "accept",
+ REMOVE_DIALOG_URL
+ );
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [
+ {
+ creationDate1Formatted,
+ creationDate2Formatted,
+ },
+ ],
+ function (args) {
+ let frameDoc = content.gSubDialog._topDialog._frame.contentDocument;
+
+ let siteItems = frameDoc.getElementsByTagName("richlistitem");
+ is(siteItems.length, 2, "Should list two sites with cookies");
+ let sitesList = frameDoc.getElementById("sitesList");
+ let site1 = sitesList.querySelector(`richlistitem[host="example.com"]`);
+ let site2 = sitesList.querySelector(`richlistitem[host="example.org"]`);
+
+ let columns = site1.querySelectorAll(".item-box > label");
+ let boxes = site1.querySelectorAll(".item-box");
+ is(columns[0].value, "example.com", "Should show the correct host.");
+ is(columns[1].value, "2", "Should show the correct number of cookies.");
+ is(columns[2].value, "", "Should show no site data.");
+ is(
+ /(now|second)/.test(columns[3].value),
+ true,
+ "Should show the relative date."
+ );
+ is(
+ boxes[3].getAttribute("tooltiptext"),
+ args.creationDate1Formatted,
+ "Should show the correct date."
+ );
+
+ columns = site2.querySelectorAll(".item-box > label");
+ boxes = site2.querySelectorAll(".item-box");
+ is(columns[0].value, "example.org", "Should show the correct host.");
+ is(columns[1].value, "1", "Should show the correct number of cookies.");
+ is(columns[2].value, "", "Should show no site data.");
+ is(
+ /(now|second)/.test(columns[3].value),
+ true,
+ "Should show the relative date."
+ );
+ is(
+ boxes[3].getAttribute("tooltiptext"),
+ args.creationDate2Formatted,
+ "Should show the correct date."
+ );
+
+ let removeBtn = frameDoc.getElementById("removeSelected");
+ let saveBtn = frameDoc.querySelector("dialog").getButton("accept");
+ site2.click();
+ removeBtn.doCommand();
+ saveBtn.doCommand();
+ }
+ );
+ await removeDialogOpenPromise;
+
+ await TestUtils.waitForCondition(
+ () => Services.cookies.countCookiesFromHost(uri2.host) == 0,
+ "Cookies from the first host should be cleared"
+ );
+ is(
+ Services.cookies.countCookiesFromHost(uri.host),
+ 2,
+ "Cookies from the second host should not be cleared"
+ );
+
+ // Open the site data manager and remove another site.
+ await openSiteDataSettingsDialog();
+ let acceptRemovePromise = BrowserTestUtils.promiseAlertDialogOpen("accept");
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [{ creationDate1Formatted }],
+ function (args) {
+ let frameDoc = content.gSubDialog._topDialog._frame.contentDocument;
+
+ let siteItems = frameDoc.getElementsByTagName("richlistitem");
+ is(siteItems.length, 1, "Should list one site with cookies");
+ let sitesList = frameDoc.getElementById("sitesList");
+ let site1 = sitesList.querySelector(`richlistitem[host="example.com"]`);
+
+ let columns = site1.querySelectorAll(".item-box > label");
+ let boxes = site1.querySelectorAll(".item-box");
+ is(columns[0].value, "example.com", "Should show the correct host.");
+ is(columns[1].value, "2", "Should show the correct number of cookies.");
+ is(columns[2].value, "", "Should show no site data.");
+ is(
+ /(now|second)/.test(columns[3].value),
+ true,
+ "Should show the relative date."
+ );
+ is(
+ boxes[3].getAttribute("tooltiptext"),
+ args.creationDate1Formatted,
+ "Should show the correct date."
+ );
+
+ let removeBtn = frameDoc.getElementById("removeSelected");
+ let saveBtn = frameDoc.querySelector("dialog").getButton("accept");
+ site1.click();
+ removeBtn.doCommand();
+ saveBtn.doCommand();
+ }
+ );
+ await acceptRemovePromise;
+
+ await TestUtils.waitForCondition(
+ () => Services.cookies.countCookiesFromHost(uri.host) == 0,
+ "Cookies from the second host should be cleared"
+ );
+
+ await openSiteDataSettingsDialog();
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
+ let frameDoc = content.gSubDialog._topDialog._frame.contentDocument;
+
+ let siteItems = frameDoc.getElementsByTagName("richlistitem");
+ is(siteItems.length, 0, "Should list no sites with cookies");
+ });
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/siteData/browser_siteData2.js b/browser/components/preferences/tests/siteData/browser_siteData2.js
new file mode 100644
index 0000000000..863a8dcefe
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/browser_siteData2.js
@@ -0,0 +1,475 @@
+"use strict";
+
+function assertAllSitesNotListed(win) {
+ let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ let removeBtn = frameDoc.getElementById("removeSelected");
+ let removeAllBtn = frameDoc.getElementById("removeAll");
+ let sitesList = frameDoc.getElementById("sitesList");
+ let sites = sitesList.getElementsByTagName("richlistitem");
+ is(sites.length, 0, "Should not list all sites");
+ is(removeBtn.disabled, true, "Should disable the removeSelected button");
+ is(removeAllBtn.disabled, true, "Should disable the removeAllBtn button");
+}
+
+// Test selecting and removing all sites one by one
+add_task(async function test_selectRemove() {
+ let hosts = await addTestData([
+ {
+ usage: 1024,
+ origin: "https://account.xyz.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "https://shopping.xyz.com",
+ },
+ {
+ usage: 1024,
+ origin: "http://cinema.bar.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "http://email.bar.com",
+ },
+ ]);
+
+ let updatePromise = promiseSiteDataManagerSitesUpdated();
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let frameDoc = null;
+ let saveBtn = null;
+ let cancelBtn = null;
+ let settingsDialogClosePromise = null;
+
+ // Test the initial state
+ assertSitesListed(doc, hosts);
+
+ // Test the "Cancel" button
+ settingsDialogClosePromise = promiseSettingsDialogClose();
+ frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ cancelBtn = frameDoc.querySelector("dialog").getButton("cancel");
+ removeAllSitesOneByOne();
+ assertAllSitesNotListed(win);
+ cancelBtn.doCommand();
+ await settingsDialogClosePromise;
+ await openSiteDataSettingsDialog();
+ assertSitesListed(doc, hosts);
+
+ // Test the "Save Changes" button but cancelling save
+ let cancelPromise = BrowserTestUtils.promiseAlertDialogOpen("cancel");
+ settingsDialogClosePromise = promiseSettingsDialogClose();
+ frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ saveBtn = frameDoc.querySelector("dialog").getButton("accept");
+ cancelBtn = frameDoc.querySelector("dialog").getButton("cancel");
+ removeAllSitesOneByOne();
+ assertAllSitesNotListed(win);
+ saveBtn.doCommand();
+ await cancelPromise;
+ cancelBtn.doCommand();
+ await settingsDialogClosePromise;
+ await openSiteDataSettingsDialog();
+ assertSitesListed(doc, hosts);
+
+ // Test the "Save Changes" button and accepting save
+ let acceptPromise = BrowserTestUtils.promiseAlertDialogOpen("accept");
+ settingsDialogClosePromise = promiseSettingsDialogClose();
+ updatePromise = promiseSiteDataManagerSitesUpdated();
+ frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ saveBtn = frameDoc.querySelector("dialog").getButton("accept");
+ removeAllSitesOneByOne();
+ assertAllSitesNotListed(win);
+ saveBtn.doCommand();
+ await acceptPromise;
+ await settingsDialogClosePromise;
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+ assertAllSitesNotListed(win);
+
+ await SiteDataTestUtils.clear();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ function removeAllSitesOneByOne() {
+ frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ let removeBtn = frameDoc.getElementById("removeSelected");
+ let sitesList = frameDoc.getElementById("sitesList");
+ let sites = sitesList.getElementsByTagName("richlistitem");
+ for (let i = sites.length - 1; i >= 0; --i) {
+ sites[i].click();
+ removeBtn.doCommand();
+ }
+ }
+});
+
+// Test selecting and removing partial sites
+add_task(async function test_removePartialSites() {
+ let hosts = await addTestData([
+ {
+ usage: 1024,
+ origin: "https://account.xyz.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "https://shopping.xyz.com",
+ persisted: false,
+ },
+ {
+ usage: 1024,
+ origin: "http://cinema.bar.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "http://email.bar.com",
+ persisted: false,
+ },
+ {
+ usage: 1024,
+ origin: "https://s3-us-west-2.amazonaws.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "https://127.0.0.1",
+ persisted: false,
+ },
+ ]);
+
+ let updatePromise = promiseSiteDataManagerSitesUpdated();
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let frameDoc = null;
+ let saveBtn = null;
+ let cancelBtn = null;
+ let removeDialogOpenPromise = null;
+ let settingsDialogClosePromise = null;
+
+ // Test the initial state
+ assertSitesListed(doc, hosts);
+
+ // Test the "Cancel" button
+ settingsDialogClosePromise = promiseSettingsDialogClose();
+ frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ cancelBtn = frameDoc.querySelector("dialog").getButton("cancel");
+ await removeSelectedSite(hosts.slice(0, 2));
+ assertSitesListed(doc, hosts.slice(2));
+ cancelBtn.doCommand();
+ await settingsDialogClosePromise;
+ await openSiteDataSettingsDialog();
+ assertSitesListed(doc, hosts);
+
+ // Test the "Save Changes" button but canceling save
+ removeDialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ "cancel",
+ REMOVE_DIALOG_URL
+ );
+ settingsDialogClosePromise = promiseSettingsDialogClose();
+ frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ saveBtn = frameDoc.querySelector("dialog").getButton("accept");
+ cancelBtn = frameDoc.querySelector("dialog").getButton("cancel");
+ await removeSelectedSite(hosts.slice(0, 2));
+ assertSitesListed(doc, hosts.slice(2));
+ saveBtn.doCommand();
+ await removeDialogOpenPromise;
+ cancelBtn.doCommand();
+ await settingsDialogClosePromise;
+ await openSiteDataSettingsDialog();
+ assertSitesListed(doc, hosts);
+
+ // Test the "Save Changes" button and accepting save
+ removeDialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ "accept",
+ REMOVE_DIALOG_URL
+ );
+ settingsDialogClosePromise = promiseSettingsDialogClose();
+ frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ saveBtn = frameDoc.querySelector("dialog").getButton("accept");
+ await removeSelectedSite(hosts.slice(0, 2));
+ assertSitesListed(doc, hosts.slice(2));
+ saveBtn.doCommand();
+ await removeDialogOpenPromise;
+ await settingsDialogClosePromise;
+ await openSiteDataSettingsDialog();
+ assertSitesListed(doc, hosts.slice(2));
+
+ await SiteDataTestUtils.clear();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ function removeSelectedSite(removeHosts) {
+ frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ let removeBtn = frameDoc.getElementById("removeSelected");
+ is(
+ removeBtn.disabled,
+ true,
+ "Should start with disabled removeSelected button"
+ );
+ let sitesList = frameDoc.getElementById("sitesList");
+ removeHosts.forEach(host => {
+ let site = sitesList.querySelector(`richlistitem[host="${host}"]`);
+ if (site) {
+ site.click();
+ let currentSelectedIndex = sitesList.selectedIndex;
+ is(
+ removeBtn.disabled,
+ false,
+ "Should enable the removeSelected button"
+ );
+ removeBtn.doCommand();
+ let newSelectedIndex = sitesList.selectedIndex;
+ if (currentSelectedIndex >= sitesList.itemCount) {
+ is(newSelectedIndex, currentSelectedIndex - 1);
+ } else {
+ is(newSelectedIndex, currentSelectedIndex);
+ }
+ } else {
+ ok(false, `Should not select and remove inexistent site of ${host}`);
+ }
+ });
+ }
+});
+
+// Test searching and then removing only visible sites
+add_task(async function () {
+ let hosts = await addTestData([
+ {
+ usage: 1024,
+ origin: "https://account.xyz.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "https://shopping.xyz.com",
+ persisted: false,
+ },
+ {
+ usage: 1024,
+ origin: "http://cinema.bar.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "http://email.bar.com",
+ persisted: false,
+ },
+ ]);
+
+ let updatePromise = promiseSiteDataManagerSitesUpdated();
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+
+ // Search "foo" to only list foo.com sites
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ let searchBox = frameDoc.getElementById("searchBox");
+ searchBox.value = "xyz";
+ searchBox.doCommand();
+ assertSitesListed(
+ doc,
+ hosts.filter(host => host.includes("xyz"))
+ );
+
+ // Test only removing all visible sites listed
+ updatePromise = promiseSiteDataManagerSitesUpdated();
+ let acceptRemovePromise = BrowserTestUtils.promiseAlertDialogOpen(
+ "accept",
+ REMOVE_DIALOG_URL
+ );
+ let settingsDialogClosePromise = promiseSettingsDialogClose();
+ let removeAllBtn = frameDoc.getElementById("removeAll");
+ let saveBtn = frameDoc.querySelector("dialog").getButton("accept");
+ removeAllBtn.doCommand();
+ saveBtn.doCommand();
+ await acceptRemovePromise;
+ await settingsDialogClosePromise;
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+ assertSitesListed(
+ doc,
+ hosts.filter(host => !host.includes("xyz"))
+ );
+
+ await SiteDataTestUtils.clear();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Test dynamically clearing all site data
+add_task(async function () {
+ let hosts = await addTestData([
+ {
+ usage: 1024,
+ origin: "https://account.xyz.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "https://shopping.xyz.com",
+ persisted: false,
+ },
+ ]);
+
+ let updatePromise = promiseSiteDataManagerSitesUpdated();
+
+ // Test the initial state
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ assertSitesListed(doc, hosts);
+
+ await addTestData([
+ {
+ usage: 1024,
+ origin: "http://cinema.bar.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "http://email.bar.com",
+ persisted: false,
+ },
+ ]);
+
+ // Test clearing all site data dynamically
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ updatePromise = promiseSiteDataManagerSitesUpdated();
+ let acceptRemovePromise = BrowserTestUtils.promiseAlertDialogOpen("accept");
+ let settingsDialogClosePromise = promiseSettingsDialogClose();
+ let removeAllBtn = frameDoc.getElementById("removeAll");
+ let saveBtn = frameDoc.querySelector("dialog").getButton("accept");
+ removeAllBtn.doCommand();
+ saveBtn.doCommand();
+ await acceptRemovePromise;
+ await settingsDialogClosePromise;
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+ assertAllSitesNotListed(win);
+
+ await SiteDataTestUtils.clear();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Tests clearing search box content via backspace does not delete site data
+add_task(async function () {
+ let hosts = await addTestData([
+ {
+ usage: 1024,
+ origin: "https://account.xyz.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "https://shopping.xyz.com",
+ persisted: false,
+ },
+ {
+ usage: 1024,
+ origin: "http://cinema.bar.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "http://email.bar.com",
+ persisted: false,
+ },
+ ]);
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await openSiteDataSettingsDialog();
+
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ let searchBox = frameDoc.getElementById("searchBox");
+ searchBox.value = "xyz";
+ searchBox.doCommand();
+ assertSitesListed(
+ doc,
+ hosts.filter(host => host.includes("xyz"))
+ );
+
+ // Make sure the focus is on the search box
+ searchBox.focus();
+ if (AppConstants.platform == "macosx") {
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {}, win);
+ } else {
+ EventUtils.synthesizeKey("VK_DELETE", {}, win);
+ }
+ assertSitesListed(
+ doc,
+ hosts.filter(host => host.includes("xyz"))
+ );
+
+ await SiteDataTestUtils.clear();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Tests remove site data via backspace
+add_task(async function () {
+ let hosts = await addTestData([
+ {
+ usage: 1024,
+ origin: "https://account.xyz.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "https://shopping.xyz.com",
+ persisted: false,
+ },
+ {
+ usage: 1024,
+ origin: "http://cinema.bar.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "http://email.bar.com",
+ persisted: false,
+ },
+ ]);
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await openSiteDataSettingsDialog();
+
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ // Test initial state
+ assertSitesListed(doc, hosts);
+
+ let sitesList = frameDoc.getElementById("sitesList");
+ let site = sitesList.querySelector(`richlistitem[host="xyz.com"]`);
+ if (site) {
+ // Move the focus from the search box to the list and select an item
+ sitesList.focus();
+ site.click();
+ if (AppConstants.platform == "macosx") {
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {}, win);
+ } else {
+ EventUtils.synthesizeKey("VK_DELETE", {}, win);
+ }
+ }
+
+ assertSitesListed(
+ doc,
+ hosts.filter(host => !host.includes("xyz"))
+ );
+
+ await SiteDataTestUtils.clear();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/siteData/browser_siteData3.js b/browser/components/preferences/tests/siteData/browser_siteData3.js
new file mode 100644
index 0000000000..58aa5bf1b9
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/browser_siteData3.js
@@ -0,0 +1,327 @@
+"use strict";
+
+// Test not displaying sites which store 0 byte and don't have persistent storage.
+add_task(async function test_exclusions() {
+ let hosts = await addTestData([
+ {
+ usage: 0,
+ origin: "https://account.xyz.com",
+ persisted: true,
+ },
+ {
+ usage: 0,
+ origin: "https://shopping.xyz.com",
+ persisted: false,
+ },
+ {
+ usage: 1024,
+ origin: "http://cinema.bar.com",
+ persisted: true,
+ },
+ {
+ usage: 1024,
+ origin: "http://email.bar.com",
+ persisted: false,
+ },
+ {
+ usage: 0,
+ origin: "http://cookies.bar.com",
+ cookies: 5,
+ persisted: false,
+ },
+ ]);
+
+ let updatePromise = promiseSiteDataManagerSitesUpdated();
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+ assertSitesListed(
+ doc,
+ hosts.filter(host => host != "shopping.xyz.com")
+ );
+
+ await SiteDataTestUtils.clear();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Test grouping and listing sites across scheme, port and origin attributes by base domain.
+add_task(async function test_grouping() {
+ let quotaUsage = 7000000;
+ let testData = [
+ {
+ usage: quotaUsage,
+ origin: "https://account.xyz.com^userContextId=1",
+ cookies: 2,
+ persisted: true,
+ },
+ {
+ usage: quotaUsage,
+ origin: "https://account.xyz.com",
+ cookies: 1,
+ persisted: false,
+ },
+ {
+ usage: quotaUsage,
+ origin: "https://account.xyz.com:123",
+ cookies: 1,
+ persisted: false,
+ },
+ {
+ usage: quotaUsage,
+ origin: "http://account.xyz.com",
+ cookies: 1,
+ persisted: false,
+ },
+ {
+ usage: quotaUsage,
+ origin: "http://search.xyz.com",
+ cookies: 3,
+ persisted: false,
+ },
+ {
+ usage: quotaUsage,
+ origin: "http://advanced.search.xyz.com",
+ cookies: 3,
+ persisted: true,
+ },
+ {
+ usage: quotaUsage,
+ origin: "http://xyz.com",
+ cookies: 1,
+ persisted: false,
+ },
+ ];
+ await addTestData(testData);
+
+ let updatedPromise = promiseSiteDataManagerSitesUpdated();
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await updatedPromise;
+ await openSiteDataSettingsDialog();
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let dialogFrame = win.gSubDialog._topDialog._frame;
+ let frameDoc = dialogFrame.contentDocument;
+
+ let siteItems = frameDoc.getElementsByTagName("richlistitem");
+ is(
+ siteItems.length,
+ 1,
+ "Should group sites across scheme, port and origin attributes"
+ );
+
+ let columns = siteItems[0].querySelectorAll(".item-box > label");
+
+ let expected = "xyz.com";
+ is(columns[0].value, expected, "Should group and list sites by host");
+
+ let cookieCount = testData.reduce((count, { cookies }) => count + cookies, 0);
+ is(
+ columns[1].value,
+ cookieCount.toString(),
+ "Should group cookies across scheme, port and origin attributes"
+ );
+
+ let [value, unit] = DownloadUtils.convertByteUnits(quotaUsage * 4);
+ let l10nAttributes = frameDoc.l10n.getAttributes(columns[2]);
+ is(
+ l10nAttributes.id,
+ "site-storage-persistent",
+ "Should show the site as persistent if one origin is persistent."
+ );
+ // The shown quota can be slightly larger than the raw data we put in (though it should
+ // never be smaller), but that doesn't really matter to us since we only want to test that
+ // the site data dialog accumulates this into a single column.
+ ok(
+ parseFloat(l10nAttributes.args.value) >= parseFloat(value),
+ "Should show the correct accumulated quota size."
+ );
+ is(
+ l10nAttributes.args.unit,
+ unit,
+ "Should show the correct quota size unit."
+ );
+
+ await SiteDataTestUtils.clear();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Test sorting
+add_task(async function test_sorting() {
+ let testData = [
+ {
+ usage: 1024,
+ origin: "https://account.xyz.com",
+ cookies: 6,
+ persisted: true,
+ },
+ {
+ usage: 1024 * 2,
+ origin: "https://books.foo.com",
+ cookies: 0,
+ persisted: false,
+ },
+ {
+ usage: 1024 * 3,
+ origin: "http://cinema.bar.com",
+ cookies: 3,
+ persisted: true,
+ },
+ {
+ usage: 1024 * 3,
+ origin: "http://vod.bar.com",
+ cookies: 2,
+ persisted: false,
+ },
+ ];
+
+ await addTestData(testData);
+
+ let updatePromise = promiseSiteDataManagerSitesUpdated();
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+
+ let dialog = content.gSubDialog._topDialog;
+ let dialogFrame = dialog._frame;
+ let frameDoc = dialogFrame.contentDocument;
+ let hostCol = frameDoc.getElementById("hostCol");
+ let usageCol = frameDoc.getElementById("usageCol");
+ let cookiesCol = frameDoc.getElementById("cookiesCol");
+ let sitesList = frameDoc.getElementById("sitesList");
+
+ function getHostOrder() {
+ let siteItems = sitesList.getElementsByTagName("richlistitem");
+ return Array.from(siteItems).map(item => item.getAttribute("host"));
+ }
+
+ // Test default sorting by usage, descending.
+ Assert.deepEqual(
+ getHostOrder(),
+ ["bar.com", "foo.com", "xyz.com"],
+ "Has sorted descending by usage"
+ );
+
+ // Test sorting on the usage column
+ usageCol.click();
+ Assert.deepEqual(
+ getHostOrder(),
+ ["xyz.com", "foo.com", "bar.com"],
+ "Has sorted ascending by usage"
+ );
+ usageCol.click();
+ Assert.deepEqual(
+ getHostOrder(),
+ ["bar.com", "foo.com", "xyz.com"],
+ "Has sorted descending by usage"
+ );
+
+ // Test sorting on the host column
+ hostCol.click();
+ Assert.deepEqual(
+ getHostOrder(),
+ ["bar.com", "foo.com", "xyz.com"],
+ "Has sorted ascending by base domain"
+ );
+ hostCol.click();
+ Assert.deepEqual(
+ getHostOrder(),
+ ["xyz.com", "foo.com", "bar.com"],
+ "Has sorted descending by base domain"
+ );
+
+ // Test sorting on the cookies column
+ cookiesCol.click();
+ Assert.deepEqual(
+ getHostOrder(),
+ ["foo.com", "bar.com", "xyz.com"],
+ "Has sorted ascending by cookies"
+ );
+ cookiesCol.click();
+ Assert.deepEqual(
+ getHostOrder(),
+ ["xyz.com", "bar.com", "foo.com"],
+ "Has sorted descending by cookies"
+ );
+
+ await SiteDataTestUtils.clear();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Test single entry removal
+add_task(async function test_single_entry_removal() {
+ let testData = await addTestData([
+ {
+ usage: 1024,
+ origin: "https://xyz.com",
+ cookies: 6,
+ persisted: true,
+ },
+ {
+ usage: 1024 * 3,
+ origin: "http://bar.com",
+ cookies: 2,
+ persisted: false,
+ },
+ ]);
+
+ let updatePromise = promiseSiteDataManagerSitesUpdated();
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+
+ let dialog = content.gSubDialog._topDialog;
+ let dialogFrame = dialog._frame;
+ let frameDoc = dialogFrame.contentDocument;
+
+ let sitesList = frameDoc.getElementById("sitesList");
+ let host = testData[0];
+ let site = sitesList.querySelector(`richlistitem[host="${host}"]`);
+ sitesList.addItemToSelection(site);
+ frameDoc.getElementById("removeSelected").doCommand();
+ let saveChangesButton = frameDoc.querySelector("dialog").getButton("accept");
+ let dialogOpened = BrowserTestUtils.promiseAlertDialogOpen(
+ null,
+ REMOVE_DIALOG_URL
+ );
+ setTimeout(() => saveChangesButton.doCommand(), 0);
+ let dialogWin = await dialogOpened;
+ let rootElement = dialogWin.document.getElementById(
+ "SiteDataRemoveSelectedDialog"
+ );
+ is(rootElement.classList.length, 1, "There should only be one class set");
+ is(
+ rootElement.classList[0],
+ "single-entry",
+ "The only class set should be single-entry (to hide the list)"
+ );
+ let description = dialogWin.document.getElementById("removing-description");
+ is(
+ description.getAttribute("data-l10n-id"),
+ "site-data-removing-single-desc",
+ "The description for single site should be selected"
+ );
+
+ let removalList = dialogWin.document.getElementById("removalList");
+ is(
+ BrowserTestUtils.is_visible(removalList),
+ false,
+ "The removal list should be invisible"
+ );
+ let removeButton = dialogWin.document
+ .querySelector("dialog")
+ .getButton("accept");
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialogWin, "unload");
+ updatePromise = promiseSiteDataManagerSitesUpdated();
+ removeButton.doCommand();
+ await dialogClosed;
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+
+ dialog = content.gSubDialog._topDialog;
+ dialogFrame = dialog._frame;
+ frameDoc = dialogFrame.contentDocument;
+ assertSitesListed(frameDoc, testData.slice(1));
+ await SiteDataTestUtils.clear();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/siteData/browser_siteData_multi_select.js b/browser/components/preferences/tests/siteData/browser_siteData_multi_select.js
new file mode 100644
index 0000000000..5ce9d7e1e1
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/browser_siteData_multi_select.js
@@ -0,0 +1,119 @@
+"use strict";
+
+// Test selecting and removing partial sites
+add_task(async function () {
+ await SiteDataTestUtils.clear();
+
+ let hosts = await addTestData([
+ {
+ usage: 1024,
+ origin: "https://127.0.0.1",
+ persisted: false,
+ },
+ {
+ usage: 1024 * 4,
+ origin: "http://cinema.bar.com",
+ persisted: true,
+ },
+ {
+ usage: 1024 * 3,
+ origin: "http://email.bar.com",
+ persisted: false,
+ },
+ {
+ usage: 1024 * 2,
+ origin: "https://s3-us-west-2.amazonaws.com",
+ persisted: true,
+ },
+ {
+ usage: 1024 * 6,
+ origin: "https://account.xyz.com",
+ persisted: true,
+ },
+ {
+ usage: 1024 * 5,
+ origin: "https://shopping.xyz.com",
+ persisted: false,
+ },
+ {
+ usage: 1024 * 5,
+ origin: "https://example.com",
+ persisted: false,
+ },
+ {
+ usage: 1024 * 5,
+ origin: "https://example.net",
+ persisted: false,
+ },
+ ]);
+
+ // Align the order of test hosts with the order of the site data table.
+ hosts.sort();
+
+ let updatePromise = promiseSiteDataManagerSitesUpdated();
+ await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ // Test the initial state
+ assertSitesListed(doc, hosts);
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
+ let removeBtn = frameDoc.getElementById("removeSelected");
+ is(
+ removeBtn.disabled,
+ true,
+ "Should start with disabled removeSelected button"
+ );
+
+ let hostCol = frameDoc.getElementById("hostCol");
+ hostCol.click();
+
+ let removeDialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ "accept",
+ REMOVE_DIALOG_URL
+ );
+ let settingsDialogClosePromise = promiseSettingsDialogClose();
+
+ // Select some sites to remove.
+ let sitesList = frameDoc.getElementById("sitesList");
+ hosts.slice(0, 2).forEach(host => {
+ let site = sitesList.querySelector(`richlistitem[host="${host}"]`);
+ sitesList.addItemToSelection(site);
+ });
+
+ is(removeBtn.disabled, false, "Should enable the removeSelected button");
+ removeBtn.doCommand();
+ is(sitesList.selectedIndex, 0, "Should select next item");
+ assertSitesListed(doc, hosts.slice(2));
+
+ // Select some other sites to remove with Delete.
+ hosts.slice(2, 4).forEach(host => {
+ let site = sitesList.querySelector(`richlistitem[host="${host}"]`);
+ sitesList.addItemToSelection(site);
+ });
+
+ is(removeBtn.disabled, false, "Should enable the removeSelected button");
+ // Move the focus from the search box to the list
+ sitesList.focus();
+ EventUtils.synthesizeKey("VK_DELETE");
+ is(sitesList.selectedIndex, 0, "Should select next item");
+ assertSitesListed(doc, hosts.slice(4));
+
+ updatePromise = promiseSiteDataManagerSitesUpdated();
+ let saveBtn = frameDoc.querySelector("dialog").getButton("accept");
+ saveBtn.doCommand();
+
+ await removeDialogOpenPromise;
+ await settingsDialogClosePromise;
+
+ await updatePromise;
+ await openSiteDataSettingsDialog();
+
+ assertSitesListed(doc, hosts.slice(4));
+
+ await SiteDataTestUtils.clear();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/tests/siteData/head.js b/browser/components/preferences/tests/siteData/head.js
new file mode 100644
index 0000000000..01f56d879d
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/head.js
@@ -0,0 +1,280 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_QUOTA_USAGE_HOST = "example.com";
+const TEST_QUOTA_USAGE_ORIGIN = "https://" + TEST_QUOTA_USAGE_HOST;
+const TEST_QUOTA_USAGE_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ TEST_QUOTA_USAGE_ORIGIN
+ ) + "/site_data_test.html";
+const TEST_OFFLINE_HOST = "example.org";
+const TEST_OFFLINE_ORIGIN = "https://" + TEST_OFFLINE_HOST;
+const TEST_OFFLINE_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ TEST_OFFLINE_ORIGIN
+ ) + "/offline/offline.html";
+const TEST_SERVICE_WORKER_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ TEST_OFFLINE_ORIGIN
+ ) + "/service_worker_test.html";
+
+const REMOVE_DIALOG_URL =
+ "chrome://browser/content/preferences/dialogs/siteDataRemoveSelected.xhtml";
+
+ChromeUtils.defineESModuleGetters(this, {
+ SiteDataTestUtils: "resource://testing-common/SiteDataTestUtils.sys.mjs",
+});
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "serviceWorkerManager",
+ "@mozilla.org/serviceworkers/manager;1",
+ "nsIServiceWorkerManager"
+);
+
+function promiseSiteDataManagerSitesUpdated() {
+ return TestUtils.topicObserved("sitedatamanager:sites-updated", () => true);
+}
+
+function is_element_visible(aElement, aMsg) {
+ isnot(aElement, null, "Element should not be null, when checking visibility");
+ ok(!BrowserTestUtils.is_hidden(aElement), aMsg);
+}
+
+function is_element_hidden(aElement, aMsg) {
+ isnot(aElement, null, "Element should not be null, when checking visibility");
+ ok(BrowserTestUtils.is_hidden(aElement), aMsg);
+}
+
+function promiseLoadSubDialog(aURL) {
+ return new Promise((resolve, reject) => {
+ content.gSubDialog._dialogStack.addEventListener(
+ "dialogopen",
+ function dialogopen(aEvent) {
+ if (
+ aEvent.detail.dialog._frame.contentWindow.location == "about:blank"
+ ) {
+ return;
+ }
+ content.gSubDialog._dialogStack.removeEventListener(
+ "dialogopen",
+ dialogopen
+ );
+
+ is(
+ aEvent.detail.dialog._frame.contentWindow.location.toString(),
+ aURL,
+ "Check the proper URL is loaded"
+ );
+
+ // Check visibility
+ is_element_visible(aEvent.detail.dialog._overlay, "Overlay is visible");
+
+ // Check that stylesheets were injected
+ let expectedStyleSheetURLs =
+ aEvent.detail.dialog._injectedStyleSheets.slice(0);
+ for (let styleSheet of aEvent.detail.dialog._frame.contentDocument
+ .styleSheets) {
+ let i = expectedStyleSheetURLs.indexOf(styleSheet.href);
+ if (i >= 0) {
+ info("found " + styleSheet.href);
+ expectedStyleSheetURLs.splice(i, 1);
+ }
+ }
+ is(
+ expectedStyleSheetURLs.length,
+ 0,
+ "All expectedStyleSheetURLs should have been found"
+ );
+
+ // Wait for the next event tick to make sure the remaining part of the
+ // testcase runs after the dialog gets ready for input.
+ executeSoon(() => resolve(aEvent.detail.dialog._frame.contentWindow));
+ }
+ );
+ });
+}
+
+function openPreferencesViaOpenPreferencesAPI(aPane, aOptions) {
+ return new Promise(resolve => {
+ let finalPrefPaneLoaded = TestUtils.topicObserved(
+ "sync-pane-loaded",
+ () => true
+ );
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
+ openPreferences(aPane);
+ let newTabBrowser = gBrowser.selectedBrowser;
+
+ newTabBrowser.addEventListener(
+ "Initialized",
+ function () {
+ newTabBrowser.contentWindow.addEventListener(
+ "load",
+ async function () {
+ let win = gBrowser.contentWindow;
+ let selectedPane = win.history.state;
+ await finalPrefPaneLoaded;
+ if (!aOptions || !aOptions.leaveOpen) {
+ gBrowser.removeCurrentTab();
+ }
+ resolve({ selectedPane });
+ },
+ { once: true }
+ );
+ },
+ { capture: true, once: true }
+ );
+ });
+}
+
+function openSiteDataSettingsDialog() {
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let settingsBtn = doc.getElementById("siteDataSettings");
+ let dialogOverlay = content.gSubDialog._preloadDialog._overlay;
+ let dialogLoadPromise = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/siteDataSettings.xhtml"
+ );
+ let dialogInitPromise = TestUtils.topicObserved(
+ "sitedata-settings-init",
+ () => true
+ );
+ let fullyLoadPromise = Promise.all([
+ dialogLoadPromise,
+ dialogInitPromise,
+ ]).then(() => {
+ is_element_visible(dialogOverlay, "The Settings dialog should be visible");
+ });
+ settingsBtn.doCommand();
+ return fullyLoadPromise;
+}
+
+function promiseSettingsDialogClose() {
+ return new Promise(resolve => {
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let dialogOverlay = win.gSubDialog._topDialog._overlay;
+ let dialogWin = win.gSubDialog._topDialog._frame.contentWindow;
+ dialogWin.addEventListener(
+ "unload",
+ function unload() {
+ if (
+ dialogWin.document.documentURI ===
+ "chrome://browser/content/preferences/dialogs/siteDataSettings.xhtml"
+ ) {
+ is_element_hidden(
+ dialogOverlay,
+ "The Settings dialog should be hidden"
+ );
+ resolve();
+ }
+ },
+ { once: true }
+ );
+ });
+}
+
+function assertSitesListed(doc, hosts) {
+ let frameDoc = content.gSubDialog._topDialog._frame.contentDocument;
+ let removeAllBtn = frameDoc.getElementById("removeAll");
+ let sitesList = frameDoc.getElementById("sitesList");
+ let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
+ is(totalSitesNumber, hosts.length, "Should list the right sites number");
+ hosts.forEach(host => {
+ let site = sitesList.querySelector(`richlistitem[host="${host}"]`);
+ ok(site, `Should list the site of ${host}`);
+ });
+ is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button");
+}
+
+// Counter used by addTestData to generate unique cookie names across function
+// calls.
+let cookieID = 0;
+
+async function addTestData(data) {
+ let hosts = new Set();
+
+ for (let site of data) {
+ is(
+ typeof site.origin,
+ "string",
+ "Passed an origin string into addTestData."
+ );
+ if (site.persisted) {
+ await SiteDataTestUtils.persist(site.origin);
+ }
+
+ if (site.usage) {
+ await SiteDataTestUtils.addToIndexedDB(site.origin, site.usage);
+ }
+
+ for (let i = 0; i < (site.cookies || 0); i++) {
+ SiteDataTestUtils.addToCookies({
+ origin: site.origin,
+ name: `cookie${cookieID++}`,
+ });
+ }
+
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ site.origin
+ );
+
+ hosts.add(principal.baseDomain || principal.host);
+ }
+
+ return Array.from(hosts);
+}
+
+function promiseCookiesCleared() {
+ return TestUtils.topicObserved("cookie-changed", (subj, data) => {
+ return data === "cleared";
+ });
+}
+
+async function loadServiceWorkerTestPage(url) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ await TestUtils.waitForCondition(() => {
+ return SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () =>
+ content.document.body.getAttribute(
+ "data-test-service-worker-registered"
+ ) === "true"
+ );
+ }, `Fail to load service worker test ${url}`);
+ BrowserTestUtils.removeTab(tab);
+}
+
+function promiseServiceWorkersCleared() {
+ return TestUtils.waitForCondition(() => {
+ let serviceWorkers = serviceWorkerManager.getAllRegistrations();
+ if (!serviceWorkers.length) {
+ ok(true, "Cleared all service workers");
+ return true;
+ }
+ return false;
+ }, "Should clear all service workers");
+}
+
+function promiseServiceWorkerRegisteredFor(url) {
+ return TestUtils.waitForCondition(() => {
+ try {
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(url);
+ let sw = serviceWorkerManager.getRegistrationByPrincipal(
+ principal,
+ principal.spec
+ );
+ if (sw) {
+ ok(true, `Found the service worker registered for ${url}`);
+ return true;
+ }
+ } catch (e) {}
+ return false;
+ }, `Should register service worker for ${url}`);
+}
diff --git a/browser/components/preferences/tests/siteData/offline/manifest.appcache b/browser/components/preferences/tests/siteData/offline/manifest.appcache
new file mode 100644
index 0000000000..a9287c64e6
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/offline/manifest.appcache
@@ -0,0 +1,3 @@
+CACHE MANIFEST
+# V1
+offline.html
diff --git a/browser/components/preferences/tests/siteData/offline/offline.html b/browser/components/preferences/tests/siteData/offline/offline.html
new file mode 100644
index 0000000000..f76b8a2bce
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/offline/offline.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html manifest="manifest.appcache">>
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="Cache-Control" content="public" />
+ <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
+
+ </head>
+
+ <body>
+ <h1>Set up offline appcache Test</h1>
+ </body>
+</html>
diff --git a/browser/components/preferences/tests/siteData/service_worker_test.html b/browser/components/preferences/tests/siteData/service_worker_test.html
new file mode 100644
index 0000000000..56f5173481
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/service_worker_test.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="Cache-Control" content="public" />
+ <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
+
+ <title>Service Worker Test</title>
+
+ </head>
+
+ <body>
+ <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"));
+ </script>
+ </body>
+</html>
diff --git a/browser/components/preferences/tests/siteData/service_worker_test.js b/browser/components/preferences/tests/siteData/service_worker_test.js
new file mode 100644
index 0000000000..2aba167d18
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/service_worker_test.js
@@ -0,0 +1 @@
+// empty worker, always succeed!
diff --git a/browser/components/preferences/tests/siteData/site_data_test.html b/browser/components/preferences/tests/siteData/site_data_test.html
new file mode 100644
index 0000000000..758106b0a5
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/site_data_test.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="Cache-Control" content="public" />
+ <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
+
+ <title>Site Data Test</title>
+
+ </head>
+
+ <body>
+ <h1>Site Data Test</h1>
+ <script type="text/javascript">
+ let request = indexedDB.open("TestDatabase", 1);
+ request.onupgradeneeded = function(e) {
+ let db = e.target.result;
+ db.createObjectStore("TestStore", { keyPath: "id" });
+ };
+ request.onsuccess = function(e) {
+ let db = e.target.result;
+ let tx = db.transaction("TestStore", "readwrite");
+ let store = tx.objectStore("TestStore");
+ tx.oncomplete = () => document.dispatchEvent(new CustomEvent("test-indexedDB-done", {bubbles: true, cancelable: false}));
+ store.put({ id: "test_id", description: "Site Data Test"});
+ };
+ </script>
+ </body>
+</html>
diff --git a/browser/components/preferences/tests/subdialog.xhtml b/browser/components/preferences/tests/subdialog.xhtml
new file mode 100644
index 0000000000..54fa88c25d
--- /dev/null
+++ b/browser/components/preferences/tests/subdialog.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Sample sub-dialog" style="width: 32em; height: 5em;"
+ onload="document.getElementById('textbox').focus();">
+<dialog id="subDialog">
+ <script>
+ document.addEventListener("dialogaccept", acceptSubdialog);
+ function acceptSubdialog() {
+ window.arguments[0].acceptCount++;
+ }
+ </script>
+
+ <description id="desc">A sample sub-dialog for testing</description>
+
+ <html:input id="textbox" value="Default text" />
+
+ <separator class="thin"/>
+
+ <button oncommand="window.close();" label="Close" />
+
+</dialog>
+</window>
diff --git a/browser/components/preferences/tests/subdialog2.xhtml b/browser/components/preferences/tests/subdialog2.xhtml
new file mode 100644
index 0000000000..9ae04d5675
--- /dev/null
+++ b/browser/components/preferences/tests/subdialog2.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Sample sub-dialog #2" style="width: 32em; height: 5em;"
+ onload="document.getElementById('textbox').focus();">
+<dialog id="subDialog">
+ <script>
+ document.addEventListener("dialogaccept", acceptSubdialog);
+ function acceptSubdialog() {
+ window.arguments[0].acceptCount++;
+ }
+ </script>
+
+ <description id="desc">A sample sub-dialog for testing</description>
+
+ <html:input id="textbox" value="Default text" />
+
+ <separator class="thin"/>
+
+ <button oncommand="window.close();" label="Close" />
+
+</dialog>
+</window>