summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/preferences/test/browser
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/preferences/test/browser')
-rw-r--r--comm/mail/components/preferences/test/browser/browser.ini20
-rw-r--r--comm/mail/components/preferences/test/browser/browser_chat.js74
-rw-r--r--comm/mail/components/preferences/test/browser/browser_cloudfile.js796
-rw-r--r--comm/mail/components/preferences/test/browser/browser_compose.js87
-rw-r--r--comm/mail/components/preferences/test/browser/browser_general.js380
-rw-r--r--comm/mail/components/preferences/test/browser/browser_openPreferences.js37
-rw-r--r--comm/mail/components/preferences/test/browser/browser_privacy.js454
-rw-r--r--comm/mail/components/preferences/test/browser/browser_sync.js419
-rw-r--r--comm/mail/components/preferences/test/browser/files/avatar.pngbin0 -> 11019 bytes
-rw-r--r--comm/mail/components/preferences/test/browser/files/icon.svg7
-rw-r--r--comm/mail/components/preferences/test/browser/files/management.html10
-rw-r--r--comm/mail/components/preferences/test/browser/head.js314
12 files changed, 2598 insertions, 0 deletions
diff --git a/comm/mail/components/preferences/test/browser/browser.ini b/comm/mail/components/preferences/test/browser/browser.ini
new file mode 100644
index 0000000000..44b7d97a31
--- /dev/null
+++ b/comm/mail/components/preferences/test/browser/browser.ini
@@ -0,0 +1,20 @@
+[DEFAULT]
+head = head.js
+prefs =
+ mail.provider.suppress_dialog_on_startup=true
+ mail.spotlight.firstRunDone=true
+ mail.winsearch.firstRunDone=true
+ mailnews.start_page.override_url=about:blank
+ mailnews.start_page.url=about:blank
+subsuite = thunderbird
+
+[browser_chat.js]
+[browser_cloudfile.js]
+support-files = files/icon.svg files/management.html
+[browser_compose.js]
+[browser_general.js]
+[browser_openPreferences.js]
+[browser_privacy.js]
+[browser_sync.js]
+skip-if = !nightly_build
+support-files = files/avatar.png
diff --git a/comm/mail/components/preferences/test/browser/browser_chat.js b/comm/mail/components/preferences/test/browser/browser_chat.js
new file mode 100644
index 0000000000..009f2a9211
--- /dev/null
+++ b/comm/mail/components/preferences/test/browser/browser_chat.js
@@ -0,0 +1,74 @@
+/* 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/. */
+
+add_task(async () => {
+ await testCheckboxes(
+ "paneChat",
+ "chatPaneCategory",
+ {
+ checkboxID: "reportIdle",
+ pref: "messenger.status.reportIdle",
+ enabledElements: ["#autoAway", "#timeBeforeAway"],
+ },
+ {
+ checkboxID: "sendTyping",
+ pref: "purple.conversations.im.send_typing",
+ },
+ {
+ checkboxID: "desktopChatNotifications",
+ pref: "mail.chat.show_desktop_notifications",
+ },
+ {
+ checkboxID: "getAttention",
+ pref: "messenger.options.getAttentionOnNewMessages",
+ },
+ {
+ checkboxID: "chatNotification",
+ pref: "mail.chat.play_sound",
+ enabledElements: ["#chatSoundType radio"],
+ }
+ );
+
+ Services.prefs.setBoolPref("messenger.status.reportIdle", true);
+ await testCheckboxes("paneChat", "chatPaneCategory", {
+ checkboxID: "autoAway",
+ pref: "messenger.status.awayWhenIdle",
+ enabledElements: ["#defaultIdleAwayMessage"],
+ });
+
+ Services.prefs.setBoolPref("mail.chat.play_sound", true);
+ await testRadioButtons("paneChat", "chatPaneCategory", {
+ pref: "mail.chat.play_sound.type",
+ states: [
+ {
+ id: "chatSoundSystemSound",
+ prefValue: 0,
+ },
+ {
+ id: "chatSoundCustom",
+ prefValue: 1,
+ enabledElements: ["#chatSoundUrlLocation", "#browseForChatSound"],
+ },
+ ],
+ });
+});
+
+add_task(async function testMessageStylePreview() {
+ await openNewPrefsTab("paneChat", "chatPaneCategory");
+ const conversationLoad = TestUtils.topicObserved("conversation-loaded");
+ const [subject] = await conversationLoad;
+ do {
+ await BrowserTestUtils.waitForEvent(subject, "MessagesDisplayed");
+ } while (subject.getPendingMessagesCount() > 0);
+ const messageParent = subject.contentChatNode;
+ let message = messageParent.firstElementChild;
+ const messages = new Set();
+ while (message) {
+ ok(message._originalMsg);
+ messages.add(message._originalMsg);
+ message = message.nextElementSibling;
+ }
+ is(messages.size, 3, "All 3 messages displayed");
+ await closePrefsTab();
+});
diff --git a/comm/mail/components/preferences/test/browser/browser_cloudfile.js b/comm/mail/components/preferences/test/browser/browser_cloudfile.js
new file mode 100644
index 0000000000..9f395f9ab8
--- /dev/null
+++ b/comm/mail/components/preferences/test/browser/browser_cloudfile.js
@@ -0,0 +1,796 @@
+/* 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/. */
+
+/* eslint-env webextensions */
+
+let { cloudFileAccounts } = ChromeUtils.import(
+ "resource:///modules/cloudFileAccounts.jsm"
+);
+let { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+function ManagementScript() {
+ browser.test.onMessage.addListener((message, assertMessage, browserStyle) => {
+ if (message !== "check-style") {
+ return;
+ }
+ function verifyButton(buttonElement, expected) {
+ let buttonStyle = window.getComputedStyle(buttonElement);
+ let buttonBackgroundColor = buttonStyle.backgroundColor;
+ if (browserStyle && expected.hasBrowserStyleClass) {
+ browser.test.assertEq(
+ "rgb(9, 150, 248)",
+ buttonBackgroundColor,
+ assertMessage
+ );
+ } else {
+ browser.test.assertTrue(
+ buttonBackgroundColor !== "rgb(9, 150, 248)",
+ assertMessage
+ );
+ }
+ }
+
+ function verifyCheckboxOrRadio(element, expected) {
+ let style = window.getComputedStyle(element);
+ let styledBackground = element.checked
+ ? "rgb(9, 150, 248)"
+ : "rgb(255, 255, 255)";
+ if (browserStyle && expected.hasBrowserStyleClass) {
+ browser.test.assertEq(
+ styledBackground,
+ style.backgroundColor,
+ assertMessage
+ );
+ } else {
+ browser.test.assertTrue(
+ style.backgroundColor != styledBackground,
+ assertMessage
+ );
+ }
+ }
+
+ let normalButton = document.getElementById("normalButton");
+ let browserStyleButton = document.getElementById("browserStyleButton");
+ verifyButton(normalButton, { hasBrowserStyleClass: false });
+ verifyButton(browserStyleButton, { hasBrowserStyleClass: true });
+
+ let normalCheckbox1 = document.getElementById("normalCheckbox1");
+ let normalCheckbox2 = document.getElementById("normalCheckbox2");
+ let browserStyleCheckbox = document.getElementById("browserStyleCheckbox");
+ verifyCheckboxOrRadio(normalCheckbox1, { hasBrowserStyleClass: false });
+ verifyCheckboxOrRadio(normalCheckbox2, { hasBrowserStyleClass: false });
+ verifyCheckboxOrRadio(browserStyleCheckbox, {
+ hasBrowserStyleClass: true,
+ });
+
+ let normalRadio1 = document.getElementById("normalRadio1");
+ let normalRadio2 = document.getElementById("normalRadio2");
+ let browserStyleRadio = document.getElementById("browserStyleRadio");
+ verifyCheckboxOrRadio(normalRadio1, { hasBrowserStyleClass: false });
+ verifyCheckboxOrRadio(normalRadio2, { hasBrowserStyleClass: false });
+ verifyCheckboxOrRadio(browserStyleRadio, { hasBrowserStyleClass: true });
+
+ browser.test.notifyPass("management-ui-browser_style");
+ });
+ browser.test.sendMessage("management-ui-ready");
+}
+
+let extension;
+async function startExtension(browser_style) {
+ let cloud_file = {
+ name: "Mochitest",
+ management_url: "management.html",
+ };
+
+ switch (browser_style) {
+ case "true":
+ cloud_file.browser_style = true;
+ break;
+ case "false":
+ cloud_file.browser_style = false;
+ break;
+ }
+
+ extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ browser.test.onMessage.addListener(async message => {
+ if (message != "set-configured") {
+ return;
+ }
+ let accounts = await browser.cloudFile.getAllAccounts();
+ for (let account of accounts) {
+ await browser.cloudFile.updateAccount(account.id, {
+ configured: true,
+ });
+ }
+ browser.test.sendMessage("ready");
+ });
+ },
+ files: {
+ "management.html": `<html>
+ <body>
+ <a id="a" href="https://www.example.com/">Click me!</a>
+ <button id="normalButton" name="button" class="default">Default</button>
+ <button id="browserStyleButton" name="button" class="browser-style default">Default</button>
+
+ <input id="normalCheckbox1" type="checkbox"/>
+ <input id="normalCheckbox2" type="checkbox"/><label>Checkbox</label>
+ <div class="browser-style">
+ <input id="browserStyleCheckbox" type="checkbox"><label for="browserStyleCheckbox">Checkbox</label>
+ </div>
+
+ <input id="normalRadio1" type="radio"/>
+ <input id="normalRadio2" type="radio"/><label>Radio</label>
+ <div class="browser-style">
+ <input id="browserStyleRadio" checked="" type="radio"><label for="browserStyleRadio">Radio</label>
+ </div>
+ </body>
+ <script src="management.js" type="text/javascript"></script>
+ </html>`,
+ "management.js": ManagementScript,
+ },
+ manifest: {
+ cloud_file,
+ applications: { gecko: { id: "cloudfile@mochitest" } },
+ },
+ });
+
+ info("Starting extension");
+ await extension.startup();
+
+ if (accountIsConfigured) {
+ extension.sendMessage("set-configured");
+ await extension.awaitMessage("ready");
+ }
+}
+
+add_task(async () => {
+ // Register a fake provider representing a built-in provider. We don't
+ // currently ship any built-in providers, but if we did, we should check
+ // if they are present before doing this. Built-in providers can be
+ // problematic for artifact builds.
+ cloudFileAccounts.registerProvider("Fake-Test", {
+ displayName: "XYZ Fake",
+ type: "ext-fake@extensions.thunderbird.net",
+ });
+ registerCleanupFunction(() => {
+ cloudFileAccounts.unregisterProvider("Fake-Test");
+ });
+});
+
+let accountIsConfigured = false;
+
+// Mock the prompt service. We're going to be asked if we're sure
+// we want to remove an account, so let's say yes.
+
+/** @implements {nsIPromptService} */
+let mockPromptService = {
+ confirmCount: 0,
+ confirm() {
+ this.confirmCount++;
+ return true;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]),
+};
+/** @implements {nsIExternalProtocolService} */
+let mockExternalProtocolService = {
+ _loadedURLs: [],
+ externalProtocolHandlerExists(aProtocolScheme) {},
+ getApplicationDescription(aScheme) {},
+ getProtocolHandlerInfo(aProtocolScheme) {},
+ getProtocolHandlerInfoFromOS(aProtocolScheme, aFound) {},
+ isExposedProtocol(aProtocolScheme) {},
+ loadURI(aURI, aWindowContext) {
+ this._loadedURLs.push(aURI.spec);
+ },
+ setProtocolHandlerDefaults(aHandlerInfo, aOSHandlerExists) {},
+ urlLoaded(aURL) {
+ return this._loadedURLs.includes(aURL);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIExternalProtocolService"]),
+};
+
+let originalPromptService = Services.prompt;
+Services.prompt = mockPromptService;
+
+let mockExternalProtocolServiceCID = MockRegistrar.register(
+ "@mozilla.org/uriloader/external-protocol-service;1",
+ mockExternalProtocolService
+);
+
+registerCleanupFunction(() => {
+ Services.prompt = originalPromptService;
+ MockRegistrar.unregister(mockExternalProtocolServiceCID);
+});
+
+add_task(async function addRemoveAccounts() {
+ is(cloudFileAccounts.providers.length, 1);
+ is(cloudFileAccounts.accounts.length, 0);
+
+ // Load the preferences tab.
+
+ let { prefsDocument, prefsWindow } = await openNewPrefsTab(
+ "paneCompose",
+ "compositionAttachmentsCategory"
+ );
+
+ // Check everything is as it should be.
+
+ let accountList = prefsDocument.getElementById("cloudFileView");
+ is(accountList.itemCount, 0);
+
+ let buttonList = prefsDocument.getElementById("addCloudFileAccountButtons");
+ ok(!buttonList.hidden);
+ is(buttonList.childElementCount, 1);
+ is(
+ buttonList.children[0].getAttribute("value"),
+ "ext-fake@extensions.thunderbird.net"
+ );
+
+ let menuButton = prefsDocument.getElementById("addCloudFileAccount");
+ ok(menuButton.hidden);
+ is(menuButton.itemCount, 1);
+ is(
+ menuButton.getItemAtIndex(0).getAttribute("value"),
+ "ext-fake@extensions.thunderbird.net"
+ );
+
+ let removeButton = prefsDocument.getElementById("removeCloudFileAccount");
+ ok(removeButton.disabled);
+
+ let cloudFileDefaultPanel = prefsDocument.getElementById(
+ "cloudFileDefaultPanel"
+ );
+ ok(!cloudFileDefaultPanel.hidden);
+
+ let browserWrapper = prefsDocument.getElementById("cloudFileSettingsWrapper");
+ is(browserWrapper.childElementCount, 0);
+
+ // Register our test provider.
+
+ await startExtension();
+ is(cloudFileAccounts.providers.length, 2);
+ is(cloudFileAccounts.accounts.length, 0);
+
+ await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve));
+
+ is(buttonList.childElementCount, 2);
+ is(
+ buttonList.children[0].getAttribute("value"),
+ "ext-fake@extensions.thunderbird.net"
+ );
+ is(buttonList.children[1].getAttribute("value"), "ext-cloudfile@mochitest");
+ is(
+ buttonList.children[1].style.listStyleImage,
+ `url("chrome://messenger/content/extension.svg")`
+ );
+
+ is(menuButton.itemCount, 2);
+ is(
+ menuButton.getItemAtIndex(0).getAttribute("value"),
+ "ext-fake@extensions.thunderbird.net"
+ );
+ is(
+ menuButton.getItemAtIndex(1).getAttribute("value"),
+ "ext-cloudfile@mochitest"
+ );
+ is(
+ menuButton.getItemAtIndex(1).getAttribute("image"),
+ "chrome://messenger/content/extension.svg"
+ );
+
+ // Create a new account.
+
+ EventUtils.synthesizeMouseAtCenter(
+ buttonList.children[1],
+ { clickCount: 1 },
+ prefsWindow
+ );
+ is(cloudFileAccounts.accounts.length, 1);
+ is(cloudFileAccounts.configuredAccounts.length, 0);
+
+ let account = cloudFileAccounts.accounts[0];
+ let accountKey = account.accountKey;
+ is(cloudFileAccounts.accounts[0].type, "ext-cloudfile@mochitest");
+
+ // Check prefs were updated.
+
+ is(
+ Services.prefs.getCharPref(
+ `mail.cloud_files.accounts.${accountKey}.displayName`
+ ),
+ "Mochitest"
+ );
+ is(
+ Services.prefs.getCharPref(`mail.cloud_files.accounts.${accountKey}.type`),
+ "ext-cloudfile@mochitest"
+ );
+
+ // Check UI was updated.
+
+ is(accountList.itemCount, 1);
+ is(accountList.selectedIndex, 0);
+ ok(!removeButton.disabled);
+
+ let accountListItem = accountList.selectedItem;
+ is(accountListItem.getAttribute("value"), accountKey);
+ is(
+ accountListItem.querySelector(".typeIcon:not(.configuredWarning)").src,
+ "chrome://messenger/content/extension.svg"
+ );
+ is(accountListItem.querySelector("label").value, "Mochitest");
+ is(accountListItem.querySelector(".configuredWarning").hidden, false);
+
+ ok(cloudFileDefaultPanel.hidden);
+ is(browserWrapper.childElementCount, 1);
+
+ let browser = browserWrapper.firstElementChild;
+ if (
+ browser.webProgress?.isLoadingDocument ||
+ browser.currentURI?.spec == "about:blank"
+ ) {
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+ is(
+ browser.currentURI.pathQueryRef,
+ `/management.html?accountId=${accountKey}`
+ );
+ await extension.awaitMessage("management-ui-ready");
+
+ let tabmail = document.getElementById("tabmail");
+ let tabCount = tabmail.tabInfo.length;
+ BrowserTestUtils.synthesizeMouseAtCenter("a", {}, browser);
+ // It might take a moment to get to the external protocol service.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 500));
+ ok(
+ mockExternalProtocolService.urlLoaded("https://www.example.com/"),
+ "Link click sent to external protocol service."
+ );
+ is(tabmail.tabInfo.length, tabCount, "No new tab opened");
+
+ // Rename the account.
+
+ EventUtils.synthesizeMouseAtCenter(
+ accountListItem,
+ { clickCount: 1 },
+ prefsWindow
+ );
+
+ await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve));
+
+ is(
+ prefsDocument.activeElement.closest("input"),
+ accountListItem.querySelector("input")
+ );
+ ok(accountListItem.querySelector("label").hidden);
+ ok(!accountListItem.querySelector("input").hidden);
+ is(accountListItem.querySelector("input").value, "Mochitest");
+ EventUtils.synthesizeKey("VK_RIGHT", undefined, prefsWindow);
+ EventUtils.synthesizeKey("!", undefined, prefsWindow);
+ EventUtils.synthesizeKey("VK_RETURN", undefined, prefsWindow);
+
+ await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve));
+
+ is(prefsDocument.activeElement, accountList);
+ ok(!accountListItem.querySelector("label").hidden);
+ is(accountListItem.querySelector("label").value, "Mochitest!");
+ ok(accountListItem.querySelector("input").hidden);
+ is(
+ Services.prefs.getCharPref(
+ `mail.cloud_files.accounts.${accountKey}.displayName`
+ ),
+ "Mochitest!"
+ );
+
+ // Start to rename the account, but bail out.
+
+ EventUtils.synthesizeMouseAtCenter(
+ accountListItem,
+ { clickCount: 1 },
+ prefsWindow
+ );
+
+ await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve));
+
+ is(
+ prefsDocument.activeElement.closest("input"),
+ accountListItem.querySelector("input")
+ );
+ EventUtils.synthesizeKey("O", undefined, prefsWindow);
+ EventUtils.synthesizeKey("o", undefined, prefsWindow);
+ EventUtils.synthesizeKey("p", undefined, prefsWindow);
+ EventUtils.synthesizeKey("s", undefined, prefsWindow);
+ EventUtils.synthesizeKey("VK_ESCAPE", undefined, prefsWindow);
+
+ await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve));
+
+ is(prefsDocument.activeElement, accountList);
+ ok(!accountListItem.querySelector("label").hidden);
+ is(accountListItem.querySelector("label").value, "Mochitest!");
+ ok(accountListItem.querySelector("input").hidden);
+ is(
+ Services.prefs.getCharPref(
+ `mail.cloud_files.accounts.${accountKey}.displayName`
+ ),
+ "Mochitest!"
+ );
+
+ // Configure the account.
+
+ account.configured = true;
+ accountIsConfigured = true;
+ cloudFileAccounts.emit("accountConfigured", account);
+
+ await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve));
+
+ is(accountListItem.querySelector(".configuredWarning").hidden, true);
+ is(cloudFileAccounts.accounts.length, 1);
+ is(cloudFileAccounts.configuredAccounts.length, 1);
+
+ // Remove the test provider. The list item, button, and browser should disappear.
+
+ info("Stopping extension");
+ await extension.unload();
+ is(cloudFileAccounts.providers.length, 1);
+ is(cloudFileAccounts.accounts.length, 0);
+
+ await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve));
+
+ is(buttonList.childElementCount, 1);
+ is(
+ buttonList.children[0].getAttribute("value"),
+ "ext-fake@extensions.thunderbird.net"
+ );
+ is(menuButton.itemCount, 1);
+ is(
+ menuButton.getItemAtIndex(0).getAttribute("value"),
+ "ext-fake@extensions.thunderbird.net"
+ );
+ is(accountList.itemCount, 0);
+ ok(!cloudFileDefaultPanel.hidden);
+ is(browserWrapper.childElementCount, 0);
+
+ // Re-add the test provider.
+
+ await startExtension();
+
+ is(cloudFileAccounts.providers.length, 2);
+ is(cloudFileAccounts.accounts.length, 1);
+ is(cloudFileAccounts.configuredAccounts.length, 1);
+
+ await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve));
+
+ is(buttonList.childElementCount, 2);
+ is(
+ buttonList.children[0].getAttribute("value"),
+ "ext-fake@extensions.thunderbird.net"
+ );
+ is(buttonList.children[1].getAttribute("value"), "ext-cloudfile@mochitest");
+
+ is(menuButton.itemCount, 2);
+ is(
+ menuButton.getItemAtIndex(0).getAttribute("value"),
+ "ext-fake@extensions.thunderbird.net"
+ );
+ is(
+ menuButton.getItemAtIndex(1).getAttribute("value"),
+ "ext-cloudfile@mochitest"
+ );
+
+ is(accountList.itemCount, 1);
+ is(accountList.selectedIndex, -1);
+ ok(removeButton.disabled);
+
+ accountListItem = accountList.getItemAtIndex(0);
+ is(
+ Services.prefs.getCharPref(
+ `mail.cloud_files.accounts.${accountKey}.displayName`
+ ),
+ "Mochitest!"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ accountList.getItemAtIndex(0),
+ { clickCount: 1 },
+ prefsWindow
+ );
+ ok(!removeButton.disabled);
+ EventUtils.synthesizeMouseAtCenter(
+ removeButton,
+ { clickCount: 1 },
+ prefsWindow
+ );
+ is(mockPromptService.confirmCount, 1);
+
+ ok(
+ !Services.prefs.prefHasUserValue(
+ `mail.cloud_files.accounts.${accountKey}.displayName`
+ )
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(
+ `mail.cloud_files.accounts.${accountKey}.type`
+ )
+ );
+
+ is(cloudFileAccounts.providers.length, 2);
+ is(cloudFileAccounts.accounts.length, 0);
+
+ info("Stopping extension");
+ await extension.unload();
+ is(cloudFileAccounts.providers.length, 1);
+ is(cloudFileAccounts.accounts.length, 0);
+
+ // Close the preferences tab.
+
+ await closePrefsTab();
+});
+
+async function subtestBrowserStyle(assertMessage, expected) {
+ is(cloudFileAccounts.providers.length, 1);
+ is(cloudFileAccounts.accounts.length, 0);
+
+ // Load the preferences tab.
+
+ let { prefsDocument, prefsWindow } = await openNewPrefsTab(
+ "paneCompose",
+ "compositionAttachmentsCategory"
+ );
+
+ // Minimal check everything is as it should be.
+
+ let accountList = prefsDocument.getElementById("cloudFileView");
+ is(accountList.itemCount, 0);
+
+ let buttonList = prefsDocument.getElementById("addCloudFileAccountButtons");
+ ok(!buttonList.hidden);
+
+ let browserWrapper = prefsDocument.getElementById("cloudFileSettingsWrapper");
+ is(browserWrapper.childElementCount, 0);
+
+ // Register our test provider.
+
+ await startExtension(expected.browser_style);
+ is(cloudFileAccounts.providers.length, 2);
+ is(cloudFileAccounts.accounts.length, 0);
+
+ await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve));
+
+ is(buttonList.childElementCount, 2);
+ is(buttonList.children[1].getAttribute("value"), "ext-cloudfile@mochitest");
+
+ // Create a new account.
+
+ EventUtils.synthesizeMouseAtCenter(
+ buttonList.children[1],
+ { clickCount: 1 },
+ prefsWindow
+ );
+ is(cloudFileAccounts.accounts.length, 1);
+ is(cloudFileAccounts.configuredAccounts.length, 0);
+
+ let account = cloudFileAccounts.accounts[0];
+ let accountKey = account.accountKey;
+ is(cloudFileAccounts.accounts[0].type, "ext-cloudfile@mochitest");
+
+ // Minimal check UI was updated.
+
+ is(accountList.itemCount, 1);
+ is(accountList.selectedIndex, 0);
+
+ let accountListItem = accountList.selectedItem;
+ is(accountListItem.getAttribute("value"), accountKey);
+
+ is(browserWrapper.childElementCount, 1);
+ let browser = browserWrapper.firstElementChild;
+ if (
+ browser.webProgress?.isLoadingDocument ||
+ browser.currentURI?.spec == "about:blank"
+ ) {
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+ is(
+ browser.currentURI.pathQueryRef,
+ `/management.html?accountId=${accountKey}`
+ );
+ await extension.awaitMessage("management-ui-ready");
+
+ // Test browser_style
+
+ extension.sendMessage(
+ "check-style",
+ assertMessage,
+ expected.browser_style == "true"
+ );
+ await extension.awaitFinish("management-ui-browser_style");
+
+ // Remove the account
+
+ accountListItem = accountList.getItemAtIndex(0);
+ EventUtils.synthesizeMouseAtCenter(
+ accountList.getItemAtIndex(0),
+ { clickCount: 1 },
+ prefsWindow
+ );
+
+ let removeButton = prefsDocument.getElementById("removeCloudFileAccount");
+ ok(!removeButton.disabled);
+ EventUtils.synthesizeMouseAtCenter(
+ removeButton,
+ { clickCount: 1 },
+ prefsWindow
+ );
+ is(mockPromptService.confirmCount, expected.confirmCount);
+
+ is(cloudFileAccounts.providers.length, 2);
+ is(cloudFileAccounts.accounts.length, 0);
+
+ info("Stopping extension");
+ await extension.unload();
+ is(cloudFileAccounts.providers.length, 1);
+ is(cloudFileAccounts.accounts.length, 0);
+
+ // Close the preferences tab.
+
+ await closePrefsTab();
+}
+
+add_task(async function test_without_setting_browser_style() {
+ await subtestBrowserStyle(
+ "Expected correct style when browser_style is excluded",
+ {
+ confirmCount: 2,
+ browser_style: "default",
+ }
+ );
+});
+
+add_task(async function test_with_browser_style_set_to_true() {
+ await subtestBrowserStyle(
+ "Expected correct style when browser_style is set to `true`",
+ {
+ confirmCount: 3,
+ browser_style: "true",
+ }
+ );
+});
+
+add_task(async function test_with_browser_style_set_to_false() {
+ await subtestBrowserStyle(
+ "Expected no style when browser_style is set to `false`",
+ {
+ confirmCount: 4,
+ browser_style: "false",
+ }
+ );
+});
+
+add_task(async function accountListOverflow() {
+ is(cloudFileAccounts.providers.length, 1);
+ is(cloudFileAccounts.accounts.length, 0);
+
+ // Register our test provider.
+
+ await startExtension();
+
+ is(cloudFileAccounts.providers.length, 2);
+ is(cloudFileAccounts.accounts.length, 0);
+
+ // Load the preferences tab.
+
+ let { prefsDocument, prefsWindow } = await openNewPrefsTab(
+ "paneCompose",
+ "compositionAttachmentsCategory"
+ );
+
+ let accountList = prefsDocument.getElementById("cloudFileView");
+ is(accountList.itemCount, 0);
+
+ let buttonList = prefsDocument.getElementById("addCloudFileAccountButtons");
+ ok(!buttonList.hidden);
+ is(buttonList.childElementCount, 2);
+ is(buttonList.children[0].getAttribute("value"), "ext-cloudfile@mochitest");
+
+ let menuButton = prefsDocument.getElementById("addCloudFileAccount");
+ ok(menuButton.hidden);
+
+ // Add new accounts until the list overflows. The list of buttons should be hidden
+ // and the button with the drop-down should appear.
+
+ let count = 0;
+ do {
+ let readyPromise = extension.awaitMessage("management-ui-ready");
+ EventUtils.synthesizeMouseAtCenter(
+ buttonList.children[0],
+ { clickCount: 1 },
+ prefsWindow
+ );
+ await readyPromise;
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 500));
+ if (buttonList.hidden) {
+ break;
+ }
+ } while (++count < 25);
+
+ ok(count < 24); // If count reaches 25, we have a problem.
+ ok(!menuButton.hidden);
+
+ // Remove the added accounts. The list of buttons should not reappear and the
+ // button with the drop-down should remain.
+
+ let removeButton = prefsDocument.getElementById("removeCloudFileAccount");
+ do {
+ EventUtils.synthesizeMouseAtCenter(
+ accountList.getItemAtIndex(0),
+ { clickCount: 1 },
+ prefsWindow
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ removeButton,
+ { clickCount: 1 },
+ prefsWindow
+ );
+ await new Promise(resolve => setTimeout(resolve));
+ } while (--count > 0);
+
+ ok(buttonList.hidden);
+ ok(!menuButton.hidden);
+
+ // Close the preferences tab.
+
+ await closePrefsTab();
+ info("Stopping extension");
+ await extension.unload();
+ Services.prefs.deleteBranch("mail.cloud_files.accounts");
+});
+
+add_task(async function accountListOrder() {
+ is(cloudFileAccounts.providers.length, 1);
+ is(cloudFileAccounts.accounts.length, 0);
+
+ for (let [key, displayName] of [
+ ["someKey1", "carl's Account"],
+ ["someKey2", "Amber's Account"],
+ ["someKey3", "alice's Account"],
+ ["someKey4", "Bob's Account"],
+ ]) {
+ Services.prefs.setCharPref(
+ `mail.cloud_files.accounts.${key}.type`,
+ "ext-cloudfile@mochitest"
+ );
+ Services.prefs.setCharPref(
+ `mail.cloud_files.accounts.${key}.displayName`,
+ displayName
+ );
+ }
+
+ // Register our test provider.
+
+ await startExtension();
+
+ is(cloudFileAccounts.providers.length, 2);
+ is(cloudFileAccounts.accounts.length, 4);
+
+ let { prefsDocument } = await openNewPrefsTab(
+ "paneCompose",
+ "compositionAttachmentsCategory"
+ );
+
+ let accountList = prefsDocument.getElementById("cloudFileView");
+ is(accountList.itemCount, 4);
+
+ is(accountList.getItemAtIndex(0).value, "someKey3");
+ is(accountList.getItemAtIndex(1).value, "someKey2");
+ is(accountList.getItemAtIndex(2).value, "someKey4");
+ is(accountList.getItemAtIndex(3).value, "someKey1");
+
+ await closePrefsTab();
+ info("Stopping extension");
+ await extension.unload();
+ Services.prefs.deleteBranch("mail.cloud_files.accounts");
+});
diff --git a/comm/mail/components/preferences/test/browser/browser_compose.js b/comm/mail/components/preferences/test/browser/browser_compose.js
new file mode 100644
index 0000000000..ea253cb555
--- /dev/null
+++ b/comm/mail/components/preferences/test/browser/browser_compose.js
@@ -0,0 +1,87 @@
+/* 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/. */
+
+add_task(async () => {
+ await testCheckboxes(
+ "paneCompose",
+ "compositionMainCategory",
+ {
+ checkboxID: "addExtension",
+ pref: "mail.forward_add_extension",
+ },
+ {
+ checkboxID: "autoSave",
+ pref: "mail.compose.autosave",
+ enabledElements: ["#autoSaveInterval"],
+ },
+ {
+ checkboxID: "mailWarnOnSendAccelKey",
+ pref: "mail.warn_on_send_accel_key",
+ },
+ {
+ checkboxID: "spellCheckBeforeSend",
+ pref: "mail.SpellCheckBeforeSend",
+ },
+ {
+ checkboxID: "inlineSpellCheck",
+ pref: "mail.spellcheck.inline",
+ }
+ );
+
+ await testCheckboxes(
+ "paneCompose",
+ "FontSelect",
+ {
+ checkboxID: "useReaderDefaults",
+ pref: "msgcompose.default_colors",
+ enabledInverted: true,
+ enabledElements: [
+ "#textColorLabel",
+ "#textColorButton",
+ "#backgroundColorLabel",
+ "#backgroundColorButton",
+ ],
+ },
+ {
+ checkboxID: "defaultToParagraph",
+ pref: "mail.compose.default_to_paragraph",
+ }
+ );
+
+ await testCheckboxes(
+ "paneCompose",
+ "compositionAddressingCategory",
+ {
+ checkboxID: "addressingAutocomplete",
+ pref: "mail.enable_autocomplete",
+ },
+ {
+ checkboxID: "autocompleteLDAP",
+ pref: "ldap_2.autoComplete.useDirectory",
+ enabledElements: ["#directoriesList", "#editButton"],
+ },
+ {
+ checkboxID: "emailCollectionOutgoing",
+ pref: "mail.collect_email_address_outgoing",
+ enabledElements: ["#localDirectoriesList"],
+ }
+ );
+});
+
+add_task(async () => {
+ await testCheckboxes(
+ "paneCompose",
+ "compositionAttachmentsCategory",
+ {
+ checkboxID: "attachment_reminder_label",
+ pref: "mail.compose.attachment_reminder",
+ enabledElements: ["#attachment_reminder_button"],
+ },
+ {
+ checkboxID: "enableThreshold",
+ pref: "mail.compose.big_attachments.notify",
+ enabledElements: ["#cloudFileThreshold"],
+ }
+ );
+});
diff --git a/comm/mail/components/preferences/test/browser/browser_general.js b/comm/mail/components/preferences/test/browser/browser_general.js
new file mode 100644
index 0000000000..f011be1e38
--- /dev/null
+++ b/comm/mail/components/preferences/test/browser/browser_general.js
@@ -0,0 +1,380 @@
+/* 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/. */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+add_task(async () => {
+ requestLongerTimeout(2);
+
+ // Temporarily disable `Once` StaticPrefs check for this test so that we
+ // can change layers.acceleration.disabled without debug builds failing.
+ await SpecialPowers.pushPrefEnv({
+ set: [["preferences.force-disable.check.once.policy", true]],
+ });
+});
+
+add_task(async () => {
+ await testCheckboxes(
+ "paneGeneral",
+ "generalCategory",
+ {
+ checkboxID: "mailnewsStartPageEnabled",
+ pref: "mailnews.start_page.enabled",
+ enabledElements: [
+ "#mailnewsStartPageUrl",
+ "#mailnewsStartPageUrl + button",
+ ],
+ },
+ {
+ checkboxID: "alwaysCheckDefault",
+ pref: "mail.shell.checkDefaultClient",
+ }
+ );
+});
+
+add_task(async () => {
+ await testCheckboxes(
+ "paneGeneral",
+ "scrollingGroup",
+ {
+ checkboxID: "useAutoScroll",
+ pref: "general.autoScroll",
+ },
+ {
+ checkboxID: "useSmoothScrolling",
+ pref: "general.smoothScroll",
+ }
+ );
+});
+
+add_task(async () => {
+ await testCheckboxes(
+ "paneGeneral",
+ "enableGloda",
+ {
+ checkboxID: "enableGloda",
+ pref: "mailnews.database.global.indexer.enabled",
+ },
+ {
+ checkboxID: "allowHWAccel",
+ pref: "layers.acceleration.disabled",
+ prefValues: [true, false],
+ }
+ );
+});
+
+add_task(async () => {
+ if (AppConstants.platform != "macosx") {
+ await testCheckboxes(
+ "paneGeneral",
+ "incomingMailCategory",
+ {
+ checkboxID: "newMailNotification",
+ pref: "mail.biff.play_sound",
+ enabledElements: ["#soundType radio"],
+ },
+ {
+ checkboxID: "newMailNotificationAlert",
+ pref: "mail.biff.show_alert",
+ enabledElements: ["#customizeMailAlert"],
+ }
+ );
+ }
+});
+
+add_task(async () => {
+ if (AppConstants.platform == "macosx") {
+ return;
+ }
+
+ Services.prefs.setBoolPref("mail.biff.play_sound", true);
+
+ await testRadioButtons("paneGeneral", "incomingMailCategory", {
+ pref: "mail.biff.play_sound.type",
+ states: [
+ {
+ id: "system",
+ prefValue: 0,
+ },
+ {
+ id: "custom",
+ prefValue: 1,
+ enabledElements: ["#soundUrlLocation", "#browseForSound"],
+ },
+ ],
+ });
+});
+
+add_task(async () => {
+ await testCheckboxes("paneGeneral", "fontsGroup", {
+ checkboxID: "displayGlyph",
+ pref: "mail.display_glyph",
+ });
+
+ await testCheckboxes(
+ "paneGeneral",
+ "readingAndDisplayCategory",
+ {
+ checkboxID: "automaticallyMarkAsRead",
+ pref: "mailnews.mark_message_read.auto",
+ enabledElements: ["#markAsReadAutoPreferences radio"],
+ },
+ {
+ checkboxID: "closeMsgOnMoveOrDelete",
+ pref: "mail.close_message_window.on_delete",
+ },
+ {
+ checkboxID: "showCondensedAddresses",
+ pref: "mail.showCondensedAddresses",
+ }
+ );
+});
+
+add_task(async () => {
+ Services.prefs.setBoolPref("mailnews.mark_message_read.auto", true);
+
+ await testRadioButtons(
+ "paneGeneral",
+ "mark_read_immediately",
+ {
+ pref: "mailnews.mark_message_read.delay",
+ states: [
+ {
+ id: "mark_read_immediately",
+ prefValue: false,
+ },
+ {
+ id: "markAsReadAfterDelay",
+ prefValue: true,
+ enabledElements: ["#markAsReadDelay"],
+ },
+ ],
+ },
+ {
+ pref: "mail.openMessageBehavior",
+ states: [
+ {
+ id: "newTab",
+ prefValue: 2,
+ },
+ {
+ id: "newWindow",
+ prefValue: 0,
+ },
+ {
+ id: "existingWindow",
+ prefValue: 1,
+ },
+ ],
+ }
+ );
+});
+
+add_task(async () => {
+ // We don't want to wake up the platform search for this test.
+ // if (AppConstants.platform == "macosx") {
+ // tests.push({
+ // checkboxID: "searchIntegration",
+ // pref: "mail.spotlight.enable",
+ // });
+ // } else if (AppConstants.platform == "win") {
+ // tests.push({
+ // checkboxID: "searchIntegration",
+ // pref: "mail.winsearch.enable",
+ // });
+ // }
+
+ await testCheckboxes(
+ "paneGeneral",
+ "allowSmartSize",
+ {
+ checkboxID: "allowSmartSize",
+ pref: "browser.cache.disk.smart_size.enabled",
+ prefValues: [true, false],
+ enabledElements: ["#cacheSize"],
+ },
+ {
+ checkboxID: "offlineCompactFolder",
+ pref: "mail.prompt_purge_threshhold",
+ enabledElements: [
+ "#offlineCompactFolderMin",
+ "#offlineCompactFolderAutomatically",
+ ],
+ }
+ );
+});
+
+add_task(async () => {
+ await testRadioButtons("paneGeneral", "formatLocale", {
+ pref: "intl.regional_prefs.use_os_locales",
+ states: [
+ {
+ id: "appLocale",
+ prefValue: false,
+ },
+ {
+ id: "rsLocale",
+ prefValue: true,
+ },
+ ],
+ });
+});
+
+add_task(async () => {
+ await testRadioButtons("paneGeneral", "filesAttachmentCategory", {
+ pref: "browser.download.useDownloadDir",
+ states: [
+ {
+ id: "saveTo",
+ prefValue: true,
+ enabledElements: ["#downloadFolder", "#chooseFolder"],
+ },
+ {
+ id: "alwaysAsk",
+ prefValue: false,
+ },
+ ],
+ });
+});
+
+add_task(async function testTagDialog() {
+ const { prefsDocument, prefsWindow } = await openNewPrefsTab(
+ "paneGeneral",
+ "tagsCategory"
+ );
+
+ let newTagDialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ undefined,
+ "chrome://messenger/content/preferences/tagDialog.xhtml",
+ {
+ isSubDialog: true,
+ async callback(dialogWindow) {
+ await TestUtils.waitForCondition(
+ () => Services.focus.focusedWindow == dialogWindow,
+ "waiting for subdialog to be focused"
+ );
+
+ const dialogDocument = dialogWindow.document;
+
+ EventUtils.sendString("tbird", dialogWindow);
+ // "#000080" == rgb(0, 0, 128);
+ dialogDocument.getElementById("tagColorPicker").value = "#000080";
+
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.querySelector("dialog").getButton("accept"),
+ {},
+ dialogWindow
+ );
+ await new Promise(r => setTimeout(r));
+ },
+ }
+ );
+
+ let newTagButton = prefsDocument.getElementById("newTagButton");
+ EventUtils.synthesizeMouseAtCenter(newTagButton, {}, prefsWindow);
+ await newTagDialogPromise;
+
+ let tagList = prefsDocument.getElementById("tagList");
+
+ Assert.ok(
+ tagList.querySelector('richlistitem[value="tbird"]'),
+ "new tbird tag should be in the list"
+ );
+ Assert.equal(
+ tagList.querySelector('richlistitem[value="tbird"]').style.color,
+ "rgb(0, 0, 128)",
+ "tbird tag color should be correct"
+ );
+ Assert.equal(
+ tagList.querySelectorAll('richlistitem[value="tbird"]').length,
+ 1,
+ "new tbird tag should be in the list exactly once"
+ );
+
+ Assert.equal(
+ tagList.querySelector('richlistitem[value="tbird"]'),
+ tagList.selectedItem,
+ "tbird tag should be selected"
+ );
+
+ // Now edit the tag. The key should stay the same, name and color will change.
+
+ let editTagDialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ undefined,
+ "chrome://messenger/content/preferences/tagDialog.xhtml",
+ {
+ isSubDialog: true,
+ async callback(dialogWindow) {
+ await TestUtils.waitForCondition(
+ () => Services.focus.focusedWindow == dialogWindow,
+ "waiting for subdialog to be focused"
+ );
+
+ const dialogDocument = dialogWindow.document;
+
+ Assert.equal(
+ dialogDocument.getElementById("name").value,
+ "tbird",
+ "should have existing tbird tag name prefilled"
+ );
+ Assert.equal(
+ dialogDocument.getElementById("tagColorPicker").value,
+ "#000080",
+ "should have existing tbird tag color prefilled"
+ );
+
+ EventUtils.sendString("-xx", dialogWindow); // => tbird-xx
+ // "#FFD700" == rgb(255, 215, 0);
+ dialogDocument.getElementById("tagColorPicker").value = "#FFD700";
+
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.querySelector("dialog").getButton("accept"),
+ {},
+ dialogWindow
+ );
+ await new Promise(r => setTimeout(r));
+ },
+ }
+ );
+
+ let editTagButton = prefsDocument.getElementById("editTagButton");
+ EventUtils.synthesizeMouseAtCenter(editTagButton, {}, prefsWindow);
+ await editTagDialogPromise;
+
+ Assert.ok(
+ tagList.querySelector(
+ 'richlistitem[value="tbird"] > label[value="tbird-xx"]'
+ ),
+ "tbird-xx tag should be in the list"
+ );
+ Assert.equal(
+ tagList.querySelector('richlistitem[value="tbird"]').style.color,
+ "rgb(255, 215, 0)",
+ "tbird-xx tag color should be correct"
+ );
+ Assert.equal(
+ tagList.querySelectorAll('richlistitem[value="tbird"]').length,
+ 1,
+ "tbird-xx tag should be in the list exactly once"
+ );
+
+ // And remove it.
+
+ EventUtils.synthesizeMouseAtCenter(
+ prefsDocument.getElementById("removeTagButton"),
+ {},
+ prefsWindow
+ );
+ await new Promise(r => setTimeout(r));
+
+ Assert.equal(
+ tagList.querySelector('richlistitem[value="tbird"]'),
+ null,
+ "tbird-xx (with key tbird) tag should have been removed from the list"
+ );
+
+ await closePrefsTab();
+});
diff --git a/comm/mail/components/preferences/test/browser/browser_openPreferences.js b/comm/mail/components/preferences/test/browser/browser_openPreferences.js
new file mode 100644
index 0000000000..01aceb085a
--- /dev/null
+++ b/comm/mail/components/preferences/test/browser/browser_openPreferences.js
@@ -0,0 +1,37 @@
+/* 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 getStoredLastSelected() {
+ return Services.xulStore.getValue(
+ "about:preferences",
+ "paneDeck",
+ "lastSelected"
+ );
+}
+
+add_task(async () => {
+ // Check that openPreferencesTab with no arguments and no stored value opens the first pane.
+ Services.xulStore.removeDocument("about:preferences");
+
+ let { prefsWindow } = await openNewPrefsTab();
+ is(prefsWindow.gLastCategory.category, "paneGeneral");
+
+ await closePrefsTab();
+});
+
+add_task(async () => {
+ // Check that openPreferencesTab with one argument opens the right pane…
+ Services.xulStore.removeDocument("about:preferences");
+
+ await openNewPrefsTab("panePrivacy");
+ is(getStoredLastSelected(), "panePrivacy");
+
+ await closePrefsTab();
+
+ // … even with a value in the XULStore.
+ await openNewPrefsTab("paneCompose");
+ is(getStoredLastSelected(), "paneCompose");
+
+ await closePrefsTab();
+});
diff --git a/comm/mail/components/preferences/test/browser/browser_privacy.js b/comm/mail/components/preferences/test/browser/browser_privacy.js
new file mode 100644
index 0000000000..1b91ec35e8
--- /dev/null
+++ b/comm/mail/components/preferences/test/browser/browser_privacy.js
@@ -0,0 +1,454 @@
+/* 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/. */
+
+add_task(async () => {
+ await testCheckboxes(
+ "panePrivacy",
+ "privacyCategory",
+ {
+ checkboxID: "acceptRemoteContent",
+ pref: "mailnews.message_display.disable_remote_image",
+ prefValues: [true, false],
+ },
+ {
+ checkboxID: "keepHistory",
+ pref: "places.history.enabled",
+ },
+ {
+ checkboxID: "acceptCookies",
+ pref: "network.cookie.cookieBehavior",
+ prefValues: [2, 0],
+ enabledElements: ["#acceptThirdPartyMenu"],
+ unaffectedElements: ["#cookieExceptions"],
+ },
+ {
+ checkboxID: "privacyDoNotTrackCheckbox",
+ pref: "privacy.donottrackheader.enabled",
+ }
+ );
+});
+
+add_task(async () => {
+ await testCheckboxes(
+ "panePrivacy",
+ "privacyJunkCategory",
+ {
+ checkboxID: "manualMark",
+ pref: "mail.spam.manualMark",
+ enabledElements: ["#manualMarkMode radio"],
+ },
+ {
+ checkboxID: "markAsReadOnSpam",
+ pref: "mail.spam.markAsReadOnSpam",
+ },
+ {
+ checkboxID: "enableJunkLogging",
+ pref: "mail.spam.logging.enabled",
+ enabledElements: ["#openJunkLogButton"],
+ }
+ );
+
+ await testCheckboxes("panePrivacy", "privacySecurityCategory", {
+ checkboxID: "enablePhishingDetector",
+ pref: "mail.phishing.detection.enabled",
+ });
+
+ await testCheckboxes("panePrivacy", "enableAntiVirusQuarantine", {
+ checkboxID: "enableAntiVirusQuarantine",
+ pref: "mailnews.downloadToTempFile",
+ });
+});
+
+add_task(async () => {
+ Services.prefs.setBoolPref("mail.spam.manualMark", true);
+
+ await testRadioButtons("panePrivacy", "privacyJunkCategory", {
+ pref: "mail.spam.manualMarkMode",
+ states: [
+ {
+ id: "manualMarkMode0",
+ prefValue: 0,
+ },
+ {
+ id: "manualMarkMode1",
+ prefValue: 1,
+ },
+ ],
+ });
+});
+
+add_task(async () => {
+ // Telemetry pref is locked.
+ // await testCheckboxes("paneAdvanced", undefined, {
+ // checkboxID: "submitTelemetryBox",
+ // pref: "toolkit.telemetry.enabled",
+ // });
+
+ await testCheckboxes("panePrivacy", "enableOCSP", {
+ checkboxID: "enableOCSP",
+ pref: "security.OCSP.enabled",
+ prefValues: [0, 1],
+ });
+});
+
+// Here we'd test the update choices, but I don't want to go near that.
+add_task(async () => {
+ await testRadioButtons("panePrivacy", "enableOCSP", {
+ pref: "security.default_personal_cert",
+ states: [
+ {
+ id: "certSelectionAuto",
+ prefValue: "Select Automatically",
+ },
+ {
+ id: "certSelectionAsk",
+ prefValue: "Ask Every Time",
+ },
+ ],
+ });
+});
+
+add_task(async function testRemoteContentDialog() {
+ const { prefsDocument, prefsWindow } = await openNewPrefsTab("panePrivacy");
+
+ let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ undefined,
+ "chrome://messenger/content/preferences/permissions.xhtml",
+ {
+ isSubDialog: true,
+ async callback(dialogWindow) {
+ await TestUtils.waitForCondition(
+ () => Services.focus.focusedWindow == dialogWindow,
+ "waiting for subdialog to be focused"
+ );
+
+ const dialogDocument = dialogWindow.document;
+ const url = dialogDocument.getElementById("url");
+ const permissionsTree =
+ dialogDocument.getElementById("permissionsTree");
+
+ EventUtils.sendString("accept.invalid", dialogWindow);
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("btnAllow"),
+ {},
+ dialogWindow
+ );
+ await new Promise(f => setTimeout(f));
+ Assert.equal(url.value, "", "url input should be cleared");
+ Assert.equal(
+ permissionsTree.view.rowCount,
+ 1,
+ "new entry should be added to list"
+ );
+
+ Assert.ok(
+ BrowserTestUtils.is_hidden(
+ dialogDocument.getElementById("btnSession")
+ ),
+ "session button should be hidden"
+ );
+
+ EventUtils.sendString("block.invalid", dialogWindow);
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("btnBlock"),
+ {},
+ dialogWindow
+ );
+ await new Promise(f => setTimeout(f));
+ Assert.equal(url.value, "", "url input should be cleared");
+ Assert.equal(
+ permissionsTree.view.rowCount,
+ 2,
+ "new entry should be added to list"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("btnApplyChanges"),
+ {},
+ dialogWindow
+ );
+ },
+ }
+ );
+ let remoteContentExceptions = prefsDocument.getElementById(
+ "remoteContentExceptions"
+ );
+ EventUtils.synthesizeMouseAtCenter(remoteContentExceptions, {}, prefsWindow);
+ await dialogPromise;
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ let acceptURI = Services.io.newURI("http://accept.invalid/");
+ let acceptPrincipal = Services.scriptSecurityManager.createContentPrincipal(
+ acceptURI,
+ {}
+ );
+ Assert.equal(
+ Services.perms.testPermissionFromPrincipal(acceptPrincipal, "image"),
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ "accept permission should exist for accept.invalid"
+ );
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ let blockURI = Services.io.newURI("http://block.invalid/");
+ let blockPrincipal = Services.scriptSecurityManager.createContentPrincipal(
+ blockURI,
+ {}
+ );
+ Assert.equal(
+ Services.perms.testPermissionFromPrincipal(blockPrincipal, "image"),
+ Ci.nsIPermissionManager.DENY_ACTION,
+ "block permission should exist for block.invalid"
+ );
+
+ dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ undefined,
+ "chrome://messenger/content/preferences/permissions.xhtml",
+ {
+ isSubDialog: true,
+ async callback(dialogWindow) {
+ await TestUtils.waitForCondition(
+ () => Services.focus.focusedWindow == dialogWindow,
+ "waiting for subdialog to be focused"
+ );
+
+ const dialogDocument = dialogWindow.document;
+ const permissionsTree =
+ dialogDocument.getElementById("permissionsTree");
+
+ Assert.equal(
+ permissionsTree.view.rowCount,
+ 2,
+ "list should be populated"
+ );
+
+ permissionsTree.view.selection.select(0);
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("removePermission"),
+ {},
+ dialogWindow
+ );
+ Assert.equal(
+ permissionsTree.view.rowCount,
+ 1,
+ "row should be removed from list"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("removeAllPermissions"),
+ {},
+ dialogWindow
+ );
+ Assert.equal(
+ permissionsTree.view.rowCount,
+ 0,
+ "row should be removed from list"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("btnApplyChanges"),
+ {},
+ dialogWindow
+ );
+ },
+ }
+ );
+ EventUtils.synthesizeMouseAtCenter(remoteContentExceptions, {}, prefsWindow);
+ await dialogPromise;
+
+ Assert.equal(
+ Services.perms.testPermissionFromPrincipal(acceptPrincipal, "image"),
+ Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ "permission should be removed for accept.invalid"
+ );
+ Assert.equal(
+ Services.perms.testPermissionFromPrincipal(blockPrincipal, "image"),
+ Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ "permission should be removed for block.invalid"
+ );
+
+ await closePrefsTab();
+});
+
+add_task(async function testCookiesDialog() {
+ const { prefsDocument, prefsWindow } = await openNewPrefsTab("panePrivacy");
+
+ let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ undefined,
+ "chrome://messenger/content/preferences/permissions.xhtml",
+ {
+ isSubDialog: true,
+ async callback(dialogWindow) {
+ await TestUtils.waitForCondition(
+ () => Services.focus.focusedWindow == dialogWindow,
+ "waiting for subdialog to be focused"
+ );
+
+ const dialogDocument = dialogWindow.document;
+ const url = dialogDocument.getElementById("url");
+ const permissionsTree =
+ dialogDocument.getElementById("permissionsTree");
+
+ EventUtils.sendString("accept.invalid", dialogWindow);
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("btnAllow"),
+ {},
+ dialogWindow
+ );
+ await new Promise(f => setTimeout(f));
+ Assert.equal(url.value, "", "url input should be cleared");
+ Assert.equal(
+ permissionsTree.view.rowCount,
+ 1,
+ "new entry should be added to list"
+ );
+
+ EventUtils.sendString("session.invalid", dialogWindow);
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("btnSession"),
+ {},
+ dialogWindow
+ );
+ await new Promise(f => setTimeout(f));
+ Assert.equal(url.value, "", "url input should be cleared");
+ Assert.equal(
+ permissionsTree.view.rowCount,
+ 2,
+ "new entry should be added to list"
+ );
+
+ EventUtils.sendString("block.invalid", dialogWindow);
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("btnBlock"),
+ {},
+ dialogWindow
+ );
+ await new Promise(f => setTimeout(f));
+ Assert.equal(url.value, "", "url input should be cleared");
+ Assert.equal(
+ permissionsTree.view.rowCount,
+ 3,
+ "new entry should be added to list"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("btnApplyChanges"),
+ {},
+ dialogWindow
+ );
+ },
+ }
+ );
+ let cookieExceptions = prefsDocument.getElementById("cookieExceptions");
+ EventUtils.synthesizeMouseAtCenter(cookieExceptions, {}, prefsWindow);
+ await dialogPromise;
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ let acceptURI = Services.io.newURI("http://accept.invalid/");
+ let acceptPrincipal = Services.scriptSecurityManager.createContentPrincipal(
+ acceptURI,
+ {}
+ );
+ Assert.equal(
+ Services.perms.testPermissionFromPrincipal(acceptPrincipal, "cookie"),
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ "accept permission should exist for accept.invalid"
+ );
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ let sessionURI = Services.io.newURI("http://session.invalid/");
+ let sessionPrincipal = Services.scriptSecurityManager.createContentPrincipal(
+ sessionURI,
+ {}
+ );
+ Assert.equal(
+ Services.perms.testPermissionFromPrincipal(sessionPrincipal, "cookie"),
+ Ci.nsICookiePermission.ACCESS_SESSION,
+ "session permission should exist for session.invalid"
+ );
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ let blockURI = Services.io.newURI("http://block.invalid/");
+ let blockPrincipal = Services.scriptSecurityManager.createContentPrincipal(
+ blockURI,
+ {}
+ );
+ Assert.equal(
+ Services.perms.testPermissionFromPrincipal(blockPrincipal, "cookie"),
+ Ci.nsIPermissionManager.DENY_ACTION,
+ "block permission should exist for block.invalid"
+ );
+
+ dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ undefined,
+ "chrome://messenger/content/preferences/permissions.xhtml",
+ {
+ isSubDialog: true,
+ async callback(dialogWindow) {
+ await TestUtils.waitForCondition(
+ () => Services.focus.focusedWindow == dialogWindow,
+ "waiting for subdialog to be focused"
+ );
+
+ const dialogDocument = dialogWindow.document;
+ const permissionsTree =
+ dialogDocument.getElementById("permissionsTree");
+
+ Assert.equal(
+ permissionsTree.view.rowCount,
+ 3,
+ "list should be populated"
+ );
+
+ permissionsTree.view.selection.select(0);
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("removePermission"),
+ {},
+ dialogWindow
+ );
+ Assert.equal(
+ permissionsTree.view.rowCount,
+ 2,
+ "row should be removed from list"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("removeAllPermissions"),
+ {},
+ dialogWindow
+ );
+ Assert.equal(
+ permissionsTree.view.rowCount,
+ 0,
+ "row should be removed from list"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.getElementById("btnApplyChanges"),
+ {},
+ dialogWindow
+ );
+ },
+ }
+ );
+ EventUtils.synthesizeMouseAtCenter(cookieExceptions, {}, prefsWindow);
+ await dialogPromise;
+
+ Assert.equal(
+ Services.perms.testPermissionFromPrincipal(acceptPrincipal, "cookie"),
+ Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ "permission should be removed for accept.invalid"
+ );
+ Assert.equal(
+ Services.perms.testPermissionFromPrincipal(sessionPrincipal, "cookie"),
+ Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ "permission should be removed for session.invalid"
+ );
+ Assert.equal(
+ Services.perms.testPermissionFromPrincipal(blockPrincipal, "cookie"),
+ Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ "permission should be removed for block.invalid"
+ );
+
+ await closePrefsTab();
+});
diff --git a/comm/mail/components/preferences/test/browser/browser_sync.js b/comm/mail/components/preferences/test/browser/browser_sync.js
new file mode 100644
index 0000000000..108695ebb5
--- /dev/null
+++ b/comm/mail/components/preferences/test/browser/browser_sync.js
@@ -0,0 +1,419 @@
+/* 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 { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+const { FxAccounts } = ChromeUtils.importESModule(
+ "resource://gre/modules/FxAccounts.sys.mjs"
+);
+FxAccounts.config.promiseConnectAccountURI = entryPoint =>
+ `https://example.org/?page=connect&entryPoint=${entryPoint}`;
+FxAccounts.config.promiseManageURI = entryPoint =>
+ `https://example.org/?page=manage&entryPoint=${entryPoint}`;
+FxAccounts.config.promiseChangeAvatarURI = entryPoint =>
+ `https://example.org/?page=avatar&entryPoint=${entryPoint}`;
+
+const ALL_ENGINES = [
+ "accounts",
+ "identities",
+ "addressbooks",
+ "calendars",
+ "passwords",
+];
+const PREF_PREFIX = "services.sync.engine";
+
+let prefsWindow, prefsDocument, tabmail;
+
+add_setup(async function () {
+ for (let engine of ALL_ENGINES) {
+ Services.prefs.setBoolPref(`${PREF_PREFIX}.${engine}`, true);
+ }
+
+ ({ prefsWindow, prefsDocument } = await openNewPrefsTab("paneSync"));
+ tabmail = document.getElementById("tabmail");
+
+ /** @implements {nsIExternalProtocolService} */
+ let mockExternalProtocolService = {
+ QueryInterface: ChromeUtils.generateQI(["nsIExternalProtocolService"]),
+ externalProtocolHandlerExists(protocolScheme) {},
+ isExposedProtocol(protocolScheme) {},
+ loadURI(uri, windowContext) {
+ Assert.report(
+ true,
+ undefined,
+ undefined,
+ `should not be opening ${uri.spec} in an external browser`
+ );
+ },
+ };
+
+ let mockExternalProtocolServiceCID = MockRegistrar.register(
+ "@mozilla.org/uriloader/external-protocol-service;1",
+ mockExternalProtocolService
+ );
+ registerCleanupFunction(() => {
+ MockRegistrar.unregister(mockExternalProtocolServiceCID);
+ });
+});
+
+add_task(async function testSectionStates() {
+ let noFxaAccount = prefsDocument.getElementById("noFxaAccount");
+ let hasFxaAccount = prefsDocument.getElementById("hasFxaAccount");
+ let accountStates = [noFxaAccount, hasFxaAccount];
+
+ let fxaLoginUnverified = prefsDocument.getElementById("fxaLoginUnverified");
+ let fxaLoginRejected = prefsDocument.getElementById("fxaLoginRejected");
+ let fxaLoginVerified = prefsDocument.getElementById("fxaLoginVerified");
+ let loginStates = [fxaLoginUnverified, fxaLoginRejected, fxaLoginVerified];
+
+ let fxaDeviceInfo = prefsDocument.getElementById("fxaDeviceInfo");
+ let syncConnected = prefsDocument.getElementById("syncConnected");
+ let syncDisconnected = prefsDocument.getElementById("syncDisconnected");
+ let syncStates = [syncConnected, syncDisconnected];
+
+ function assertStateVisible(states, visibleState) {
+ for (let state of states) {
+ let visible = BrowserTestUtils.is_visible(state);
+ Assert.equal(
+ visible,
+ state == visibleState,
+ `${state.id} should be ${state == visibleState ? "visible" : "hidden"}`
+ );
+ }
+ }
+
+ function checkStates({
+ accountState,
+ loginState = null,
+ deviceInfoVisible = false,
+ syncState = null,
+ }) {
+ prefsWindow.gSyncPane.updateWeavePrefs();
+ assertStateVisible(accountStates, accountState);
+ assertStateVisible(loginStates, loginState);
+ Assert.equal(
+ BrowserTestUtils.is_visible(fxaDeviceInfo),
+ deviceInfoVisible,
+ `fxaDeviceInfo should be ${deviceInfoVisible ? "visible" : "hidden"}`
+ );
+ assertStateVisible(syncStates, syncState);
+ }
+
+ async function assertTabOpens(target, expectedURL) {
+ if (typeof target == "string") {
+ target = prefsDocument.getElementById(target);
+ }
+
+ let tabPromise = BrowserTestUtils.waitForEvent(window, "TabOpen");
+ EventUtils.synthesizeMouseAtCenter(target, {}, prefsWindow);
+ await tabPromise;
+ let tab = tabmail.currentTabInfo;
+ await BrowserTestUtils.browserLoaded(tab.browser);
+ Assert.equal(
+ tab.browser.currentURI.spec,
+ `https://example.org/${expectedURL}`,
+ "a tab opened to the correct URL"
+ );
+ tabmail.closeTab(tab);
+ }
+
+ info("No account");
+ Assert.equal(prefsWindow.UIState.get().status, "not_configured");
+ checkStates({ accountState: noFxaAccount });
+
+ // Check clicking the Sign In button opens the connect page in a tab.
+ await assertTabOpens("noFxaSignIn", "?page=connect&entryPoint=");
+
+ // Override the window's UIState object with mock values.
+ let baseState = {
+ email: "test@invalid",
+ displayName: "Testy McTest",
+ avatarURL:
+ "https://example.org/browser/comm/mail/components/preferences/test/browser/files/avatar.png",
+ avatarIsDefault: false,
+ };
+ let mockState;
+ prefsWindow.UIState = {
+ ON_UPDATE: "sync-ui-state:update",
+ STATUS_LOGIN_FAILED: "login_failed",
+ STATUS_NOT_CONFIGURED: "not_configured",
+ STATUS_NOT_VERIFIED: "not_verified",
+ STATUS_SIGNED_IN: "signed_in",
+ get() {
+ return mockState;
+ },
+ };
+
+ info("Login not verified");
+ mockState = { ...baseState, status: "not_verified" };
+ checkStates({
+ accountState: hasFxaAccount,
+ loginState: fxaLoginUnverified,
+ deviceInfoVisible: true,
+ });
+ Assert.deepEqual(
+ await prefsDocument.l10n.getAttributes(
+ prefsDocument.getElementById("fxaAccountMailNotVerified")
+ ),
+ {
+ id: "sync-pane-email-not-verified",
+ args: { userEmail: "test@invalid" },
+ },
+ "email address set correctly"
+ );
+
+ // Untested: Resend and remove account buttons.
+
+ info("Login rejected");
+ mockState = { ...baseState, status: "login_failed" };
+ checkStates({
+ accountState: hasFxaAccount,
+ loginState: fxaLoginRejected,
+ deviceInfoVisible: true,
+ });
+ Assert.deepEqual(
+ await prefsDocument.l10n.getAttributes(
+ prefsDocument.getElementById("fxaAccountLoginRejected")
+ ),
+ {
+ id: "sync-signedin-login-failure",
+ args: { userEmail: "test@invalid" },
+ },
+ "email address set correctly"
+ );
+
+ // Untested: Sign in and remove account buttons.
+
+ info("Logged in, sync disabled");
+ mockState = { ...baseState, status: "verified", syncEnabled: false };
+ checkStates({
+ accountState: hasFxaAccount,
+ loginState: fxaLoginVerified,
+ deviceInfoVisible: true,
+ syncState: syncDisconnected,
+ });
+ let photo = fxaLoginVerified.querySelector(".contact-photo");
+ Assert.equal(
+ photo.src,
+ "https://example.org/browser/comm/mail/components/preferences/test/browser/files/avatar.png",
+ "avatar image set correctly"
+ );
+
+ // Check clicking the avatar image opens the avatar page in a tab.
+ await assertTabOpens(photo, "?page=avatar&entryPoint=preferences");
+
+ Assert.equal(
+ prefsDocument.getElementById("fxaDisplayName").textContent,
+ "Testy McTest",
+ "display name set correctly"
+ );
+ Assert.equal(
+ prefsDocument.getElementById("fxaEmailAddress").textContent,
+ "test@invalid",
+ "email address set correctly"
+ );
+
+ // Check clicking the management link opens the management page in a tab.
+ await assertTabOpens("verifiedManage", "?page=manage&entryPoint=preferences");
+
+ // Untested: Sign out button.
+
+ info("Device name section");
+ let deviceNameInput = prefsDocument.getElementById("fxaDeviceNameInput");
+ let deviceNameCancel = prefsDocument.getElementById("fxaDeviceNameCancel");
+ let deviceNameSave = prefsDocument.getElementById("fxaDeviceNameSave");
+ let deviceNameChange = prefsDocument.getElementById(
+ "fxaDeviceNameChangeDeviceName"
+ );
+ Assert.ok(deviceNameInput.readOnly, "input is read-only");
+ Assert.ok(
+ BrowserTestUtils.is_hidden(deviceNameCancel),
+ "cancel button is hidden"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_hidden(deviceNameSave),
+ "save button is hidden"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_visible(deviceNameChange),
+ "change button is visible"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(deviceNameChange, {}, prefsWindow);
+ Assert.ok(!deviceNameInput.readOnly, "input is writeable");
+ Assert.equal(prefsDocument.activeElement, deviceNameInput, "input is active");
+ Assert.ok(
+ BrowserTestUtils.is_visible(deviceNameCancel),
+ "cancel button is visible"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_visible(deviceNameSave),
+ "save button is visible"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_hidden(deviceNameChange),
+ "change button is hidden"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(deviceNameCancel, {}, prefsWindow);
+ Assert.ok(deviceNameInput.readOnly, "input is read-only");
+ Assert.ok(
+ BrowserTestUtils.is_hidden(deviceNameCancel),
+ "cancel button is hidden"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_hidden(deviceNameSave),
+ "save button is hidden"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_visible(deviceNameChange),
+ "change button is visible"
+ );
+
+ // Check the turn on sync button works.
+ await openEngineDialog({ expectEngines: ALL_ENGINES, button: "syncSetup" });
+
+ info("Logged in, sync enabled");
+ mockState = { ...baseState, status: "verified", syncEnabled: true };
+ checkStates({
+ accountState: hasFxaAccount,
+ loginState: fxaLoginVerified,
+ deviceInfoVisible: true,
+ syncState: syncConnected,
+ });
+
+ // Untested: Sync now button.
+
+ // Check the learn more link opens a tab.
+ await assertTabOpens("enginesLearnMore", "?page=learnMore");
+
+ // Untested: Disconnect button.
+});
+
+add_task(async function testEngines() {
+ function assertEnginesEnabled(...expectedEnabled) {
+ for (let engine of ALL_ENGINES) {
+ let enabled = Services.prefs.getBoolPref(`${PREF_PREFIX}.${engine}`);
+ Assert.equal(
+ enabled,
+ expectedEnabled.includes(engine),
+ `${engine} should be ${
+ expectedEnabled.includes(engine) ? "enabled" : "disabled"
+ }`
+ );
+ }
+ }
+
+ function assertEnginesShown(...expectEngines) {
+ let ENGINES_TO_ITEMS = {
+ accounts: "showSyncAccount",
+ identities: "showSyncIdentity",
+ addressbooks: "showSyncAddress",
+ calendars: "showSyncCalendar",
+ passwords: "showSyncPasswords",
+ };
+ let expectItems = expectEngines.map(engine => ENGINES_TO_ITEMS[engine]);
+ let items = Array.from(
+ prefsDocument.querySelectorAll("#showSyncedList > li:not([hidden])"),
+ li => li.id
+ );
+ Assert.deepEqual(items, expectItems, "enabled engines shown correctly");
+ }
+
+ assertEnginesShown(...ALL_ENGINES);
+ Services.prefs.setBoolPref(`${PREF_PREFIX}.accounts`, false);
+ assertEnginesShown("identities", "addressbooks", "calendars", "passwords");
+ Services.prefs.setBoolPref(`${PREF_PREFIX}.identities`, false);
+ Services.prefs.setBoolPref(`${PREF_PREFIX}.addressbooks`, false);
+ Services.prefs.setBoolPref(`${PREF_PREFIX}.calendars`, false);
+ assertEnginesShown("passwords");
+ Services.prefs.setBoolPref(`${PREF_PREFIX}.passwords`, false);
+ assertEnginesShown();
+
+ info("Checking the engine selection dialog");
+ await openEngineDialog({
+ toggleEngines: ["accounts", "identities", "passwords"],
+ });
+
+ assertEnginesEnabled("accounts", "identities", "passwords");
+ assertEnginesShown("accounts", "identities", "passwords");
+
+ await openEngineDialog({
+ expectEngines: ["accounts", "identities", "passwords"],
+ toggleEngines: ["calendars", "passwords"],
+ action: "cancel",
+ });
+
+ assertEnginesEnabled("accounts", "identities", "passwords");
+ assertEnginesShown("accounts", "identities", "passwords");
+
+ await openEngineDialog({
+ expectEngines: ["accounts", "identities", "passwords"],
+ toggleEngines: ["calendars", "passwords"],
+ action: "accept",
+ });
+
+ assertEnginesEnabled("accounts", "identities", "calendars");
+ assertEnginesShown("accounts", "identities", "calendars");
+
+ Services.prefs.setBoolPref(`${PREF_PREFIX}.addressbooks`, true);
+ Services.prefs.setBoolPref(`${PREF_PREFIX}.passwords`, true);
+ assertEnginesShown(...ALL_ENGINES);
+});
+
+async function openEngineDialog({
+ expectEngines = [],
+ toggleEngines = [],
+ action = "accept",
+ button = "syncChangeOptions",
+}) {
+ const ENGINES_TO_CHECKBOXES = {
+ accounts: "configSyncAccount",
+ identities: "configSyncIdentity",
+ addressbooks: "configSyncAddress",
+ calendars: "configSyncCalendar",
+ passwords: "configSyncPasswords",
+ };
+ let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ undefined,
+ "chrome://messenger/content/preferences/syncDialog.xhtml",
+ { isSubDialog: true }
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ prefsDocument.getElementById(button),
+ {},
+ prefsWindow
+ );
+ let dialogWindow = await dialogPromise;
+ let dialogDocument = dialogWindow.document;
+ await new Promise(resolve => dialogWindow.setTimeout(resolve));
+
+ let expectItems = expectEngines.map(engine => ENGINES_TO_CHECKBOXES[engine]);
+
+ let checkedItems = Array.from(
+ dialogDocument.querySelectorAll(`input[type="checkbox"]`)
+ )
+ .filter(cb => cb.checked)
+ .map(cb => cb.id);
+ Assert.deepEqual(
+ checkedItems,
+ expectItems,
+ "enabled engines checked correctly"
+ );
+
+ for (let toggleItem of toggleEngines) {
+ let checkbox = dialogDocument.getElementById(
+ ENGINES_TO_CHECKBOXES[toggleItem]
+ );
+ checkbox.checked = !checkbox.checked;
+ }
+
+ EventUtils.synthesizeMouseAtCenter(
+ dialogDocument.querySelector("dialog").getButton(action),
+ {},
+ dialogWindow
+ );
+}
diff --git a/comm/mail/components/preferences/test/browser/files/avatar.png b/comm/mail/components/preferences/test/browser/files/avatar.png
new file mode 100644
index 0000000000..ca0894316a
--- /dev/null
+++ b/comm/mail/components/preferences/test/browser/files/avatar.png
Binary files differ
diff --git a/comm/mail/components/preferences/test/browser/files/icon.svg b/comm/mail/components/preferences/test/browser/files/icon.svg
new file mode 100644
index 0000000000..6c1a552445
--- /dev/null
+++ b/comm/mail/components/preferences/test/browser/files/icon.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
+ <circle cx="8" cy="8" r="7.5" fill="#ffffff" stroke="#00aa00" stroke-width="1.5"/>
+ <circle cx="5" cy="6" r="1.5" fill="#00aa00"/>
+ <circle cx="11" cy="6" r="1.5" fill="#00aa00"/>
+ <path d="M 12.83,9.30 C 12.24,11.48 10.26,13 8,13 5.75,13 3.74,11.48 3.17,9.29" fill="none" stroke="#00aa00" stroke-width="1.5"/>
+</svg>
diff --git a/comm/mail/components/preferences/test/browser/files/management.html b/comm/mail/components/preferences/test/browser/files/management.html
new file mode 100644
index 0000000000..7e3561d823
--- /dev/null
+++ b/comm/mail/components/preferences/test/browser/files/management.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8"/>
+ <title></title>
+</head>
+<body>
+ <a id="a" href="https://www.example.com/">Click me!</a>
+</body>
+</html>
diff --git a/comm/mail/components/preferences/test/browser/head.js b/comm/mail/components/preferences/test/browser/head.js
new file mode 100644
index 0000000000..12cbdb17f1
--- /dev/null
+++ b/comm/mail/components/preferences/test/browser/head.js
@@ -0,0 +1,314 @@
+/* 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/. */
+
+/* import-globals-from ../../../../base/content/utilityOverlay.js */
+
+async function openNewPrefsTab(paneID, scrollPaneTo, otherArgs) {
+ let tabmail = document.getElementById("tabmail");
+ let prefsTabMode = tabmail.tabModes.preferencesTab;
+
+ is(prefsTabMode.tabs.length, 0, "Prefs tab is not open");
+
+ let prefsDocument = await new Promise(resolve => {
+ Services.obs.addObserver(function documentLoaded(subject) {
+ if (subject.URL.startsWith("about:preferences")) {
+ Services.obs.removeObserver(documentLoaded, "chrome-document-loaded");
+ subject.ownerGlobal.setTimeout(() => resolve(subject));
+ }
+ }, "chrome-document-loaded");
+ openPreferencesTab(paneID, scrollPaneTo, otherArgs);
+ });
+ ok(prefsDocument.URL.startsWith("about:preferences"), "Prefs tab is open");
+
+ prefsDocument = prefsTabMode.tabs[0].browser.contentDocument;
+ let prefsWindow = prefsDocument.ownerGlobal;
+ prefsWindow.resizeTo(screen.availWidth, screen.availHeight);
+
+ if (paneID) {
+ await new Promise(resolve => prefsWindow.setTimeout(resolve));
+ is(
+ prefsWindow.gLastCategory.category,
+ paneID,
+ `Selected pane is ${paneID}`
+ );
+ } else {
+ // If we don't wait here for other scripts to run, they
+ // could be in a bad state if our test closes the tab.
+ await new Promise(resolve => prefsWindow.setTimeout(resolve));
+ }
+
+ registerCleanupOnce();
+
+ await new Promise(resolve => prefsWindow.setTimeout(resolve));
+ let container = prefsDocument.getElementById("preferencesContainer");
+ if (scrollPaneTo && container.scrollHeight > container.clientHeight) {
+ Assert.greater(
+ container.scrollTop,
+ 0,
+ "Prefs page did scroll when it was supposed to"
+ );
+ }
+ return { prefsDocument, prefsWindow };
+}
+
+async function openExistingPrefsTab(paneID, scrollPaneTo, otherArgs) {
+ let tabmail = document.getElementById("tabmail");
+ let prefsTabMode = tabmail.tabModes.preferencesTab;
+
+ is(prefsTabMode.tabs.length, 1, "Prefs tab is open");
+
+ let prefsDocument = prefsTabMode.tabs[0].browser.contentDocument;
+ let prefsWindow = prefsDocument.ownerGlobal;
+ prefsWindow.resizeTo(screen.availWidth, screen.availHeight);
+
+ if (paneID && prefsWindow.gLastCategory.category != paneID) {
+ openPreferencesTab(paneID, scrollPaneTo, otherArgs);
+ }
+
+ await new Promise(resolve => prefsWindow.setTimeout(resolve));
+ is(prefsWindow.gLastCategory.category, paneID, `Selected pane is ${paneID}`);
+
+ if (scrollPaneTo) {
+ Assert.greater(
+ prefsDocument.getElementById("preferencesContainer").scrollTop,
+ 0,
+ "Prefs page did scroll when it was supposed to"
+ );
+ }
+
+ registerCleanupOnce();
+ return { prefsDocument, prefsWindow };
+}
+
+function registerCleanupOnce() {
+ if (registerCleanupOnce.alreadyRegistered) {
+ return;
+ }
+ registerCleanupFunction(closePrefsTab);
+ registerCleanupOnce.alreadyRegistered = true;
+}
+
+async function closePrefsTab() {
+ info("Closing prefs tab");
+ let tabmail = document.getElementById("tabmail");
+ let prefsTab = tabmail.tabModes.preferencesTab.tabs[0];
+ if (prefsTab) {
+ tabmail.closeTab(prefsTab);
+ }
+}
+
+/**
+ * Tests a checkbox sets the preference is set in the right state when the preferences tab opens,
+ * that the preference it relates to is set properly, and any UI elements that should be disabled
+ * by it are disabled.
+ *
+ * Each of the tests arguments is an object describing a test, containing:
+ * checkboxID - the ID of the checkbox to test
+ * pref - the name of a preference,
+ * prefValues - an array of two values: pref value when not checked, pref value when checked
+ * (optional, defaults to [false, true])
+ * enabledElements - an array of CSS selectors (optional)
+ * enabledInverted - if the elements should be disabled when the checkbox is checked (optional)
+ * unaffectedElements - array of CSS selectors that should not be affected by
+ * the toggling of the checkbox.
+ */
+async function testCheckboxes(paneID, scrollPaneTo, ...tests) {
+ for (let initiallyChecked of [true, false]) {
+ info(`Opening ${paneID} with prefs set to ${initiallyChecked}`);
+
+ for (let test of tests) {
+ let wantedValue = initiallyChecked;
+ if (test.prefValues) {
+ wantedValue = wantedValue ? test.prefValues[1] : test.prefValues[0];
+ }
+ if (typeof wantedValue == "number") {
+ Services.prefs.setIntPref(test.pref, wantedValue);
+ } else {
+ Services.prefs.setBoolPref(test.pref, wantedValue);
+ }
+ }
+
+ let { prefsDocument, prefsWindow } = await openNewPrefsTab(
+ paneID,
+ scrollPaneTo
+ );
+
+ let testUIState = function (test, checked) {
+ let wantedValue = checked;
+ if (test.prefValues) {
+ wantedValue = wantedValue ? test.prefValues[1] : test.prefValues[0];
+ }
+ let checkbox = prefsDocument.getElementById(test.checkboxID);
+ is(
+ checkbox.checked,
+ checked,
+ wantedValue,
+ "Checkbox " + (checked ? "is" : "isn't") + " checked"
+ );
+ if (typeof wantedValue == "number") {
+ is(
+ Services.prefs.getIntPref(test.pref, -999),
+ wantedValue,
+ `Pref is ${wantedValue}`
+ );
+ } else {
+ is(
+ Services.prefs.getBoolPref(test.pref),
+ wantedValue,
+ `Pref is ${wantedValue}`
+ );
+ }
+
+ if (test.enabledElements) {
+ let disabled = checked;
+ if (test.enabledInverted) {
+ disabled = !disabled;
+ }
+ for (let selector of test.enabledElements) {
+ let elements = prefsDocument.querySelectorAll(selector);
+ ok(
+ elements.length >= 1,
+ `At least one element matched '${selector}'`
+ );
+ for (let element of elements) {
+ is(
+ element.disabled,
+ !disabled,
+ "Element " + (disabled ? "isn't" : "is") + " disabled"
+ );
+ }
+ }
+ }
+ };
+
+ let testUnaffected = function (ids, states) {
+ ids.forEach((sel, index) => {
+ let isOk = prefsDocument.querySelector(sel).disabled === states[index];
+ is(isOk, true, `Element "${sel}" is unaffected`);
+ });
+ };
+
+ for (let test of tests) {
+ info(`Checking ${test.checkboxID}`);
+
+ let unaffectedSelectors = test.unaffectedElements || [];
+ let unaffectedStates = unaffectedSelectors.map(
+ sel => prefsDocument.querySelector(sel).disabled
+ );
+
+ let checkbox = prefsDocument.getElementById(test.checkboxID);
+ checkbox.scrollIntoView(false);
+ testUIState(test, initiallyChecked);
+
+ EventUtils.synthesizeMouseAtCenter(checkbox, {}, prefsWindow);
+ testUIState(test, !initiallyChecked);
+ testUnaffected(unaffectedSelectors, unaffectedStates);
+
+ EventUtils.synthesizeMouseAtCenter(checkbox, {}, prefsWindow);
+ testUIState(test, initiallyChecked);
+ testUnaffected(unaffectedSelectors, unaffectedStates);
+ }
+
+ await closePrefsTab();
+ }
+}
+
+/**
+ * Tests a set of radio buttons is in the right state when the preferences tab opens, and when
+ * the selected button changes that the preference it relates to is set properly, and any related
+ * UI elements that should be disabled are disabled.
+ *
+ * Each of the tests arguments is an object describing a test, containing:
+ * pref - the name of an integer preference,
+ * states - an array with each element describing a radio button:
+ * id - the ID of the button to test,
+ * prefValue - the value the pref should be set to
+ * enabledElements - an array of CSS selectors to elements that should be enabled when this
+ * radio button is selected (optional)
+ */
+async function testRadioButtons(paneID, scrollPaneTo, ...tests) {
+ for (let { pref, states } of tests) {
+ for (let initialState of states) {
+ info(`Opening ${paneID} with ${pref} set to ${initialState.prefValue}`);
+
+ if (typeof initialState.prefValue == "number") {
+ Services.prefs.setIntPref(pref, initialState.prefValue);
+ } else if (typeof initialState.prefValue == "boolean") {
+ Services.prefs.setBoolPref(pref, initialState.prefValue);
+ } else {
+ Services.prefs.setCharPref(pref, initialState.prefValue);
+ }
+
+ let { prefsDocument, prefsWindow } = await openNewPrefsTab(
+ paneID,
+ scrollPaneTo
+ );
+
+ let testUIState = function (currentState) {
+ info(`Testing with ${pref} set to ${currentState.prefValue}`);
+ for (let state of states) {
+ let isCurrentState = state == currentState;
+ let radio = prefsDocument.getElementById(state.id);
+ is(radio.selected, isCurrentState, `${state.id}.selected`);
+
+ if (state.enabledElements) {
+ for (let selector of state.enabledElements) {
+ let elements = prefsDocument.querySelectorAll(selector);
+ ok(
+ elements.length >= 1,
+ `At least one element matched '${selector}'`
+ );
+ for (let element of elements) {
+ is(
+ element.disabled,
+ !isCurrentState,
+ "Element " + (isCurrentState ? "isn't" : "is") + " disabled"
+ );
+ }
+ }
+ }
+ }
+ if (typeof initialState.prefValue == "number") {
+ is(
+ Services.prefs.getIntPref(pref, -999),
+ currentState.prefValue,
+ `Pref is ${currentState.prefValue}`
+ );
+ } else if (typeof initialState.prefValue == "boolean") {
+ is(
+ Services.prefs.getBoolPref(pref),
+ currentState.prefValue,
+ `Pref is ${currentState.prefValue}`
+ );
+ } else {
+ is(
+ Services.prefs.getCharPref(pref, "FAKE VALUE"),
+ currentState.prefValue,
+ `Pref is ${currentState.prefValue}`
+ );
+ }
+ };
+
+ // Check the initial setup is correct.
+ testUIState(initialState);
+ // Cycle through possible values, checking each one.
+ for (let state of states) {
+ if (state == initialState) {
+ continue;
+ }
+ let radio = prefsDocument.getElementById(state.id);
+ radio.scrollIntoView(false);
+ EventUtils.synthesizeMouseAtCenter(radio, {}, prefsWindow);
+ testUIState(state);
+ }
+ // Go back to the initial value.
+ let initialRadio = prefsDocument.getElementById(initialState.id);
+ initialRadio.scrollIntoView(false);
+ EventUtils.synthesizeMouseAtCenter(initialRadio, {}, prefsWindow);
+ testUIState(initialState);
+
+ await closePrefsTab();
+ }
+ }
+}