summaryrefslogtreecommitdiffstats
path: root/comm/mail/test/browser/message-header
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/test/browser/message-header')
-rw-r--r--comm/mail/test/browser/message-header/browser.ini18
-rw-r--r--comm/mail/test/browser/message-header/browser_messageHeader.js1237
-rw-r--r--comm/mail/test/browser/message-header/browser_messageHeaderCustomize.js388
-rw-r--r--comm/mail/test/browser/message-header/browser_phishingBar.js307
-rw-r--r--comm/mail/test/browser/message-header/browser_replyIdentity.js231
-rw-r--r--comm/mail/test/browser/message-header/browser_replyToListFromAddressSelection.js121
-rw-r--r--comm/mail/test/browser/message-header/browser_returnReceipt.js208
-rw-r--r--comm/mail/test/browser/message-header/data/evil-attached.eml22
-rw-r--r--comm/mail/test/browser/message-header/data/evil.eml7
-rw-r--r--comm/mail/test/browser/message-header/head.js190
10 files changed, 2729 insertions, 0 deletions
diff --git a/comm/mail/test/browser/message-header/browser.ini b/comm/mail/test/browser/message-header/browser.ini
new file mode 100644
index 0000000000..786f724775
--- /dev/null
+++ b/comm/mail/test/browser/message-header/browser.ini
@@ -0,0 +1,18 @@
+[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
+ datareporting.policy.dataSubmissionPolicyBypassNotification=true
+subsuite = thunderbird
+support-files = data/**
+
+[browser_messageHeader.js]
+[browser_messageHeaderCustomize.js]
+[browser_phishingBar.js]
+[browser_replyIdentity.js]
+[browser_replyToListFromAddressSelection.js]
+[browser_returnReceipt.js]
diff --git a/comm/mail/test/browser/message-header/browser_messageHeader.js b/comm/mail/test/browser/message-header/browser_messageHeader.js
new file mode 100644
index 0000000000..f198a36f3f
--- /dev/null
+++ b/comm/mail/test/browser/message-header/browser_messageHeader.js
@@ -0,0 +1,1237 @@
+/* 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/. */
+
+/**
+ * Test functionality in the message header,
+ */
+
+"use strict";
+
+var {
+ create_address_book,
+ create_mailing_list,
+ ensure_no_card_exists,
+ get_cards_in_all_address_books_for_email,
+ get_mailing_list_from_address_book,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/AddressBookHelpers.jsm"
+);
+var { wait_for_content_tab_load } = ChromeUtils.import(
+ "resource://testing-common/mozmill/ContentTabHelpers.jsm"
+);
+var {
+ add_message_to_folder,
+ assert_selected_and_displayed,
+ be_in_folder,
+ close_popup,
+ create_folder,
+ create_message,
+ gDefaultWindowHeight,
+ get_smart_folder_named,
+ get_about_3pane,
+ get_about_message,
+ inboxFolder,
+ mc,
+ msgGen,
+ restore_default_window_size,
+ select_click_row,
+ select_none,
+ wait_for_message_display_completion,
+ wait_for_popup_to_open,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { element_visible_recursive } = ChromeUtils.import(
+ "resource://testing-common/mozmill/DOMHelpers.jsm"
+);
+var { resize_to } = ChromeUtils.import(
+ "resource://testing-common/mozmill/WindowHelpers.jsm"
+);
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+let about3Pane = get_about_3pane();
+let aboutMessage = get_about_message();
+
+const LINES_PREF = "mailnews.headers.show_n_lines_before_more";
+
+// Used to get the accessible object for a DOM node.
+var gAccService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+);
+
+var folder;
+var folderMore;
+var gInterestingMessage;
+
+add_setup(async function () {
+ folder = await create_folder("MessageWindowA");
+ folderMore = await create_folder("MesageHeaderMoreButton");
+
+ // Create a message that has the interesting headers that commonly shows up in
+ // the message header pane for testing.
+ gInterestingMessage = create_message({
+ cc: msgGen.makeNamesAndAddresses(20),
+ subject:
+ "This is a really, really, really, really, really, really, really, really, long subject.",
+ clobberHeaders: {
+ Newsgroups: "alt.test",
+ "Reply-To": "J. Doe <j.doe@momo.invalid>",
+ "Content-Base": "https://example.com/",
+ Bcc: "Richard Roe <richard.roe@momo.invalid>",
+ },
+ });
+
+ await add_message_to_folder([folder], gInterestingMessage);
+
+ // Create a message that has multiple to and cc addresses.
+ let msgMore1 = create_message({
+ to: msgGen.makeNamesAndAddresses(40),
+ cc: msgGen.makeNamesAndAddresses(40),
+ });
+ await add_message_to_folder([folderMore], msgMore1);
+
+ // Create a message that has multiple to and cc addresses.
+ let msgMore2 = create_message({
+ to: msgGen.makeNamesAndAddresses(20),
+ cc: msgGen.makeNamesAndAddresses(20),
+ });
+ await add_message_to_folder([folderMore], msgMore2);
+
+ // Create a regular message with one recipient.
+ let msg = create_message();
+ await add_message_to_folder([folder], msg);
+
+ // Some of these tests critically depends on the window width, collapse
+ // everything that might be in the way.
+ mc.window.document.getElementById(
+ "tabmail"
+ ).currentTabInfo.folderPaneVisible = false;
+
+ // Disable animations on the panel, so that we don't have to deal with
+ // async openings. The panel is lazy-loaded, so it needs to be referenced
+ // this way rather than finding it in the DOM.
+ aboutMessage.editContactInlineUI.panel.setAttribute("animate", false);
+ await ensure_table_view();
+
+ registerCleanupFunction(async () => {
+ await ensure_cards_view();
+ // Delete created folder.
+ folder.deleteSelf(null);
+ folderMore.deleteSelf(null);
+
+ // Restore animation to the contact panel.
+ aboutMessage.document
+ .getElementById("editContactPanel")
+ .removeAttribute("animate");
+ });
+});
+
+/**
+ * Helper function that takes an array of header recipients elements and
+ * returns the last one in the list that is not hidden. Returns null if no
+ * such element exists.
+ *
+ * @param {HTMLOListElement} recipientsList - The list element containing all
+ * recipient addresses.
+ */
+function get_last_visible_address(recipientsList) {
+ let last = recipientsList.childNodes[recipientsList.childNodes.length - 1];
+ // Avoid returning the "more" button.
+ if (last.classList.contains("show-more-recipients")) {
+ return recipientsList.childNodes[recipientsList.childNodes.length - 2];
+ }
+ return last;
+}
+
+add_task(async function test_add_tag_with_really_long_label() {
+ await be_in_folder(folder);
+ await ensure_table_view();
+
+ // Select the first message, which will display it.
+ let curMessage = select_click_row(0);
+
+ assert_selected_and_displayed(mc, curMessage);
+
+ let topLabel = aboutMessage.document.getElementById("expandedfromLabel");
+ let bottomLabel = aboutMessage.document.getElementById(
+ "expandedsubjectLabel"
+ );
+ if (topLabel.clientWidth != bottomLabel.clientWidth) {
+ throw new Error(
+ `Header columns have different widths! ${topLabel.clientWidth} != ${bottomLabel.clientWidth}`
+ );
+ }
+ let defaultWidth = topLabel.clientWidth;
+
+ // Make the tags label really long.
+ let tagsLabel = aboutMessage.document.getElementById("expandedtagsLabel");
+ let oldTagsValue = tagsLabel.value;
+ tagsLabel.value = "taaaaaaaaaaaaaaaaaags";
+ if (topLabel.clientWidth != bottomLabel.clientWidth) {
+ tagsLabel.value = oldTagsValue;
+ throw new Error(
+ `Header columns have different widths! ${topLabel.clientWidth} != ${bottomLabel.clientWidth}`
+ );
+ }
+
+ if (topLabel.clientWidth != defaultWidth) {
+ tagsLabel.value = oldTagsValue;
+ throw new Error(
+ `Header columns changed width! ${topLabel.clientWidth} != ${defaultWidth}`
+ );
+ }
+
+ let fromRow = aboutMessage.document.getElementById("expandedfromRow");
+ // Add the first tag, and make sure that the label are the same length.
+ fromRow.focus();
+ EventUtils.synthesizeKey("1", {}, aboutMessage);
+ if (topLabel.clientWidth != bottomLabel.clientWidth) {
+ tagsLabel.value = oldTagsValue;
+ throw new Error(
+ `Header columns have different widths! ${topLabel.clientWidth} != ${bottomLabel.clientWidth}`
+ );
+ }
+
+ if (topLabel.clientWidth == defaultWidth) {
+ tagsLabel.value = oldTagsValue;
+ throw new Error(
+ `Header columns didn't change width! ${topLabel.clientWidth} == ${defaultWidth}`
+ );
+ }
+
+ // Remove the tag and put it back so that the a11y label gets regenerated
+ // with the normal value rather than "taaaaaaaags".
+ tagsLabel.value = oldTagsValue;
+ fromRow.focus();
+ EventUtils.synthesizeKey("1", {}, aboutMessage);
+ fromRow.focus();
+ EventUtils.synthesizeKey("1", {}, aboutMessage);
+}).skip();
+
+/**
+ * Data and methods for a space.
+ *
+ * @typedef {object} HeaderInfo
+ * @property {string} name - Used for pretty-printing in exceptions.
+ * @property {Function} element - A callback returning the DOM
+ * element with the data.
+ * @property {Function} expectedName - A callback returning the expected value
+ * of the accessible name of the DOM element.
+ */
+/**
+ * List all header rows that we want to test.
+ *
+ * @type {HeaderInfo[]}
+ */
+const headersToTest = [
+ {
+ name: "Subject",
+ element() {
+ return aboutMessage.document.getElementById("expandedsubjectBox");
+ },
+ expectedName(element) {
+ return `${
+ aboutMessage.document.getElementById("expandedsubjectLabel").value
+ }: ${element.value.textContent}`;
+ },
+ },
+ {
+ name: "Content-Base",
+ element() {
+ return aboutMessage.document.getElementById("expandedcontent-baseBox");
+ },
+ expectedName(element) {
+ return `${
+ aboutMessage.document.getElementById("expandedcontent-baseLabel").value
+ }: ${element.value.textContent}`;
+ },
+ },
+];
+
+/**
+ * Use the information from HeaderInfo to verify that screen readers will do the
+ * right thing with the given message header.
+ *
+ * @param {HeaderInfo} header - The HeaderInfo data type object.
+ */
+async function verify_header_a11y(header) {
+ let element = header.element();
+ Assert.notEqual(
+ element,
+ null,
+ `element not found for header '${header.name}'`
+ );
+
+ let headerAccessible;
+ await TestUtils.waitForCondition(
+ () => (headerAccessible = gAccService.getAccessibleFor(element)) != null,
+ `didn't find accessible element for header '${header.name}'`
+ );
+
+ let expectedName = header.expectedName(element);
+ Assert.equal(
+ headerAccessible.name,
+ expectedName,
+ `headerAccessible.name for ${header.name} ` +
+ `was '${headerAccessible.name}'; expected '${expectedName}'`
+ );
+}
+
+/**
+ * Test the accessibility attributes of the various message headers.
+ *
+ * INFO: The gInterestingMessage has no tags until after
+ * test_add_tag_with_really_long_label, so ensure it runs after that one.
+ */
+add_task(async function test_a11y_attrs() {
+ await be_in_folder(folder);
+ // Convert the SyntheticMessage gInterestingMessage into an actual nsIMsgDBHdr
+ // XPCOM message.
+ let hdr = folder.msgDatabase.getMsgHdrForMessageID(
+ gInterestingMessage.messageId
+ );
+ // Select and open the interesting message.
+ let curMessage = select_click_row(
+ about3Pane.gDBView.findIndexOfMsgHdr(hdr, false)
+ );
+ // Make sure it loads.
+ assert_selected_and_displayed(mc, curMessage);
+ // Test all the headers with this message.
+ headersToTest.forEach(verify_header_a11y);
+});
+
+/**
+ * Test the keyboard accessibility of the toolbarbuttons on the message header.
+ */
+add_task(async function enter_msg_hdr_toolbar() {
+ await be_in_folder(folder);
+ // Convert the SyntheticMessage gInterestingMessage into an actual nsIMsgDBHdr
+ // XPCOM message.
+ let hdr = folder.msgDatabase.getMsgHdrForMessageID(
+ gInterestingMessage.messageId
+ );
+ // Select and open the interesting message.
+ let curMessage = select_click_row(
+ about3Pane.gDBView.findIndexOfMsgHdr(hdr, false)
+ );
+ // Make sure it loads.
+ assert_selected_and_displayed(mc, curMessage);
+
+ const BUTTONS_SELECTOR = `toolbarbutton:not([hidden="true"],[is="toolbarbutton-menu-button"]), toolbaritem[id="hdrSmartReplyButton"]>toolbarbutton:not([hidden="true"])>dropmarker, button:not([hidden])`;
+ let headerToolbar = aboutMessage.document.getElementById(
+ "header-view-toolbar"
+ );
+ let headerButtons = headerToolbar.querySelectorAll(BUTTONS_SELECTOR);
+
+ // Create an array of all the menu popups on message header.
+ let msgHdrMenupopups = headerToolbar.querySelectorAll(".no-icon-menupopup");
+ let menupopupToOpen, msgHdrActiveElement;
+
+ // Press tab while on the message selected.
+ EventUtils.synthesizeKey("KEY_Tab", {}, about3Pane);
+ Assert.equal(
+ headerButtons[0].id,
+ aboutMessage.document.activeElement.id,
+ "focused on first msgHdr toolbar button"
+ );
+
+ // Simulate the Arrow Right keypress to make sure the correct button gets the
+ // focus.
+ for (let i = 1; i < headerButtons.length; i++) {
+ let previousElement = document.activeElement;
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, about3Pane);
+ Assert.equal(
+ aboutMessage.document.activeElement.id,
+ headerButtons[i].id,
+ "The next button is focused"
+ );
+ Assert.ok(
+ aboutMessage.document.activeElement.tabIndex == 0 &&
+ previousElement.tabIndex == -1,
+ "The roving tab index was updated"
+ );
+ msgHdrActiveElement = aboutMessage.document.activeElement;
+
+ // Simulate Enter and Space keypress events to ensure the menus in the
+ // message header buttons area are keyboard accessible.
+ if (
+ msgHdrActiveElement.hasAttribute("type") &&
+ msgHdrActiveElement.getAttribute("type") == "menu"
+ ) {
+ let parentID = msgHdrActiveElement.parentElement.id;
+ for (let menupopup of msgHdrMenupopups) {
+ if (
+ menupopup.id.replace("Dropdown", "") ==
+ parentID.replace("Button", "") ||
+ menupopup.id.replace("Popup", "") ==
+ msgHdrActiveElement.id.replace("Button", "")
+ ) {
+ menupopupToOpen = menupopup;
+ }
+ }
+
+ let menupopupOpenEnterPromise = BrowserTestUtils.waitForEvent(
+ menupopupToOpen,
+ "popupshown"
+ );
+ EventUtils.synthesizeKey("KEY_Enter", {}, about3Pane);
+ await menupopupOpenEnterPromise;
+
+ let menupopupClosePromise = BrowserTestUtils.waitForEvent(
+ menupopupToOpen,
+ "popuphidden"
+ );
+ EventUtils.synthesizeKey("KEY_Escape", {}, about3Pane);
+ await menupopupClosePromise;
+
+ Assert.equal(
+ msgHdrActiveElement.id,
+ headerButtons[i].id,
+ "The correct button is focused"
+ );
+
+ let menupopupOpenSpacePromise = BrowserTestUtils.waitForEvent(
+ menupopupToOpen,
+ "popupshown"
+ );
+ // Simulate Space keypress.
+ EventUtils.synthesizeKey(" ", {}, about3Pane);
+ await menupopupOpenSpacePromise;
+
+ EventUtils.synthesizeKey("KEY_Escape", {}, about3Pane);
+ await menupopupClosePromise;
+
+ Assert.equal(
+ msgHdrActiveElement.id,
+ headerButtons[i].id,
+ "The correct button is focused after opening and closing the menupopup"
+ );
+ }
+ }
+
+ // Simulate the Arrow Left keypress to make sure the correct button gets the
+ // focus.
+ for (let i = headerButtons.length - 2; i > -1; i--) {
+ let previousElement = document.activeElement;
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, about3Pane);
+ Assert.equal(
+ aboutMessage.document.activeElement.id,
+ headerButtons[i].id,
+ "The previous button is focused"
+ );
+ Assert.ok(
+ aboutMessage.document.activeElement.tabIndex == 0 &&
+ previousElement.tabIndex == -1,
+ "The roving tab index was updated"
+ );
+ }
+ EventUtils.synthesizeKey("KEY_Tab", {}, about3Pane);
+ Assert.equal(
+ aboutMessage.document.activeElement.id,
+ "fromRecipient0",
+ "The sender is now focused"
+ );
+}).__skipMe = AppConstants.platform == "macosx";
+
+// Full keyboard navigation on OSX only works if Full Keyboard Access setting is
+// set to All Control in System Keyboard Preferences. This also works with the
+// setting, Keyboard > Keyboard navigation, in addition to
+// Accessibility > Keyboard > Full Keyboard Access.
+
+add_task(function test_more_button_with_many_recipients() {
+ // Start on the interesting message.
+ let curMessage = select_click_row(0);
+
+ // Make sure it loads.
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+
+ // Click on the "more" button.
+ EventUtils.synthesizeMouseAtCenter(
+ aboutMessage.document.getElementById("expandedccBox").moreButton,
+ {},
+ aboutMessage
+ );
+
+ let msgHeader = aboutMessage.document.getElementById("messageHeader");
+ // Check that the message header can scroll to fit all recipients.
+ Assert.ok(
+ msgHeader.classList.contains("scrollable"),
+ "The message header is scrollable"
+ );
+
+ // Switch to the boring message, to force the more button to collapse.
+ curMessage = select_click_row(1);
+
+ // Make sure it loads.
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+
+ // Check that the message header is not scrollable anymore
+ Assert.notEqual(
+ msgHeader.classList.contains("scrollable"),
+ "The message header is not scrollable"
+ );
+});
+
+/**
+ * Test that clicking the add to address book button updates the UI properly.
+ *
+ * @param {HTMLOListElement} recipientsList
+ */
+function subtest_more_widget_ab_button_click(recipientsList) {
+ let recipient = get_last_visible_address(recipientsList);
+ ensure_no_card_exists(recipient.emailAddress);
+
+ // Scroll to the bottom first so the address is in view.
+ let view = aboutMessage.document.getElementById("messageHeader");
+ view.scrollTop = view.scrollHeight - view.clientHeight;
+
+ EventUtils.synthesizeMouseAtCenter(recipient.abIndicator, {}, aboutMessage);
+
+ Assert.ok(
+ recipient.abIndicator.classList.contains("in-address-book"),
+ "The recipient was added to the Address Book"
+ );
+}
+
+/**
+ * Test that we can open up the inline contact editor when we
+ * click on the address book button.
+ */
+add_task(async function test_clicking_ab_button_opens_inline_contact_editor() {
+ // Make sure we're in the right folder.
+ await be_in_folder(folder);
+ // Add a new message.
+ let msg = create_message();
+ await add_message_to_folder([folder], msg);
+ // Open the latest message.
+ select_click_row(-1);
+ wait_for_message_display_completion(mc);
+
+ // Ensure that the inline contact editing panel is not open
+ let contactPanel = aboutMessage.document.getElementById("editContactPanel");
+ Assert.notEqual(contactPanel.state, "open");
+
+ let recipientsList =
+ aboutMessage.document.getElementById("expandedtoBox").recipientsList;
+ subtest_more_widget_ab_button_click(recipientsList);
+
+ // Ok, if we're here, then the star has been clicked, and
+ // the contact has been added to our AB.
+ let recipient = get_last_visible_address(recipientsList);
+
+ let panelOpened = TestUtils.waitForCondition(
+ () => contactPanel.state == "open",
+ "The contactPanel was opened"
+ );
+ // Click on the star, and ensure that the inline contact editing panel opens.
+ EventUtils.synthesizeMouseAtCenter(recipient.abIndicator, {}, aboutMessage);
+ await panelOpened;
+
+ EventUtils.synthesizeMouseAtCenter(
+ aboutMessage.document.getElementById("editContactPanelEditDetailsButton"),
+ {},
+ aboutMessage
+ );
+ wait_for_content_tab_load(undefined, "about:addressbook");
+ // TODO check the card.
+ mc.window.document.getElementById("tabmail").closeTab();
+});
+
+/**
+ * Test that clicking references context menu works properly.
+ */
+add_task(async function test_msg_id_context_menu() {
+ Services.prefs.setBoolPref("mailnews.headers.showReferences", true);
+
+ // Add a new message.
+ let msg = create_message({
+ clobberHeaders: {
+ References:
+ "<4880C986@example.com> <4880CAB2@example.com> <4880CC76@example.com>",
+ },
+ });
+ await add_message_to_folder([folder], msg);
+ await be_in_folder(folder);
+
+ // Open the latest message.
+ select_click_row(-1);
+
+ // Right click to show the context menu.
+ EventUtils.synthesizeMouseAtCenter(
+ aboutMessage.document.querySelector(
+ "#expandedreferencesBox .header-message-id"
+ ),
+ { type: "contextmenu" },
+ aboutMessage
+ );
+ await wait_for_popup_to_open(
+ aboutMessage.document.getElementById("messageIdContext")
+ );
+
+ // Ensure Open Message For ID is shown and that Open Browser With Message-ID
+ // isn't shown.
+ Assert.ok(
+ !aboutMessage.document.getElementById(
+ "messageIdContext-openMessageForMsgId"
+ ).hidden,
+ "The menu item is hidden"
+ );
+ Assert.ok(
+ aboutMessage.document.getElementById(
+ "messageIdContext-openBrowserWithMsgId"
+ ).hidden,
+ "The menu item is visible"
+ );
+
+ await close_popup(
+ mc,
+ aboutMessage.document.getElementById("messageIdContext")
+ );
+
+ // Reset the preferences.
+ Services.prefs.setBoolPref("mailnews.headers.showReferences", false);
+});
+
+/**
+ * Test that if a contact belongs to a mailing list within their address book,
+ * then the inline contact editor will not allow the user to change what address
+ * book the contact belongs to. The editor should also show a message to explain
+ * why the contact cannot be moved.
+ */
+add_task(
+ async function test_address_book_switch_disabled_on_contact_in_mailing_list() {
+ const MAILING_LIST_DIRNAME = "Some Mailing List";
+ const ADDRESS_BOOK_NAME = "Some Address Book";
+ // Add a new message.
+ let msg = create_message();
+ await add_message_to_folder([folder], msg);
+
+ // Make sure we're in the right folder.
+ await be_in_folder(folder);
+
+ // Open the latest message.
+ select_click_row(-1);
+
+ // Ensure that the inline contact editing panel is not open
+ let contactPanel = aboutMessage.document.getElementById("editContactPanel");
+ Assert.notEqual(contactPanel.state, "open");
+
+ let recipientsList =
+ aboutMessage.document.getElementById("expandedtoBox").recipientsList;
+ subtest_more_widget_ab_button_click(recipientsList);
+
+ // Ok, if we're here, then the star has been clicked, and
+ // the contact has been added to our AB.
+ let recipient = get_last_visible_address(recipientsList);
+
+ let panelOpened = TestUtils.waitForCondition(
+ () => contactPanel.state == "open",
+ "The contactPanel was opened"
+ );
+ // Click on the address book button, and ensure that the inline contact
+ // editing panel opens.
+ EventUtils.synthesizeMouseAtCenter(recipient.abIndicator, {}, aboutMessage);
+ await panelOpened;
+
+ let abDrop = aboutMessage.document.getElementById(
+ "editContactAddressBookList"
+ );
+ // Ensure that the address book dropdown is not disabled
+ Assert.ok(!abDrop.disabled);
+
+ let warningMsg = aboutMessage.document.getElementById(
+ "contactMoveDisabledText"
+ );
+ // We should not be displaying any warning
+ Assert.ok(warningMsg.hidden);
+
+ // Now close the popup.
+ contactPanel.hidePopup();
+
+ // For the contact that was added, create a mailing list in the address book
+ // it resides in, and then add that contact to the mailing list.
+ let cards = get_cards_in_all_address_books_for_email(
+ recipient.emailAddress
+ );
+
+ // There should be only one copy of this email address in the address books.
+ Assert.equal(cards.length, 1);
+
+ // Remove the card from any of the address books.s
+ ensure_no_card_exists(recipient.emailAddress);
+
+ // Add the card to a new address book, and insert it into a mailing list
+ // under that address book.
+ let ab = create_address_book(ADDRESS_BOOK_NAME);
+ ab.dropCard(cards[0], false);
+ let ml = create_mailing_list(MAILING_LIST_DIRNAME);
+ ab.addMailList(ml);
+
+ // Now we have to retrieve the mailing list from the address book, in order
+ // for us to add and delete cards from it.
+ ml = get_mailing_list_from_address_book(ab, MAILING_LIST_DIRNAME);
+ ml.addCard(cards[0]);
+
+ // Click on the address book button, and ensure that the inline contact
+ // editing panel opens.
+ EventUtils.synthesizeMouseAtCenter(recipient.abIndicator, {}, aboutMessage);
+ await panelOpened;
+
+ // The dropdown should be disabled now
+ Assert.ok(abDrop.disabled);
+ // We should be displaying a warning
+ Assert.ok(!warningMsg.hidden);
+
+ contactPanel.hidePopup();
+
+ // And if we remove the contact from the mailing list, the warning should be
+ // gone and the address book switching menu re-enabled.
+ ml.deleteCards([cards[0]]);
+
+ // Click on the address book button, and ensure that the inline contact
+ // editing panel opens.
+ EventUtils.synthesizeMouseAtCenter(recipient.abIndicator, {}, aboutMessage);
+ await panelOpened;
+
+ // Ensure that the address book dropdown is not disabled
+ Assert.ok(!abDrop.disabled);
+ // We should not be displaying any warning
+ Assert.ok(warningMsg.hidden);
+
+ contactPanel.hidePopup();
+ }
+);
+
+/**
+ * Test that clicking the adding an address node adds it to the address book.
+ */
+add_task(async function test_add_contact_from_context_menu() {
+ let popup = aboutMessage.document.getElementById("emailAddressPopup");
+ let popupShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ // Click the contact to show the emailAddressPopup popup menu.
+ let recipient = aboutMessage.document.querySelector(
+ "#expandedfromBox .header-recipient"
+ );
+ EventUtils.synthesizeMouseAtCenter(recipient, {}, aboutMessage);
+ await popupShown;
+
+ const addToAddressBookItem = aboutMessage.document.getElementById(
+ "addToAddressBookItem"
+ );
+ Assert.ok(!addToAddressBookItem.hidden, "addToAddressBookItem is not hidden");
+
+ const editContactItem =
+ aboutMessage.document.getElementById("editContactItem");
+ Assert.ok(editContactItem.hidden, "editContactItem is hidden");
+
+ let recipientAdded = TestUtils.waitForCondition(
+ () => recipient.abIndicator.classList.contains("in-address-book"),
+ "The recipient was added to the address book"
+ );
+
+ // Click the Add to Address Book context menu entry.
+ // NOTE: Use activateItem because macOS uses native context menus.
+ popup.activateItem(addToAddressBookItem);
+ // (for reasons unknown, the pop-up does not close itself)
+ await close_popup(mc, popup);
+ await recipientAdded;
+
+ // NOTE: We need to redefine these selectors otherwise the popup will not
+ // properly close for some reason.
+ let popup2 = aboutMessage.document.getElementById("emailAddressPopup");
+ let popupShown2 = BrowserTestUtils.waitForEvent(popup, "popupshown");
+
+ // Now click the contact again, the context menu should now show the Edit
+ // Contact menu instead.
+ EventUtils.synthesizeMouseAtCenter(recipient, {}, aboutMessage);
+ await popupShown2;
+ // (for reasons unknown, the pop-up does not close itself)
+ await close_popup(mc, popup2);
+
+ Assert.ok(addToAddressBookItem.hidden, "addToAddressBookItem is hidden");
+ Assert.ok(!editContactItem.hidden, "editContactItem is not hidden");
+});
+
+add_task(async function test_that_msg_without_date_clears_previous_headers() {
+ await be_in_folder(folder);
+
+ // Create a message with a descriptive subject.
+ let msg = create_message({ subject: "this is without date" });
+
+ // Ensure that this message doesn't have a Date header.
+ delete msg.headers.Date;
+
+ // Sdd the message to the end of the folder.
+ await add_message_to_folder([folder], msg);
+
+ // Not the first anymore. The timestamp is that of "NOW".
+ // Select and open the LAST message.
+ let curMessage = select_click_row(-1);
+
+ // Make sure it loads.
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+
+ // Since we didn't give create_message an argument that would create a
+ // Newsgroups header, the newsgroups <row> element should be collapsed.
+ // However, since the previously displayed message _did_ have such a header,
+ // certain bugs in the display of this header could cause the collapse
+ // never to have happened.
+ Assert.ok(
+ aboutMessage.document.getElementById("expandednewsgroupsRow").hidden,
+ "The Newsgroups header row is hidden."
+ );
+});
+
+/**
+ * Get the number of lines in one of the multi-recipient-row fields.
+ *
+ * @param {HTMLOListElement} node - The recipients container of a header row.
+ * @returns {int} - The number of rows.
+ */
+function help_get_num_lines(node) {
+ let style = getComputedStyle(node.firstElementChild);
+ return Math.round(
+ parseFloat(getComputedStyle(node).height) /
+ parseFloat(style.height + style.paddingTop + style.paddingBottom)
+ );
+}
+
+/**
+ * Test that the "more" button displays when it should.
+ *
+ * @param {HTMLOListElement} node - The recipients container of a header row.
+ * @param {boolean} [showAll=false] - If we're currently showing all the
+ * recipients.
+ */
+async function subtest_more_widget_display(node, showAll = false) {
+ // Test that the `To` element doesn't have more than max lines.
+ let numLines = help_get_num_lines(node);
+ // Get the max line pref.
+ let maxLines = Services.prefs.getIntPref(LINES_PREF);
+
+ if (showAll) {
+ await BrowserTestUtils.waitForCondition(
+ () => numLines > maxLines,
+ `Currently visible lines are more than the number of max lines. ${numLines} > ${maxLines}`
+ );
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ !aboutMessage.document
+ .getElementById("expandedtoBox")
+ .querySelector(".show-more-recipients"),
+ "The `more` button doesn't exist."
+ );
+ } else {
+ await BrowserTestUtils.waitForCondition(
+ () => numLines <= maxLines,
+ `Currently visible lines are fewer than the number of max lines. ${numLines} <= ${maxLines}`
+ );
+ // Test that we've got a "more" button and that it's visible.
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ !aboutMessage.document.getElementById("expandedtoBox").moreButton
+ .hidden,
+ "The `more` button is visible."
+ );
+ }
+}
+
+/**
+ * Test that activating the "more" button displays all the addresses.
+ *
+ * @param {HTMLOListElement} node - The recipients container of a header row.
+ */
+function subtest_more_widget_activate(node) {
+ let oldNumLines = help_get_num_lines(node);
+
+ let moreButton = node.querySelector(".show-more-recipients");
+ Assert.ok(moreButton, "The more button should exist");
+ moreButton.focus();
+ // Activate the "more" button.
+ EventUtils.synthesizeKey("KEY_Enter", {}, aboutMessage);
+
+ // Make sure that the "more" button was removed when showing all addresses.
+ Assert.ok(
+ !node.querySelector(".show-more-recipients"),
+ "The more button should not exist anymore."
+ );
+
+ // Test that we actually have more lines than we did before!
+ let newNumLines = help_get_num_lines(node);
+ Assert.greater(
+ newNumLines,
+ oldNumLines,
+ "Number of address lines present increases after more click"
+ );
+}
+
+/**
+ * Test the behavior of the "more" button.
+ */
+add_task(async function test_view_more_button() {
+ // Generate message with 35 recipients to guarantee overflow.
+ await be_in_folder(folder);
+ let msg = create_message({
+ toCount: 35,
+ subject: "Many To addresses to test_more_widget",
+ });
+
+ // Add the message to the end of the folder.
+ await add_message_to_folder([folder], msg);
+
+ // Select and open the injected message.
+ // It is at the second last message in the display list.
+ let curMessage = select_click_row(-2);
+ // FIXME: Switch between a couple of messages to allow the UI to properly
+ // refresh and fetch the proper recipients row width in order to avoid an
+ // unexpected recipients wrapping. This happens because the width calculation
+ // happens before the message header layout is fully generated.
+ let prevMessage = select_click_row(-3);
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, prevMessage);
+
+ curMessage = select_click_row(-2);
+
+ // Make sure it loads.
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+
+ // Get the sender address.
+ let node =
+ aboutMessage.document.getElementById("expandedtoBox").recipientsList;
+ await subtest_more_widget_display(node);
+ subtest_more_widget_activate(node);
+});
+
+/**
+ * Test the focus behavior when activating the more button.
+ */
+add_task(async function test_view_more_button_focus() {
+ // Generate message with 35 recipients to guarantee overflow.
+ await be_in_folder(folder);
+ let msg = create_message({
+ toCount: 35,
+ subject: "Test more button focus",
+ });
+
+ // Add the message to the end of the folder.
+ await add_message_to_folder([folder], msg);
+
+ for (let { focusMore, useKeyboard } of [
+ { focusMore: true, useKeyboard: false },
+ { focusMore: true, useKeyboard: true },
+ { focusMore: false, useKeyboard: false },
+ ]) {
+ // Reload the message.
+ let prevMessage = select_click_row(-1);
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, prevMessage);
+
+ let curMessage = select_click_row(-2);
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+
+ let items = [
+ ...aboutMessage.document.querySelectorAll(
+ "#expandedtoBox .recipients-list li"
+ ),
+ ];
+ Assert.greater(items.length, 2, "Should have enough items for the test");
+ let moreButton = aboutMessage.document.querySelector(
+ "#expandedtoBox .show-more-recipients"
+ );
+ Assert.ok(moreButton, "The more button should exist");
+ Assert.ok(
+ items[items.length - 1].contains(moreButton),
+ "More button should be the final button in the list"
+ );
+ let index;
+ if (focusMore) {
+ index = items.length - 1;
+ moreButton.focus();
+ Assert.ok(
+ moreButton.matches(":focus"),
+ "The more button can receive focus"
+ );
+ } else {
+ index = 1;
+ items[1].focus();
+ Assert.ok(
+ items[1].matches(":focus"),
+ "The second item can receive focus"
+ );
+ }
+ if (useKeyboard) {
+ EventUtils.synthesizeKey("KEY_Enter", {}, aboutMessage);
+ } else {
+ EventUtils.synthesizeMouseAtCenter(moreButton, {}, aboutMessage);
+ }
+
+ Assert.ok(
+ !aboutMessage.document.querySelector(
+ "#expandedtoBox .show-more-recipients"
+ ),
+ "The more button should be removed"
+ );
+ items = [
+ ...aboutMessage.document.querySelectorAll(
+ "#expandedtoBox .recipients-list li"
+ ),
+ ];
+ Assert.ok(
+ items[index].matches(":focus"),
+ `The focus should be on item ${index}`
+ );
+ }
+});
+
+/**
+ * Test that all addresses are shown in show all header mode.
+ */
+add_task(async function test_show_all_header_mode() {
+ async function toggle_header_mode(show) {
+ let popup = document.getElementById("otherActionsPopup");
+ let popupShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("otherActionsButton"),
+ {},
+ mc.window
+ );
+ await popupShown;
+
+ let panel = document.getElementById("messageHeaderCustomizationPanel");
+ let customizeBtn = document.getElementById(
+ "messageHeaderMoreMenuCustomize"
+ );
+ let panelShown = BrowserTestUtils.waitForEvent(panel, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(customizeBtn, {}, mc.window);
+ await panelShown;
+
+ let viewAllHeaders = document.getElementById("headerViewAllHeaders");
+
+ let modeChanged = await TestUtils.waitForCondition(
+ () =>
+ document
+ .getElementById("messageHeader")
+ .getAttribute("show_header_mode") == show
+ ? "all"
+ : "normal",
+ "Message header updated correctly"
+ );
+ EventUtils.synthesizeMouseAtCenter(viewAllHeaders, {}, mc.window);
+ await modeChanged;
+
+ Assert.ok(
+ viewAllHeaders.checked == show,
+ "The view all headers checkbox was updated to the correct state"
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ aboutMessage.document.getElementById("expandedsubjectBox").value
+ .textContent,
+ "The message was loaded"
+ );
+
+ let panelHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ await panelHidden;
+ }
+
+ // Generate message with 35 recipients.
+ await be_in_folder(folder);
+ let msg = create_message({
+ toCount: 35,
+ subject: "many To addresses for test_show_all_header_mode",
+ });
+
+ // Add the message to the end of the folder.
+ await add_message_to_folder([folder], msg);
+
+ // Select and open the added message.
+ // It is at the second last position in the display list.
+ let curMessage = select_click_row(-2);
+
+ // Make sure it loads.
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+
+ await toggle_header_mode(true);
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+ let node =
+ aboutMessage.document.getElementById("expandedtoBox").recipientsList;
+ await subtest_more_widget_display(node, true);
+
+ await toggle_header_mode(false);
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+ await subtest_more_widget_display(node);
+ subtest_more_widget_activate(node);
+ await subtest_more_widget_display(node, true);
+}).skip();
+
+async function help_test_starred_messages() {
+ await be_in_folder(folder);
+
+ // Select the last message, which will display it.
+ let curMessage = select_click_row(-1);
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+
+ let starButton = aboutMessage.document.getElementById("starMessageButton");
+ // The message shouldn't be starred.
+ Assert.ok(
+ !starButton.classList.contains("flagged"),
+ "The message is not starred"
+ );
+
+ // Press s to mark the message as starred.
+ EventUtils.synthesizeKey("s", {}, aboutMessage);
+ // The message should be starred.
+ Assert.ok(starButton.classList.contains("flagged"), "The message is starred");
+
+ // Click on the star button.
+ EventUtils.synthesizeMouseAtCenter(starButton, {}, aboutMessage);
+ // The message shouldn't be starred.
+ Assert.ok(
+ !starButton.classList.contains("flagged"),
+ "The message is not starred"
+ );
+
+ // Click again on the star button.
+ EventUtils.synthesizeMouseAtCenter(starButton, {}, aboutMessage);
+ // The message should be starred.
+ Assert.ok(starButton.classList.contains("flagged"), "The message is starred");
+
+ // Select the first message.
+ curMessage = select_click_row(0);
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+
+ // The newly selected message shouldn't be starred.
+ Assert.ok(
+ !starButton.classList.contains("flagged"),
+ "The message is not starred"
+ );
+
+ // Select again the last message.
+ curMessage = select_click_row(-1);
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+
+ // The message should still be starred.
+ Assert.ok(starButton.classList.contains("flagged"), "The message is starred");
+
+ let hdr = folder.msgDatabase.getMsgHdrForMessageID(curMessage.messageId);
+ // Update the starred state not through a click on the star button, to make
+ // sure the method works.
+ hdr.markFlagged(false);
+ // The message should be starred.
+ Assert.ok(
+ !starButton.classList.contains("flagged"),
+ "The message is not starred"
+ );
+}
+
+/**
+ * Test the marking of a message as starred, be sure the header is properly
+ * updated and changing selected message doesn't affect the state of others.
+ */
+add_task(async function test_starred_message() {
+ await help_test_starred_messages();
+});
+
+add_task(async function test_starred_message_unified_mode() {
+ mc.window.document.getElementById(
+ "tabmail"
+ ).currentTabInfo.folderPaneVisible = true;
+ select_none();
+ // Show the "Unified" folders view.
+ mc.folderTreeView.activeModes = "smart";
+ // Hide the all folders view. The activeModes setter takes care of removing
+ // the mode is is already visible.
+ mc.folderTreeView.activeModes = "all";
+
+ await help_test_starred_messages();
+
+ mc.window.document.getElementById(
+ "tabmail"
+ ).currentTabInfo.folderPaneVisible = false;
+ select_none();
+ // Show the "All" folders view.
+ mc.folderTreeView.activeModes = "all";
+ // Hide the "Unified" folders view. The activeModes setter takes care of
+ // removing the mode is is already visible.
+ mc.folderTreeView.activeModes = "smart";
+}).skip();
+/**
+ * Test the DBListener to be sure is initialized and cleared when needed, and it
+ * doesn't change when not needed.
+ */
+add_task(async function test_folder_db_listener() {
+ await be_in_folder(folderMore);
+ // Select the last message, which will display it.
+ let curMessage = select_click_row(-1);
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+
+ Assert.ok(
+ aboutMessage.gFolderDBListener.isRegistered,
+ "The folder DB listener was initialized"
+ );
+ Assert.equal(
+ folderMore,
+ aboutMessage.gFolderDBListener.selectedFolder,
+ "The current folder was stored correctly"
+ );
+
+ // Keep a reference before it gets cleared.
+ let gFolderDBRef = aboutMessage.gFolderDBListener;
+
+ // Collapse the message pane.
+ aboutMessage.HideMessageHeaderPane();
+
+ Assert.ok(!gFolderDBRef.isRegistered, "The folder DB listener was cleared");
+ Assert.equal(
+ folderMore,
+ gFolderDBRef.selectedFolder,
+ "The current folder wasn't cleared and is still the same"
+ );
+
+ // Change folder
+ await be_in_folder(folder);
+
+ // Select the last message, which will display it.
+ curMessage = select_click_row(-1);
+ wait_for_message_display_completion(mc);
+ assert_selected_and_displayed(mc, curMessage);
+
+ Assert.ok(
+ aboutMessage.gFolderDBListener?.isRegistered,
+ "The folder DB listener was initialized"
+ );
+ Assert.equal(
+ folder,
+ aboutMessage.gFolderDBListener.selectedFolder,
+ "The current folder was stored correctly"
+ );
+});
+
+/**
+ * Remove the reference to the accessibility service so that it stops observing
+ * vsync notifications at the end of the test.
+ */
+add_task(function cleanup() {
+ gAccService = null;
+ // The actual reference to the XPCOM object will be dropped at the next GC,
+ // so force one to happen immediately.
+ Cu.forceGC();
+});
diff --git a/comm/mail/test/browser/message-header/browser_messageHeaderCustomize.js b/comm/mail/test/browser/message-header/browser_messageHeaderCustomize.js
new file mode 100644
index 0000000000..67ca412587
--- /dev/null
+++ b/comm/mail/test/browser/message-header/browser_messageHeaderCustomize.js
@@ -0,0 +1,388 @@
+/* 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/. */
+
+/**
+ * Test the message header customization features.
+ */
+var {
+ add_message_to_folder,
+ assert_selected_and_displayed,
+ be_in_folder,
+ close_popup,
+ create_folder,
+ create_message,
+ gDefaultWindowHeight,
+ get_smart_folder_named,
+ get_about_3pane,
+ get_about_message,
+ inboxFolder,
+ mc,
+ msgGen,
+ restore_default_window_size,
+ select_click_row,
+ select_none,
+ wait_for_message_display_completion,
+ wait_for_popup_to_open,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+let about3Pane = get_about_3pane();
+let aboutMessage = get_about_message();
+
+var { MailTelemetryForTests } = ChromeUtils.import(
+ "resource:///modules/MailGlue.jsm"
+);
+var { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+var gFolder;
+
+add_setup(async function () {
+ Services.xulStore.removeDocument(
+ "chrome://messenger/content/messenger.xhtml"
+ );
+ Services.telemetry.clearScalars();
+
+ let account = createAccount();
+ gFolder = await createSubfolder(account.incomingServer.rootFolder, "test0");
+ createMessages(gFolder, 1);
+
+ registerCleanupFunction(() => {
+ gFolder.deleteSelf(null);
+ MailServices.accounts.removeAccount(account, true);
+ Services.xulStore.removeDocument(
+ "chrome://messenger/content/messenger.xhtml"
+ );
+ });
+});
+
+add_task(async function test_customize_toolbar_buttons() {
+ be_in_folder(gFolder);
+ select_click_row(0);
+
+ let moreBtn = aboutMessage.document.getElementById("otherActionsButton");
+ // Make sure we loaded the expected message.
+ await assertVisibility(moreBtn, true, "The more button is visible");
+
+ // Confirm we're starting from a clean state.
+ let header = aboutMessage.document.getElementById("messageHeader");
+ Assert.ok(
+ header.classList.contains("message-header-show-recipient-avatar"),
+ "The From recipient is showing the avatar"
+ );
+ let avatar = aboutMessage.document.querySelector(".recipient-avatar");
+ await assertVisibility(avatar, true, "The recipient avatar is shown");
+
+ Assert.ok(
+ header.classList.contains("message-header-show-sender-full-address"),
+ "The From recipient is showing the full address on two lines"
+ );
+ let multiLine = aboutMessage.document.querySelector(".recipient-multi-line");
+ await assertVisibility(
+ multiLine,
+ true,
+ "The recipient multi line is visible"
+ );
+ let singleLine = aboutMessage.document.querySelector(
+ ".recipient-single-line"
+ );
+ await assertVisibility(
+ singleLine,
+ false,
+ "he recipient single line is hidden"
+ );
+
+ Assert.ok(
+ header.classList.contains("message-header-hide-label-column"),
+ "The labels column is hidden"
+ );
+
+ let firstLabel = aboutMessage.document.querySelector(".message-header-label");
+ Assert.equal(
+ firstLabel.style.minWidth,
+ "0px",
+ "The first label has no min-width value"
+ );
+ await assertVisibility(firstLabel, false, "The labels column is hidden");
+
+ Assert.ok(
+ header.classList.contains("message-header-large-subject"),
+ "The message header has a large subject"
+ );
+ Assert.ok(
+ !header.classList.contains("message-header-buttons-only-icons"),
+ "The message header buttons aren't showing only icons"
+ );
+ Assert.ok(
+ !header.classList.contains("message-header-buttons-only-text"),
+ "The message header buttons aren't showing only text"
+ );
+
+ MailTelemetryForTests.reportUIConfiguration();
+ let scalarName = "tb.ui.configuration.message_header";
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertScalarUnset(scalars, scalarName);
+
+ let popup = aboutMessage.document.getElementById("otherActionsPopup");
+ let popupShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(moreBtn, {}, aboutMessage);
+ await popupShown;
+
+ let panel = aboutMessage.document.getElementById(
+ "messageHeaderCustomizationPanel"
+ );
+ let customizeBtn = aboutMessage.document.getElementById(
+ "messageHeaderMoreMenuCustomize"
+ );
+ let panelShown = BrowserTestUtils.waitForEvent(panel, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(customizeBtn, {}, aboutMessage);
+ await panelShown;
+
+ let buttonStyle = aboutMessage.document.getElementById("headerButtonStyle");
+ // Assert the options are in a default state.
+ Assert.equal(
+ buttonStyle.value,
+ "default",
+ "The buttons style is in the default state"
+ );
+ let subjectLarge = aboutMessage.document.getElementById("headerSubjectLarge");
+ Assert.ok(subjectLarge.checked, "The subject field is in the default state");
+
+ let showAvatar = aboutMessage.document.getElementById("headerShowAvatar");
+ Assert.ok(
+ showAvatar.checked,
+ "The show avatar field is in the default state"
+ );
+
+ let showFullAddress = aboutMessage.document.getElementById(
+ "headerShowFullAddress"
+ );
+ Assert.ok(
+ showFullAddress.checked,
+ "The show full address field is in the default state"
+ );
+
+ let hideLabels = aboutMessage.document.getElementById("headerHideLabels");
+ Assert.ok(
+ hideLabels.checked,
+ "The hide labels field is in the default state"
+ );
+
+ let openMenuPopup = async function () {
+ aboutMessage.document.getElementById("headerButtonStyle").focus();
+
+ let menuPopupShown = BrowserTestUtils.waitForEvent(
+ aboutMessage.document.querySelector("#headerButtonStyle menupopup"),
+ "popupshown"
+ );
+ // Use the keyboard to open and cycle through the menulist items because the
+ // mouse events are unreliable in tests.
+ EventUtils.synthesizeMouseAtCenter(
+ aboutMessage.document.getElementById("headerButtonStyle"),
+ {},
+ aboutMessage
+ );
+ await menuPopupShown;
+ };
+
+ // Cycle through the buttons style and confirm the style is properly applied.
+ // Use the keyboard to open and cycle through the menulist items because the
+ // mouse events are unreliable in tests.
+ await openMenuPopup();
+ EventUtils.sendKey("down", aboutMessage);
+ EventUtils.sendKey("return", aboutMessage);
+
+ await BrowserTestUtils.waitForCondition(
+ () => header.classList.contains("message-header-buttons-only-text"),
+ "The buttons are showing only text"
+ );
+ Assert.ok(
+ header.classList.contains("message-header-large-subject"),
+ "The subject line wasn't changed"
+ );
+ Assert.ok(
+ header.classList.contains("message-header-show-recipient-avatar"),
+ "The avatar visibility wasn't changed"
+ );
+ Assert.ok(
+ header.classList.contains("message-header-show-sender-full-address"),
+ "The full address visibility wasn't changed"
+ );
+ Assert.ok(
+ header.classList.contains("message-header-hide-label-column"),
+ "The labels column visibility wasn't changed"
+ );
+
+ await openMenuPopup();
+ EventUtils.sendKey("down", aboutMessage);
+ EventUtils.sendKey("return", aboutMessage);
+ await BrowserTestUtils.waitForCondition(
+ () => header.classList.contains("message-header-buttons-only-icons"),
+ "The buttons are showing only icons"
+ );
+ Assert.ok(
+ header.classList.contains("message-header-large-subject"),
+ "The subject line wasn't changed"
+ );
+
+ await openMenuPopup();
+ EventUtils.sendKey("up", aboutMessage);
+ EventUtils.sendKey("up", aboutMessage);
+ EventUtils.sendKey("return", aboutMessage);
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ !header.classList.contains("message-header-buttons-only-icons") &&
+ !header.classList.contains("message-header-buttons-only-text") &&
+ header.classList.contains("message-header-large-subject") &&
+ header.classList.contains("message-header-show-recipient-avatar") &&
+ header.classList.contains("message-header-show-sender-full-address") &&
+ header.classList.contains("message-header-hide-label-column"),
+ "The message header is clear of any custom style"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(subjectLarge, {}, aboutMessage);
+ await BrowserTestUtils.waitForCondition(
+ () => !header.classList.contains("message-header-large-subject"),
+ "The subject line was changed"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(showAvatar, {}, aboutMessage);
+ await BrowserTestUtils.waitForCondition(
+ () => !header.classList.contains("message-header-show-recipient-avatar"),
+ "The avatar style was changed"
+ );
+ await assertVisibility(avatar, false, "The recipient avatar is hidden");
+
+ EventUtils.synthesizeMouseAtCenter(showFullAddress, {}, aboutMessage);
+ await BrowserTestUtils.waitForCondition(
+ () => !header.classList.contains("message-header-show-sender-full-address"),
+ "The full address style was changed"
+ );
+ await assertVisibility(
+ multiLine,
+ false,
+ "The recipient multi line is hidden"
+ );
+ await assertVisibility(
+ singleLine,
+ true,
+ "The recipient single line is visible"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(hideLabels, {}, aboutMessage);
+ await BrowserTestUtils.waitForCondition(
+ () => !header.classList.contains("message-header-hide-label-column"),
+ "The labels column style was changed"
+ );
+ await assertVisibility(firstLabel, true, "The first label is visible");
+ await BrowserTestUtils.waitForCondition(
+ () => firstLabel.style.minWidth != "0px",
+ "The first label has a min-width value"
+ );
+
+ await openMenuPopup();
+ EventUtils.sendKey("down", aboutMessage);
+ EventUtils.sendKey("down", aboutMessage);
+ EventUtils.sendKey("return", aboutMessage);
+ await BrowserTestUtils.waitForCondition(
+ () => header.classList.contains("message-header-buttons-only-icons"),
+ "The buttons are showing only icons"
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => !header.classList.contains("message-header-large-subject"),
+ "The subject line edit was maintained"
+ );
+
+ let panelHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, aboutMessage);
+ await panelHidden;
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ Services.xulStore.hasValue(
+ "chrome://messenger/content/messenger.xhtml",
+ "messageHeader",
+ "layout"
+ ),
+ "The customization data was saved"
+ );
+
+ MailTelemetryForTests.reportUIConfiguration();
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(scalars, scalarName, "subjectLarge", 0);
+ TelemetryTestUtils.assertKeyedScalar(scalars, scalarName, "buttonStyle", 1);
+ TelemetryTestUtils.assertKeyedScalar(scalars, scalarName, "hideLabels", 0);
+ TelemetryTestUtils.assertKeyedScalar(scalars, scalarName, "showAvatar", 0);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ scalarName,
+ "showFullAddress",
+ 0
+ );
+
+ popupShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(moreBtn, {}, aboutMessage);
+ await popupShown;
+
+ panelShown = BrowserTestUtils.waitForEvent(panel, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(customizeBtn, {}, aboutMessage);
+ await panelShown;
+
+ await openMenuPopup();
+ EventUtils.sendKey("up", aboutMessage);
+ EventUtils.sendKey("up", aboutMessage);
+ EventUtils.sendKey("return", aboutMessage);
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ !header.classList.contains("message-header-buttons-only-icons") &&
+ !header.classList.contains("message-header-buttons-only-text"),
+ "The buttons style was reverted to the default"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(subjectLarge, {}, aboutMessage);
+ EventUtils.synthesizeMouseAtCenter(showAvatar, {}, aboutMessage);
+ EventUtils.synthesizeMouseAtCenter(showFullAddress, {}, aboutMessage);
+ EventUtils.synthesizeMouseAtCenter(hideLabels, {}, aboutMessage);
+
+ panelHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, aboutMessage);
+ await panelHidden;
+
+ await BrowserTestUtils.waitForCondition(
+ () => header.classList.contains("message-header-large-subject"),
+ "The subject line is large again"
+ );
+ await assertVisibility(avatar, true, "The recipient avatar is visible");
+ await assertVisibility(
+ multiLine,
+ true,
+ "The recipient multi line is visible"
+ );
+ await assertVisibility(
+ singleLine,
+ false,
+ "he recipient single line is hidden"
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => firstLabel.style.minWidth == "0px",
+ "The first label has no min-width value"
+ );
+ await assertVisibility(firstLabel, false, "The labels column is hidden");
+
+ MailTelemetryForTests.reportUIConfiguration();
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(scalars, scalarName, "subjectLarge", 1);
+ TelemetryTestUtils.assertKeyedScalar(scalars, scalarName, "buttonStyle", 0);
+ TelemetryTestUtils.assertKeyedScalar(scalars, scalarName, "hideLabels", 1);
+ TelemetryTestUtils.assertKeyedScalar(scalars, scalarName, "showAvatar", 1);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ scalarName,
+ "showFullAddress",
+ 1
+ );
+});
diff --git a/comm/mail/test/browser/message-header/browser_phishingBar.js b/comm/mail/test/browser/message-header/browser_phishingBar.js
new file mode 100644
index 0000000000..f4b429725e
--- /dev/null
+++ b/comm/mail/test/browser/message-header/browser_phishingBar.js
@@ -0,0 +1,307 @@
+/* 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/. */
+
+/**
+ * Test that phishing notifications behave properly.
+ */
+
+"use strict";
+
+var { gMockExtProtSvcReg } = ChromeUtils.import(
+ "resource://testing-common/mozmill/ContentTabHelpers.jsm"
+);
+var {
+ add_message_to_folder,
+ be_in_folder,
+ create_folder,
+ create_message,
+ get_about_message,
+ inboxFolder,
+ mc,
+ open_message_from_file,
+ select_click_row,
+ wait_for_message_display_completion,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var {
+ assert_notification_displayed,
+ get_notification_button,
+ wait_for_notification_to_show,
+ wait_for_notification_to_stop,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/NotificationBoxHelpers.jsm"
+);
+var {
+ async_plan_for_new_window,
+ close_window,
+ plan_for_modal_dialog,
+ plan_for_new_window,
+ wait_for_new_window,
+ click_menus_in_sequence,
+} = ChromeUtils.import("resource://testing-common/mozmill/WindowHelpers.jsm");
+var folder;
+
+var kBoxId = "mail-notification-top";
+var kNotificationValue = "maybeScam";
+
+add_setup(async function () {
+ gMockExtProtSvcReg.register();
+
+ folder = await create_folder("PhishingBarA");
+ await add_message_to_folder(
+ [folder],
+ create_message({
+ body: {
+ body: '<form action="http://localhost/download-me"><input></form>.',
+ contentType: "text/html",
+ },
+ })
+ );
+ await add_message_to_folder([folder], create_message());
+ await add_message_to_folder(
+ [folder],
+ create_message({
+ body: {
+ body: "check out http://130.128.4.1. and http://130.128.4.2/.",
+ contentType: "text/plain",
+ },
+ })
+ );
+ await add_message_to_folder(
+ [folder],
+ create_message({
+ body: {
+ body: '<a href="http://subdomain.google.com/">http://www.google.com</a>.',
+ contentType: "text/html",
+ },
+ })
+ );
+ await add_message_to_folder(
+ [folder],
+ create_message({
+ body: {
+ body: '<a href="http://subdomain.google.com/">http://google.com</a>.',
+ contentType: "text/html",
+ },
+ })
+ );
+ await add_message_to_folder(
+ [folder],
+ create_message({
+ body: {
+ body: '<a href="http://evilhost">http://localhost</a>.',
+ contentType: "text/html",
+ },
+ })
+ );
+ await add_message_to_folder(
+ [folder],
+ create_message({
+ body: {
+ body: '<form action="http://localhost/download-me"><input></form>.',
+ contentType: "text/html",
+ },
+ })
+ );
+});
+
+registerCleanupFunction(() => {
+ gMockExtProtSvcReg.unregister();
+});
+
+/**
+ * Make sure the notification shows, and goes away once the Ignore menuitem
+ * is clicked.
+ */
+async function assert_ignore_works(aController) {
+ let aboutMessage = get_about_message(aController.window);
+ wait_for_notification_to_show(aboutMessage, kBoxId, kNotificationValue);
+ let prefButton = get_notification_button(
+ aboutMessage,
+ kBoxId,
+ kNotificationValue,
+ { popup: "phishingOptions" }
+ );
+ EventUtils.synthesizeMouseAtCenter(prefButton, {}, prefButton.ownerGlobal);
+ await click_menus_in_sequence(
+ aboutMessage.document.getElementById("phishingOptions"),
+ [{ id: "phishingOptionIgnore" }]
+ );
+ wait_for_notification_to_stop(aboutMessage, kBoxId, kNotificationValue);
+}
+
+/**
+ * Helper function to click the first link in a message if one is available.
+ */
+function click_link_if_available() {
+ let msgBody =
+ get_about_message().getMessagePaneBrowser().contentDocument.body;
+ if (msgBody.getElementsByTagName("a").length > 0) {
+ msgBody.getElementsByTagName("a")[0].click();
+ }
+}
+
+/**
+ * Test that when viewing a message, choosing ignore hides the the phishing
+ * notification.
+ */
+add_task(async function test_ignore_phishing_warning_from_message() {
+ let aboutMessage = get_about_message();
+
+ await be_in_folder(folder);
+ select_click_row(0);
+ await assert_ignore_works(mc);
+
+ select_click_row(1);
+ // msg 1 is normal -> no phishing warning
+ assert_notification_displayed(
+ aboutMessage,
+ kBoxId,
+ kNotificationValue,
+ false
+ );
+ select_click_row(0);
+ // msg 0 is a potential phishing attempt, but we ignored it so that should
+ // be remembered
+ assert_notification_displayed(
+ aboutMessage,
+ kBoxId,
+ kNotificationValue,
+ false
+ );
+});
+
+/**
+ * Test that when viewing en eml file, choosing ignore hides the phishing
+ * notification.
+ */
+add_task(async function test_ignore_phishing_warning_from_eml() {
+ let file = new FileUtils.File(getTestFilePath("data/evil.eml"));
+
+ let msgc = await open_message_from_file(file);
+ await assert_ignore_works(msgc);
+ close_window(msgc);
+}).skip();
+
+/**
+ * Test that when viewing an attached eml file, the phishing notification works.
+ */
+add_task(async function test_ignore_phishing_warning_from_eml_attachment() {
+ let file = new FileUtils.File(getTestFilePath("data/evil-attached.eml"));
+
+ let msgc = await open_message_from_file(file);
+ let aboutMessage = get_about_message(msgc.window);
+
+ // Make sure the root message shows the phishing bar.
+ wait_for_notification_to_show(aboutMessage, kBoxId, kNotificationValue);
+
+ // Open the attached message.
+ let newWindowPromise = async_plan_for_new_window("mail:messageWindow");
+ aboutMessage.document
+ .getElementById("attachmentList")
+ .getItemAtIndex(0)
+ .attachment.open();
+ let msgc2 = await newWindowPromise;
+ wait_for_message_display_completion(msgc2, true);
+
+ // Now make sure the attached message shows the phishing bar.
+ wait_for_notification_to_show(
+ get_about_message(msgc2.window),
+ kBoxId,
+ kNotificationValue
+ );
+
+ close_window(msgc2);
+ close_window(msgc);
+}).skip();
+
+/**
+ * Test that when viewing a message with an auto-linked ip address, we don't
+ * get a warning when clicking the link.
+ * We'll have http://130.128.4.1 vs. http://130.128.4.1/
+ */
+add_task(async function test_no_phishing_warning_for_ip_sameish_text() {
+ await be_in_folder(folder);
+ select_click_row(2); // Mail with Public IP address.
+ click_link_if_available();
+ assert_notification_displayed(
+ get_about_message(),
+ kBoxId,
+ kNotificationValue,
+ false
+ ); // not shown
+});
+
+/**
+ * Test that when viewing a message with a link whose base domain matches but
+ * has a different subdomain (e.g. http://subdomain.google.com/ vs
+ * http://google.com/), we don't get a warning if the link is pressed.
+ */
+add_task(async function test_no_phishing_warning_for_subdomain() {
+ let aboutMessage = get_about_message();
+ await be_in_folder(folder);
+ select_click_row(3);
+ click_link_if_available();
+ assert_notification_displayed(
+ aboutMessage,
+ kBoxId,
+ kNotificationValue,
+ false
+ ); // not shown
+
+ select_click_row(4);
+ click_link_if_available();
+ assert_notification_displayed(
+ aboutMessage,
+ kBoxId,
+ kNotificationValue,
+ false
+ ); // not shown
+});
+
+/**
+ * Test that when clicking a link where the text and/or href
+ * has no TLD, we still warn as appropriate.
+ */
+add_task(async function test_phishing_warning_for_local_domain() {
+ await be_in_folder(folder);
+ select_click_row(5);
+
+ let dialogAppeared = false;
+
+ plan_for_modal_dialog("commonDialogWindow", function (ctrler) {
+ dialogAppeared = true;
+ });
+
+ click_link_if_available();
+
+ Assert.ok(dialogAppeared);
+});
+
+/**
+ * Test that we warn about emails which contain <form>s with action attributes.
+ */
+add_task(async function test_phishing_warning_for_action_form() {
+ await be_in_folder(folder);
+ select_click_row(6);
+ assert_notification_displayed(
+ get_about_message(),
+ kBoxId,
+ kNotificationValue,
+ true
+ ); // shown
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
+
+registerCleanupFunction(async function teardown() {
+ await be_in_folder(inboxFolder);
+ folder.deleteSelf(null);
+});
diff --git a/comm/mail/test/browser/message-header/browser_replyIdentity.js b/comm/mail/test/browser/message-header/browser_replyIdentity.js
new file mode 100644
index 0000000000..fa1c1a7dae
--- /dev/null
+++ b/comm/mail/test/browser/message-header/browser_replyIdentity.js
@@ -0,0 +1,231 @@
+/* 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/. */
+
+/**
+ * Tests that actions such as replying choses the most suitable identity.
+ */
+
+"use strict";
+
+var { close_compose_window, open_compose_with_reply } = ChromeUtils.import(
+ "resource://testing-common/mozmill/ComposeHelpers.jsm"
+);
+var {
+ add_message_to_folder,
+ assert_selected_and_displayed,
+ be_in_folder,
+ create_message,
+ mc,
+ select_click_row,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var testFolder = null;
+
+var identity1Email = "carl@example.com";
+var identity2Email = "lenny@springfield.invalid";
+
+add_setup(async function () {
+ addIdentitiesAndFolder();
+ // Msg #0
+ await add_message_to_folder(
+ [testFolder],
+ create_message({
+ from: "Homer <homer@example.com>",
+ to: "workers@springfield.invalid",
+ subject: "no matching identity, like bcc/list",
+ body: {
+ body: "Alcohol is a way of life, alcohol is my way of life, and I aim to keep it.",
+ },
+ clobberHeaders: {},
+ })
+ );
+ // Msg #1
+ await add_message_to_folder(
+ [testFolder],
+ create_message({
+ from: "Homer <homer@example.com>",
+ to: "powerplant-workers@springfield.invalid",
+ subject: "only delivered-to header matching identity",
+ body: {
+ body: "Just because I don't care doesn't mean I don't understand.",
+ },
+ clobberHeaders: {
+ "Delivered-To": "<" + identity2Email + ">",
+ },
+ })
+ );
+ // Msg #2
+ await add_message_to_folder(
+ [testFolder],
+ create_message({
+ from: "Homer <homer@example.com>",
+ to: "powerplant-workers@springfield.invalid, Apu <apu@test.invalid>",
+ cc: "other." + identity2Email,
+ subject: "subpart of cc address matching identity",
+ body: { body: "Blame the guy who doesn't speak Engish." },
+ clobberHeaders: {},
+ })
+ );
+ // Msg #3
+ await add_message_to_folder(
+ [testFolder],
+ create_message({
+ from: "Homer <homer@example.com>",
+ to: "Lenny <" + identity2Email + ">",
+ subject: "normal to:address match, with full name",
+ body: {
+ body: "Remember as far as anyone knows, we're a nice normal family.",
+ },
+ })
+ );
+ // Msg #4
+ await add_message_to_folder(
+ [testFolder],
+ create_message({
+ from: "Homer <homer@example.com>",
+ to: "powerplant-workers@springfield.invalid",
+ subject: "delivered-to header matching only subpart of identity email",
+ body: { body: "Mmmm...Forbidden donut" },
+ clobberHeaders: {
+ "Delivered-To": "<other." + identity2Email + ">",
+ },
+ })
+ );
+ // Msg #5
+ await add_message_to_folder(
+ [testFolder],
+ create_message({
+ from: identity2Email + " <" + identity2Email + ">",
+ to: "Marge <marge@example.com>",
+ subject: "from second self",
+ body: {
+ body: "All my life I've had one dream, to achieve my many goals.",
+ },
+ })
+ );
+});
+
+function addIdentitiesAndFolder() {
+ let server = MailServices.accounts.createIncomingServer(
+ "nobody",
+ "Reply Identity Testing",
+ "pop3"
+ );
+ testFolder = server.rootFolder
+ .QueryInterface(Ci.nsIMsgLocalMailFolder)
+ .createLocalSubfolder("Replies");
+
+ let identity = MailServices.accounts.createIdentity();
+ identity.email = identity1Email;
+
+ let identity2 = MailServices.accounts.createIdentity();
+ identity2.email = identity2Email;
+
+ let account = MailServices.accounts.createAccount();
+ account.incomingServer = server;
+ account.addIdentity(identity);
+ account.addIdentity(identity2);
+}
+
+function checkReply(replyWin, expectedFromEmail) {
+ let identityList = replyWin.window.document.getElementById("msgIdentity");
+ if (!identityList.selectedItem.label.includes(expectedFromEmail)) {
+ throw new Error(
+ "The From address is not correctly selected! Expected: " +
+ expectedFromEmail +
+ "; Actual: " +
+ identityList.selectedItem.label
+ );
+ }
+}
+
+add_task(async function test_reply_no_matching_identity() {
+ await be_in_folder(testFolder);
+
+ let msg = select_click_row(0);
+ assert_selected_and_displayed(mc, msg);
+
+ let replyWin = open_compose_with_reply();
+ // Should have selected the default identity.
+ checkReply(replyWin, identity1Email);
+ close_compose_window(replyWin);
+});
+
+add_task(async function test_reply_matching_only_deliveredto() {
+ await be_in_folder(testFolder);
+
+ let msg = select_click_row(1);
+ assert_selected_and_displayed(mc, msg);
+
+ let replyWin = open_compose_with_reply();
+ // Should have selected the second id, which is listed in Delivered-To:.
+ checkReply(replyWin, identity2Email);
+ close_compose_window(replyWin);
+}).skip();
+
+add_task(async function test_reply_matching_subaddress() {
+ await be_in_folder(testFolder);
+
+ let msg = select_click_row(2);
+ assert_selected_and_displayed(mc, msg);
+
+ let replyWin = open_compose_with_reply();
+ // Should have selected the first id, the email doesn't fully match.
+ // other.lenny != "our" lenny
+ checkReply(replyWin, identity1Email);
+ close_compose_window(replyWin);
+});
+
+add_task(async function test_reply_to_matching_second_id() {
+ await be_in_folder(testFolder);
+
+ let msg = select_click_row(3);
+ assert_selected_and_displayed(mc, msg);
+
+ let replyWin = open_compose_with_reply();
+ // Should have selected the second id, which was in To;.
+ checkReply(replyWin, identity2Email);
+ close_compose_window(replyWin);
+});
+
+add_task(async function test_deliveredto_to_matching_only_parlty() {
+ await be_in_folder(testFolder);
+
+ let msg = select_click_row(4);
+ assert_selected_and_displayed(mc, msg);
+
+ let replyWin = open_compose_with_reply();
+ // Should have selected the (default) first id.
+ checkReply(replyWin, identity1Email);
+ close_compose_window(replyWin);
+});
+
+/**
+ * A reply from self is treated as a follow-up. And this self
+ * was the second identity, so the reply should also be from the second identity.
+ */
+add_task(async function test_reply_to_self_second_id() {
+ await be_in_folder(testFolder);
+
+ let msg = select_click_row(5);
+ assert_selected_and_displayed(mc, msg);
+
+ let replyWin = open_compose_with_reply();
+ // Should have selected the second id, which was in From.
+ checkReply(replyWin, identity2Email);
+ close_compose_window(replyWin, false /* no prompt*/);
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
diff --git a/comm/mail/test/browser/message-header/browser_replyToListFromAddressSelection.js b/comm/mail/test/browser/message-header/browser_replyToListFromAddressSelection.js
new file mode 100644
index 0000000000..b72d28de45
--- /dev/null
+++ b/comm/mail/test/browser/message-header/browser_replyToListFromAddressSelection.js
@@ -0,0 +1,121 @@
+/* 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/. */
+
+/*
+ * Test for the most suitable identity in From address for reply-to-list
+ */
+
+"use strict";
+
+var { close_compose_window, open_compose_with_reply_to_list } =
+ ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
+var { assert_selected_and_displayed, be_in_folder, mc, select_click_row } =
+ ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+ );
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var testFolder = null;
+var msgHdr = null;
+var replyToListWindow = null;
+
+var identityString1 = "tinderbox_correct_identity@foo.invalid";
+
+add_setup(function () {
+ addIdentitiesAndFolder();
+ addMessageToFolder(testFolder);
+});
+
+function addMessageToFolder(aFolder) {
+ var msgId = Services.uuid.generateUUID() + "@mozillamessaging.invalid";
+
+ var source =
+ "From - Sat Nov 1 12:39:54 2008\n" +
+ "X-Mozilla-Status: 0001\n" +
+ "X-Mozilla-Status2: 00000000\n" +
+ "Delivered-To: <tinderbox_identity333@foo.invalid>\n" +
+ "Delivered-To: <" +
+ identityString1 +
+ ">\n" +
+ "Delivered-To: <tinderbox_identity555@foo.invalid>\n" +
+ "Message-ID: <" +
+ msgId +
+ ">\n" +
+ "Date: Wed, 11 Jun 2008 20:32:02 -0400\n" +
+ "From: Tester <tests@mozillamessaging.invalid>\n" +
+ "User-Agent: Thunderbird 3.0a2pre (Macintosh/2008052122)\n" +
+ "MIME-Version: 1.0\n" +
+ "List-ID: <list.mozillamessaging.invalid>\n" +
+ "List-Post: <list.mozillamessaging.invalid>, \n" +
+ " <mailto: list@mozillamessaging.invalid>\n" +
+ "To: recipient@mozillamessaging.invalid\n" +
+ "Subject: a subject\n" +
+ "Content-Type: text/html; charset=ISO-8859-1\n" +
+ "Content-Transfer-Encoding: 7bit\n" +
+ "\ntext body\n";
+
+ aFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ aFolder.gettingNewMessages = true;
+ aFolder.addMessage(source);
+ aFolder.gettingNewMessages = false;
+
+ return aFolder.msgDatabase.getMsgHdrForMessageID(msgId);
+}
+
+function addIdentitiesAndFolder() {
+ let identity2 = MailServices.accounts.createIdentity();
+ // identity.fullName = "Tinderbox_Identity1";
+ identity2.email = "tinderbox_identity1@foo.invalid";
+
+ let identity = MailServices.accounts.createIdentity();
+ // identity.fullName = "Tinderbox_Identity1";
+ identity.email = identityString1;
+
+ let server = MailServices.accounts.createIncomingServer(
+ "nobody",
+ "Test Local Folders",
+ "pop3"
+ );
+ let localRoot = server.rootFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ testFolder = localRoot.createLocalSubfolder("Test Folder");
+
+ let account = MailServices.accounts.createAccount();
+ account.incomingServer = server;
+ account.addIdentity(identity);
+ account.addIdentity(identity2);
+}
+
+add_task(async function test_Reply_To_List_From_Address() {
+ await be_in_folder(testFolder);
+
+ let curMessage = select_click_row(0);
+ assert_selected_and_displayed(mc, curMessage);
+
+ replyToListWindow = open_compose_with_reply_to_list();
+
+ var identityList =
+ replyToListWindow.window.document.getElementById("msgIdentity");
+
+ // see if it's the correct identity selected
+ if (!identityList.selectedItem.label.includes(identityString1)) {
+ throw new Error(
+ "The From address is not correctly selected! Expected: " +
+ identityString1 +
+ "; Actual: " +
+ identityList.selectedItem.label
+ );
+ }
+
+ close_compose_window(replyToListWindow);
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
diff --git a/comm/mail/test/browser/message-header/browser_returnReceipt.js b/comm/mail/test/browser/message-header/browser_returnReceipt.js
new file mode 100644
index 0000000000..f48fa04313
--- /dev/null
+++ b/comm/mail/test/browser/message-header/browser_returnReceipt.js
@@ -0,0 +1,208 @@
+/* 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/. */
+
+/*
+ * Test return receipt (MDN) stuff.
+ */
+
+"use strict";
+
+var {
+ add_message_to_folder,
+ assert_selected_and_displayed,
+ be_in_folder,
+ create_folder,
+ create_message,
+ get_about_message,
+ mc,
+ select_click_row,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { assert_notification_displayed } = ChromeUtils.import(
+ "resource://testing-common/mozmill/NotificationBoxHelpers.jsm"
+);
+
+var folder;
+
+var kBoxId = "mail-notification-top";
+var kNotificationValue = "mdnRequested";
+
+add_setup(async function () {
+ folder = await create_folder("ReturnReceiptTest");
+
+ // Create a message that requests a return receipt.
+ let msg0 = create_message({
+ from: ["Ake", "ake@example.com"],
+ clobberHeaders: { "Disposition-Notification-To": "ake@example.com" },
+ });
+ await add_message_to_folder([folder], msg0);
+
+ // ... and one that doesn't request a return receipt.
+ let msg1 = create_message();
+ await add_message_to_folder([folder], msg1);
+
+ // Create a message that requests a return receipt to a different address.
+ let msg2 = create_message({
+ from: ["Mimi", "me@example.org"],
+ clobberHeaders: { "Disposition-Notification-To": "other@example.com" },
+ });
+ await add_message_to_folder([folder], msg2);
+
+ // Create a message that requests a return receipt to different addresses.
+ let msg3 = create_message({
+ from: ["Bobby", "bob@example.org"],
+ clobberHeaders: {
+ "Disposition-Notification-To": "ex1@example.com, ex2@example.com",
+ },
+ });
+ await add_message_to_folder([folder], msg3);
+
+ // Create a message that requests a return receipt using non-standard header.
+ let msg4 = create_message({
+ from: ["Ake", "ake@example.com"],
+ clobberHeaders: { "Return-Receipt-To": "ake@example.com" },
+ });
+ await add_message_to_folder([folder], msg4);
+
+ // Create a message that requests a return receipt to a different address
+ // using non-standard header.
+ let msg5 = create_message({
+ from: ["Mimi", "me@example.org"],
+ clobberHeaders: { "Return-Receipt-To": "other@example.com" },
+ });
+ await add_message_to_folder([folder], msg5);
+
+ // Create a message that requests a return receipt to different addresses
+ // using non-standard header.
+ let msg6 = create_message({
+ from: ["Bobby", "bob@example.org"],
+ clobberHeaders: { "Return-Receipt-To": "ex1@example.com, ex2@example.com" },
+ });
+ await add_message_to_folder([folder], msg6);
+
+ await be_in_folder(folder);
+});
+
+/** Utility to select a message. */
+function gotoMsg(row) {
+ let curMessage = select_click_row(row);
+ assert_selected_and_displayed(mc, curMessage);
+}
+
+/**
+ * Utility to make sure the MDN bar is shown / not shown.
+ */
+function assert_mdn_shown(shouldShow) {
+ assert_notification_displayed(
+ get_about_message(),
+ kBoxId,
+ kNotificationValue,
+ shouldShow
+ );
+}
+
+/**
+ * Utility function to make sure the notification contains a certain text.
+ */
+function assert_mdn_text_contains(text, shouldContain) {
+ let nb = get_about_message().document.getElementById(kBoxId);
+ let box = nb.querySelector(".notificationbox-stack")._notificationBox;
+ let notificationText = box.currentNotification.messageText.textContent;
+ if (shouldContain && !notificationText.includes(text)) {
+ throw new Error(
+ "mdnBar should contain text=" +
+ text +
+ "; notificationText=" +
+ notificationText
+ );
+ }
+ if (!shouldContain && notificationText.includes(text)) {
+ throw new Error(
+ "mdnBar shouldn't contain text=" +
+ text +
+ "; notificationText=" +
+ notificationText
+ );
+ }
+}
+
+/**
+ * Test that return receipts are not shown when Disposition-Notification-To
+ * and Return-Receipt-To isn't set.
+ */
+add_task(function test_no_mdn_for_normal_msgs() {
+ gotoMsg(0); // TODO this shouldn't be needed but the selection goes to 0 on focus.
+ gotoMsg(1); // This message doesn't request a return receipt.
+ assert_mdn_shown(false);
+});
+
+/**
+ * Test that return receipts are shown when Disposition-Notification-To is set.
+ */
+add_task(function test_basic_mdn_shown() {
+ gotoMsg(0); // This message requests a return receipt.
+ assert_mdn_shown(true);
+ assert_mdn_text_contains("ake@example.com", false); // only name should show
+});
+
+/**
+ * Test that return receipts are shown when Return-Receipt-To is set.
+ */
+add_task(function test_basic_mdn_shown_nonrfc() {
+ gotoMsg(4); // This message requests a return receipt.
+ assert_mdn_shown(true);
+ assert_mdn_text_contains("ake@example.com", false); // only name should show
+});
+
+/**
+ * Test that return receipts warns when the mdn address is different.
+ * The RFC compliant version.
+ */
+add_task(function test_mdn_when_from_and_disposition_to_differs() {
+ gotoMsg(2); // Should display a notification with warning.
+ assert_mdn_shown(true);
+ assert_mdn_text_contains("other@example.com", true); // address should show
+});
+
+/**
+ * Test that return receipts warns when the mdn address is different.
+ * The RFC non-compliant version.
+ */
+add_task(function test_mdn_when_from_and_disposition_to_differs_nonrfc() {
+ gotoMsg(5); // Should display a notification with warning.
+ assert_mdn_shown(true);
+ assert_mdn_text_contains("other@example.com", true); // address should show
+});
+
+/**
+ * Test that return receipts warns when the mdn address consists of multiple
+ * addresses.
+ */
+add_task(function test_mdn_when_disposition_to_multi() {
+ gotoMsg(3);
+ // Should display a notification with warning listing all the addresses.
+ assert_mdn_shown(true);
+ assert_mdn_text_contains("ex1@example.com", true);
+ assert_mdn_text_contains("ex2@example.com", true);
+});
+
+/**
+ * Test that return receipts warns when the mdn address consists of multiple
+ * addresses. Non-RFC compliant version.
+ */
+add_task(function test_mdn_when_disposition_to_multi_nonrfc() {
+ gotoMsg(6);
+ // Should display a notification with warning listing all the addresses.
+ assert_mdn_shown(true);
+ assert_mdn_text_contains("ex1@example.com", true);
+ assert_mdn_text_contains("ex2@example.com", true);
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
diff --git a/comm/mail/test/browser/message-header/data/evil-attached.eml b/comm/mail/test/browser/message-header/data/evil-attached.eml
new file mode 100644
index 0000000000..24e3874ff4
--- /dev/null
+++ b/comm/mail/test/browser/message-header/data/evil-attached.eml
@@ -0,0 +1,22 @@
+Date: Mon, 10 Jan 2011 12:00:00 -0500
+From: phisher@evil.com
+To: user@example.com
+Subject: Please look at this attachment!
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+
+This is a multi-part message in MIME format.
+--BOUNDARY
+Content-Type: text/html
+
+See below.
+--BOUNDARY
+Content-Type: message/rfc822
+
+Date: Mon, 10 Jan 2011 12:00:00 -0500
+From: phisher@evil.com
+To: user@example.com
+Subject: Please click on this link!
+Content-Type: text/html
+
+<form action="http://localhost/download-me"><input></form>
+--BOUNDARY--
diff --git a/comm/mail/test/browser/message-header/data/evil.eml b/comm/mail/test/browser/message-header/data/evil.eml
new file mode 100644
index 0000000000..938ade296b
--- /dev/null
+++ b/comm/mail/test/browser/message-header/data/evil.eml
@@ -0,0 +1,7 @@
+Date: Mon, 10 Jan 2011 12:00:00 -0500
+From: phisher@evil.com
+To: user@example.com
+Subject: Please click on this link!
+Content-Type: text/html
+
+<form action="http://localhost/download-me"><input></form>
diff --git a/comm/mail/test/browser/message-header/head.js b/comm/mail/test/browser/message-header/head.js
new file mode 100644
index 0000000000..2a2c6e1b6a
--- /dev/null
+++ b/comm/mail/test/browser/message-header/head.js
@@ -0,0 +1,190 @@
+/* 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 { MailConsts } = ChromeUtils.import("resource:///modules/MailConsts.jsm");
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+
+registerCleanupFunction(() => {
+ let tabmail = document.getElementById("tabmail");
+ is(tabmail.tabInfo.length, 1);
+
+ while (tabmail.tabInfo.length > 1) {
+ tabmail.closeTab(tabmail.tabInfo[1]);
+ }
+
+ // Some tests that open new windows don't return focus to the main window
+ // in a way that satisfies mochitest, and the test times out.
+ Services.focus.focusedWindow = window;
+ // Focus an element in the main window, then blur it again to avoid it
+ // hijacking keypresses.
+ let mainWindowElement = document.getElementById("button-appmenu");
+ mainWindowElement.focus();
+ mainWindowElement.blur();
+
+ Services.prefs.clearUserPref("mail.pane_config.dynamic");
+ Services.xulStore.removeValue(
+ "chrome://messenger/content/messenger.xhtml",
+ "threadPane",
+ "view"
+ );
+});
+
+function createAccount(type = "none") {
+ let account;
+
+ if (type == "local") {
+ MailServices.accounts.createLocalMailAccount();
+ account = MailServices.accounts.FindAccountForServer(
+ MailServices.accounts.localFoldersServer
+ );
+ } else {
+ account = MailServices.accounts.createAccount();
+ account.incomingServer = MailServices.accounts.createIncomingServer(
+ `${account.key}user`,
+ "localhost",
+ type
+ );
+ }
+
+ info(`Created account ${account.toString()}`);
+ return account;
+}
+
+async function createSubfolder(parent, name) {
+ parent.createSubfolder(name, null);
+ return parent.getChildNamed(name);
+}
+
+function createMessages(folder, makeMessagesArg) {
+ if (typeof makeMessagesArg == "number") {
+ makeMessagesArg = { count: makeMessagesArg };
+ }
+ if (!createMessages.messageGenerator) {
+ createMessages.messageGenerator = new MessageGenerator();
+ }
+
+ let messages = createMessages.messageGenerator.makeMessages(makeMessagesArg);
+ let messageStrings = messages.map(message => message.toMboxString());
+ folder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ folder.addMessageBatch(messageStrings);
+}
+
+async function openMessageInTab(msgHdr) {
+ if (!msgHdr.QueryInterface(Ci.nsIMsgDBHdr)) {
+ throw new Error("No message passed to openMessageInTab");
+ }
+
+ // Ensure the behaviour pref is set to open a new tab. It is the default,
+ // but you never know.
+ let oldPrefValue = Services.prefs.getIntPref("mail.openMessageBehavior");
+ Services.prefs.setIntPref(
+ "mail.openMessageBehavior",
+ MailConsts.OpenMessageBehavior.NEW_TAB
+ );
+ MailUtils.displayMessages([msgHdr]);
+ Services.prefs.setIntPref("mail.openMessageBehavior", oldPrefValue);
+
+ let win = Services.wm.getMostRecentWindow("mail:3pane");
+ let tab = win.document.getElementById("tabmail").currentTabInfo;
+ let browser = tab.browser;
+
+ await promiseMessageLoaded(browser, msgHdr);
+ return tab;
+}
+
+async function openMessageInWindow(msgHdr) {
+ if (!msgHdr.QueryInterface(Ci.nsIMsgDBHdr)) {
+ throw new Error("No message passed to openMessageInWindow");
+ }
+
+ let messageWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded(
+ undefined,
+ async win =>
+ win.document.documentURI ==
+ "chrome://messenger/content/messageWindow.xhtml"
+ );
+ MailUtils.openMessageInNewWindow(msgHdr);
+
+ let messageWindow = await messageWindowPromise;
+ let browser = messageWindow.document.getElementById("messagepane");
+
+ await promiseMessageLoaded(browser, msgHdr);
+ return messageWindow;
+}
+
+async function promiseMessageLoaded(browser, msgHdr) {
+ let messageURI = msgHdr.folder.getUriForMsg(msgHdr);
+ messageURI = MailServices.messageServiceFromURI(messageURI).getUrlForUri(
+ messageURI,
+ null
+ );
+
+ if (
+ browser.webProgress?.isLoadingDocument ||
+ !browser.currentURI?.equals(messageURI)
+ ) {
+ await BrowserTestUtils.browserLoaded(
+ browser,
+ null,
+ uri => uri == messageURI.spec
+ );
+ }
+}
+
+async function assertVisibility(element, isVisible, msg) {
+ await TestUtils.waitForCondition(
+ () => BrowserTestUtils.is_visible(element) == isVisible,
+ `The ${element.id} should be ${isVisible ? "visible" : "hidden"}: ${msg}`
+ );
+}
+
+/**
+ * Helper method to switch to a cards view with vertical layout.
+ */
+async function ensure_cards_view() {
+ const { threadTree, threadPane } =
+ document.getElementById("tabmail").currentAbout3Pane;
+
+ Services.prefs.setIntPref("mail.pane_config.dynamic", 2);
+ Services.xulStore.setValue(
+ "chrome://messenger/content/messenger.xhtml",
+ "threadPane",
+ "view",
+ "cards"
+ );
+ threadPane.updateThreadView("cards");
+
+ await BrowserTestUtils.waitForCondition(
+ () => threadTree.getAttribute("rows") == "thread-card",
+ "The tree view switched to a cards layout"
+ );
+}
+
+/**
+ * Helper method to switch to a table view with classic layout.
+ */
+async function ensure_table_view() {
+ const { threadTree, threadPane } =
+ document.getElementById("tabmail").currentAbout3Pane;
+
+ Services.prefs.setIntPref("mail.pane_config.dynamic", 0);
+ Services.xulStore.setValue(
+ "chrome://messenger/content/messenger.xhtml",
+ "threadPane",
+ "view",
+ "table"
+ );
+ threadPane.updateThreadView("table");
+
+ await BrowserTestUtils.waitForCondition(
+ () => threadTree.getAttribute("rows") == "thread-row",
+ "The tree view switched to a table layout"
+ );
+}