summaryrefslogtreecommitdiffstats
path: root/comm/mail/test/browser/attachment/browser_attachment.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/test/browser/attachment/browser_attachment.js')
-rw-r--r--comm/mail/test/browser/attachment/browser_attachment.js764
1 files changed, 764 insertions, 0 deletions
diff --git a/comm/mail/test/browser/attachment/browser_attachment.js b/comm/mail/test/browser/attachment/browser_attachment.js
new file mode 100644
index 0000000000..c24a15eff2
--- /dev/null
+++ b/comm/mail/test/browser/attachment/browser_attachment.js
@@ -0,0 +1,764 @@
+/* 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/. */
+
+/**
+ * Checks various attachments display correctly
+ */
+
+"use strict";
+
+var { close_compose_window, open_compose_with_forward } = ChromeUtils.import(
+ "resource://testing-common/mozmill/ComposeHelpers.jsm"
+);
+var {
+ add_message_to_folder,
+ assert_attachment_list_focused,
+ assert_message_pane_focused,
+ assert_selected_and_displayed,
+ be_in_folder,
+ close_popup,
+ create_folder,
+ create_message,
+ get_about_message,
+ mc,
+ msgGen,
+ plan_to_wait_for_folder_events,
+ select_click_row,
+ select_none,
+ wait_for_folder_events,
+ wait_for_message_display_completion,
+ wait_for_popup_to_open,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { SyntheticPartLeaf, SyntheticPartMultiMixed } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+
+var {
+ async_plan_for_new_window,
+ close_window,
+ plan_for_modal_dialog,
+ wait_for_modal_dialog,
+} = ChromeUtils.import("resource://testing-common/mozmill/WindowHelpers.jsm");
+
+var folder;
+var messages;
+
+var textAttachment =
+ "One of these days... people like me will rise up and overthrow you, and " +
+ "the end of tyranny by the homeostatic machine will have arrived. The day " +
+ "of human values and compassion and simple warmth will return, and when " +
+ "that happens someone like myself who has gone through an ordeal and who " +
+ "genuinely needs hot coffee to pick him up and keep him functioning when " +
+ "he has to function will get the hot coffee whether he happens to have a " +
+ "poscred readily available or not.";
+
+var binaryAttachment = textAttachment;
+
+add_setup(async function () {
+ folder = await create_folder("AttachmentA");
+
+ var attachedMessage = msgGen.makeMessage({
+ body: { body: "I'm an attached email!" },
+ attachments: [
+ { body: textAttachment, filename: "inner attachment.txt", format: "" },
+ ],
+ });
+
+ // create some messages that have various types of attachments
+ messages = [
+ // no attachment
+ {},
+ // text attachment
+ {
+ attachments: [{ body: textAttachment, filename: "ubik.txt", format: "" }],
+ },
+ // binary attachment; filename has 9 "1"s, which should be just within the
+ // limit for showing the original name
+ {
+ attachments: [
+ {
+ body: binaryAttachment,
+ contentType: "application/octet-stream",
+ filename: "ubik-111111111.xxyyzz",
+ format: "",
+ },
+ ],
+ },
+ // multiple attachments
+ {
+ attachments: [
+ { body: textAttachment, filename: "ubik.txt", format: "" },
+ {
+ body: binaryAttachment,
+ contentType: "application/octet-stream",
+ filename: "ubik.xxyyzz",
+ format: "",
+ },
+ ],
+ },
+ // attachment with a long name; the attachment bar should crop this
+ {
+ attachments: [
+ {
+ body: textAttachment,
+ filename:
+ "this-is-a-file-with-an-extremely-long-name-" +
+ "that-seems-to-go-on-forever-seriously-you-" +
+ "would-not-believe-how-long-this-name-is-it-" +
+ "surely-exceeds-the-maximum-filename-length-" +
+ "for-most-filesystems.txt",
+ format: "",
+ },
+ ],
+ },
+ // a message with a text attachment and an email attachment, which in turn
+ // has its own text attachment
+ {
+ bodyPart: new SyntheticPartMultiMixed([
+ new SyntheticPartLeaf("I'm a message!"),
+ new SyntheticPartLeaf(textAttachment, {
+ filename: "outer attachment.txt",
+ contentType: "text/plain",
+ format: "",
+ }),
+ attachedMessage,
+ ]),
+ },
+ // evilly-named attachment; spaces should be collapsed and trimmed on the
+ // ends
+ {
+ attachments: [
+ {
+ body: textAttachment,
+ contentType: "application/octet-stream",
+ filename: " ubik .txt .evil ",
+ sanitizedFilename: "ubik .txt .evil",
+ format: "",
+ },
+ ],
+ },
+ // another evilly-named attachment; filename has 10 "_"s, which should be
+ // just enough to trigger the sanitizer
+ {
+ attachments: [
+ {
+ body: textAttachment,
+ contentType: "application/octet-stream",
+ filename: "ubik.txt__________.evil",
+ sanitizedFilename: "ubik.txt_…_.evil",
+ format: "",
+ },
+ ],
+ },
+ // No texdir change in the filename please.
+ {
+ attachments: [
+ {
+ body: textAttachment,
+ filename: "ABC\u202EE.txt.zip",
+ sanitizedFilename: "ABC.E.txt.zip",
+ },
+ ],
+ },
+ ];
+
+ // Add another evilly-named attachment for Windows tests, to ensure that
+ // trailing periods are stripped.
+ if ("@mozilla.org/windows-registry-key;1" in Cc) {
+ messages.push({
+ attachments: [
+ {
+ body: textAttachment,
+ contentType: "application/octet-stream",
+ filename: "ubik.evil. . . . . . . . . ....",
+ sanitizedFilename: "ubik.evil",
+ format: "",
+ },
+ ],
+ });
+ }
+
+ for (let i = 0; i < messages.length; i++) {
+ await add_message_to_folder([folder], create_message(messages[i]));
+ }
+});
+
+/**
+ * Set the pref to ensure that the attachments pane starts out (un)expanded
+ *
+ * @param expand true if the attachment pane should start out expanded,
+ * false otherwise
+ */
+function ensure_starts_expanded(expand) {
+ Services.prefs.setBoolPref(
+ "mailnews.attachments.display.start_expanded",
+ expand
+ );
+}
+
+add_task(async function test_attachment_view_collapsed() {
+ await be_in_folder(folder);
+
+ select_click_row(0);
+ assert_selected_and_displayed(0);
+
+ if (
+ !get_about_message().document.getElementById("attachmentView").collapsed
+ ) {
+ throw new Error("Attachment pane expanded when it shouldn't be!");
+ }
+});
+
+add_task(async function test_attachment_view_expanded() {
+ await be_in_folder(folder);
+
+ for (let i = 1; i < messages.length; i++) {
+ select_click_row(i);
+ assert_selected_and_displayed(i);
+
+ if (
+ get_about_message().document.getElementById("attachmentView").collapsed
+ ) {
+ throw new Error(
+ "Attachment pane collapsed (on message #" + i + " when it shouldn't be!"
+ );
+ }
+ }
+});
+
+add_task(async function test_attachment_name_sanitization() {
+ await be_in_folder(folder);
+
+ let aboutMessage = get_about_message();
+ let attachmentList = aboutMessage.document.getElementById("attachmentList");
+
+ for (let i = 0; i < messages.length; i++) {
+ if ("attachments" in messages[i]) {
+ select_click_row(i);
+ assert_selected_and_displayed(i);
+
+ let attachments = messages[i].attachments;
+ if (messages[i].attachments.length == 1) {
+ Assert.equal(
+ aboutMessage.document.getElementById("attachmentName").value,
+ attachments[0].sanitizedFilename || attachments[0].filename
+ );
+ }
+
+ for (let j = 0; j < attachments.length; j++) {
+ Assert.equal(
+ attachmentList.getItemAtIndex(j).getAttribute("name"),
+ attachments[j].sanitizedFilename || attachments[j].filename
+ );
+ }
+ }
+ }
+});
+
+add_task(async function test_long_attachment_name() {
+ await be_in_folder(folder);
+
+ select_click_row(4);
+ assert_selected_and_displayed(4);
+
+ let aboutMessage = get_about_message();
+ let messagepaneBox = aboutMessage.document.getElementById("messagepanebox");
+ let attachmentBar = aboutMessage.document.getElementById("attachmentBar");
+
+ Assert.ok(
+ messagepaneBox.getBoundingClientRect().width >=
+ attachmentBar.getBoundingClientRect().width,
+ "Attachment bar has expanded off the edge of the window!"
+ );
+});
+
+/**
+ * Make sure that, when opening attached messages, we only show the attachments
+ * "beneath" the attached message (as opposed to all attachments for the root
+ * message).
+ */
+add_task(async function test_attached_message_attachments() {
+ await be_in_folder(folder);
+
+ select_click_row(5);
+ assert_selected_and_displayed(5);
+
+ // Make sure we have the expected number of attachments in the root message:
+ // an outer text attachment, an attached email, and an inner text attachment.
+ let aboutMessage = get_about_message();
+ Assert.equal(
+ aboutMessage.document.getElementById("attachmentList").itemCount,
+ 3
+ );
+
+ // Open the attached email.
+ let newWindowPromise = async_plan_for_new_window("mail:messageWindow");
+ aboutMessage.document
+ .getElementById("attachmentList")
+ .getItemAtIndex(1)
+ .attachment.open();
+ let msgc = await newWindowPromise;
+ wait_for_message_display_completion(msgc, true);
+
+ // Make sure we have the expected number of attachments in the attached
+ // message: just an inner text attachment.
+ Assert.equal(
+ msgc.window.document.getElementById("attachmentList").itemCount,
+ 1
+ );
+
+ close_window(msgc);
+}).skip();
+
+add_task(async function test_attachment_name_click() {
+ await be_in_folder(folder);
+
+ select_click_row(1);
+ assert_selected_and_displayed(1);
+
+ let aboutMessage = get_about_message();
+ let attachmentList = aboutMessage.document.getElementById("attachmentList");
+
+ Assert.ok(
+ attachmentList.collapsed,
+ "Attachment list should start out collapsed!"
+ );
+
+ // Ensure the open dialog appears when clicking on the attachment name and
+ // that the attachment list doesn't expand.
+ plan_for_modal_dialog("unknownContentTypeWindow", function () {});
+ EventUtils.synthesizeMouseAtCenter(
+ aboutMessage.document.getElementById("attachmentName"),
+ { clickCount: 1 },
+ aboutMessage
+ );
+ wait_for_modal_dialog("unknownContentTypeWindow");
+ Assert.ok(
+ attachmentList.collapsed,
+ "Attachment list should not expand when clicking on attachmentName!"
+ );
+});
+
+/**
+ * Test that right-clicking on a particular element opens the expected context
+ * menu.
+ *
+ * @param elementId the id of the element to right click on
+ * @param contextMenuId the id of the context menu that should appear
+ */
+async function subtest_attachment_right_click(elementId, contextMenuId) {
+ let aboutMessage = get_about_message();
+ let element = aboutMessage.document.getElementById(elementId);
+ let contextMenu = aboutMessage.document.getElementById(contextMenuId);
+
+ let shownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(
+ element,
+ { type: "contextmenu" },
+ aboutMessage
+ );
+ await shownPromise;
+ let hiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.hidePopup();
+ await hiddenPromise;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+}
+
+add_task(async function test_attachment_right_click_single() {
+ await be_in_folder(folder);
+
+ select_click_row(1);
+ assert_selected_and_displayed(1);
+
+ await subtest_attachment_right_click(
+ "attachmentIcon",
+ "attachmentItemContext"
+ );
+ await subtest_attachment_right_click(
+ "attachmentCount",
+ "attachmentItemContext"
+ );
+ await subtest_attachment_right_click(
+ "attachmentName",
+ "attachmentItemContext"
+ );
+ await subtest_attachment_right_click(
+ "attachmentSize",
+ "attachmentItemContext"
+ );
+
+ await subtest_attachment_right_click(
+ "attachmentToggle",
+ "attachment-toolbar-context-menu"
+ );
+ await subtest_attachment_right_click(
+ "attachmentSaveAllSingle",
+ "attachment-toolbar-context-menu"
+ );
+ await subtest_attachment_right_click(
+ "attachmentBar",
+ "attachment-toolbar-context-menu"
+ );
+});
+
+add_task(async function test_attachment_right_click_multiple() {
+ await be_in_folder(folder);
+
+ select_click_row(3);
+ assert_selected_and_displayed(3);
+
+ await subtest_attachment_right_click(
+ "attachmentIcon",
+ "attachmentListContext"
+ );
+ await subtest_attachment_right_click(
+ "attachmentCount",
+ "attachmentListContext"
+ );
+ await subtest_attachment_right_click(
+ "attachmentSize",
+ "attachmentListContext"
+ );
+
+ await subtest_attachment_right_click(
+ "attachmentToggle",
+ "attachment-toolbar-context-menu"
+ );
+ await subtest_attachment_right_click(
+ "attachmentSaveAllMultiple",
+ "attachment-toolbar-context-menu"
+ );
+ await subtest_attachment_right_click(
+ "attachmentBar",
+ "attachment-toolbar-context-menu"
+ );
+});
+
+/**
+ * Test that clicking on various elements in the attachment bar toggles the
+ * attachment list.
+ *
+ * @param elementId the id of the element to click
+ */
+function subtest_attachment_list_toggle(elementId) {
+ let aboutMessage = get_about_message();
+ let attachmentList = aboutMessage.document.getElementById("attachmentList");
+ let element = aboutMessage.document.getElementById(elementId);
+
+ EventUtils.synthesizeMouseAtCenter(element, { clickCount: 1 }, aboutMessage);
+ Assert.ok(
+ !attachmentList.collapsed,
+ `Attachment list should be expanded after clicking ${elementId}!`
+ );
+ assert_attachment_list_focused();
+
+ EventUtils.synthesizeMouseAtCenter(element, { clickCount: 1 }, aboutMessage);
+ Assert.ok(
+ attachmentList.collapsed,
+ `Attachment list should be collapsed after clicking ${elementId} again!`
+ );
+ assert_message_pane_focused();
+}
+
+add_task(async function test_attachment_list_expansion() {
+ await be_in_folder(folder);
+
+ select_click_row(1);
+ assert_selected_and_displayed(1);
+
+ let aboutMessage = get_about_message();
+ Assert.ok(
+ aboutMessage.document.getElementById("attachmentList").collapsed,
+ "Attachment list should start out collapsed!"
+ );
+
+ subtest_attachment_list_toggle("attachmentToggle");
+ subtest_attachment_list_toggle("attachmentIcon");
+ subtest_attachment_list_toggle("attachmentCount");
+ subtest_attachment_list_toggle("attachmentSize");
+ subtest_attachment_list_toggle("attachmentBar");
+
+ // Ensure that clicking the "Save All" button doesn't expand the attachment
+ // list.
+ let dm = aboutMessage.document.querySelector(
+ "#attachmentSaveAllSingle .toolbarbutton-menubutton-dropmarker"
+ );
+ EventUtils.synthesizeMouseAtCenter(dm, { clickCount: 1 }, aboutMessage);
+ Assert.ok(
+ aboutMessage.document.getElementById("attachmentList").collapsed,
+ "Attachment list should be collapsed after clicking save button!"
+ );
+}).skip();
+
+add_task(async function test_attachment_list_starts_expanded() {
+ ensure_starts_expanded(true);
+ await be_in_folder(folder);
+
+ select_click_row(2);
+ assert_selected_and_displayed(2);
+
+ Assert.ok(
+ !get_about_message().document.getElementById("attachmentList").collapsed,
+ "Attachment list should start out expanded!"
+ );
+});
+
+add_task(async function test_selected_attachments_are_cleared() {
+ ensure_starts_expanded(false);
+ await be_in_folder(folder);
+ // First, select the message with two attachments.
+ select_click_row(3);
+
+ // Expand the attachment list.
+ let aboutMessage = get_about_message();
+ EventUtils.synthesizeMouseAtCenter(
+ aboutMessage.document.getElementById("attachmentToggle"),
+ { clickCount: 1 },
+ aboutMessage
+ );
+
+ // Select both the attachments.
+ let attachmentList = aboutMessage.document.getElementById("attachmentList");
+ Assert.equal(
+ attachmentList.selectedItems.length,
+ 1,
+ "On first load the first item should be selected"
+ );
+
+ // We can just click on the first element, but the second one needs a
+ // ctrl-click (or cmd-click for those Mac-heads among us).
+ EventUtils.synthesizeMouseAtCenter(
+ attachmentList.children[0],
+ { clickCount: 1 },
+ aboutMessage
+ );
+ EventUtils.synthesizeMouse(
+ attachmentList.children[1],
+ 5,
+ 5,
+ { accelKey: true },
+ aboutMessage
+ );
+
+ Assert.equal(
+ attachmentList.selectedItems.length,
+ 2,
+ "We had the wrong number of selected items after selecting some!"
+ );
+
+ // Switch to the message with one attachment, and make sure there are no
+ // selected attachments.
+ select_click_row(2);
+
+ // Expand the attachment list again.
+ EventUtils.synthesizeMouseAtCenter(
+ aboutMessage.document.getElementById("attachmentToggle"),
+ { clickCount: 1 },
+ aboutMessage
+ );
+
+ Assert.equal(
+ attachmentList.selectedItems.length,
+ 1,
+ "After loading a new message the first item should be selected"
+ );
+});
+
+add_task(async function test_select_all_attachments_key() {
+ await be_in_folder(folder);
+
+ // First, select the message with two attachments.
+ select_none();
+ select_click_row(3);
+
+ // Expand the attachment list.
+ let aboutMessage = get_about_message();
+ EventUtils.synthesizeMouseAtCenter(
+ aboutMessage.document.getElementById("attachmentToggle"),
+ { clickCount: 1 },
+ aboutMessage
+ );
+
+ let attachmentList = aboutMessage.document.getElementById("attachmentList");
+ attachmentList.focus();
+ EventUtils.synthesizeKey("a", { accelKey: true }, aboutMessage);
+ Assert.equal(
+ attachmentList.selectedItems.length,
+ 2,
+ "Should have selected all attachments!"
+ );
+});
+
+add_task(async function test_delete_attachment_key() {
+ await be_in_folder(folder);
+
+ // First, select the message with two attachments.
+ select_none();
+ select_click_row(3);
+
+ // Expand the attachment list.
+ assert_selected_and_displayed(3);
+ let aboutMessage = get_about_message();
+ if (aboutMessage.document.getElementById("attachmentList").collapsed) {
+ EventUtils.synthesizeMouseAtCenter(
+ aboutMessage.document.getElementById("attachmentToggle"),
+ { clickCount: 1 },
+ aboutMessage
+ );
+ }
+ let firstAttachment =
+ aboutMessage.document.getElementById("attachmentList").firstElementChild;
+ EventUtils.synthesizeMouseAtCenter(
+ firstAttachment,
+ { clickCount: 1 },
+ aboutMessage
+ );
+
+ // Try deleting with the delete key
+ let dialogPromise = BrowserTestUtils.promiseAlertDialog("cancel");
+ firstAttachment.focus();
+ EventUtils.synthesizeKey("VK_DELETE", {}, aboutMessage);
+ await dialogPromise;
+
+ // Try deleting with the shift-delete key combo.
+ dialogPromise = BrowserTestUtils.promiseAlertDialog("cancel");
+ firstAttachment.focus();
+ EventUtils.synthesizeKey("VK_DELETE", { shiftKey: true }, aboutMessage);
+ await dialogPromise;
+}).skip();
+
+add_task(async function test_attachments_compose_menu() {
+ await be_in_folder(folder);
+
+ // First, select the message with two attachments.
+ select_none();
+ select_click_row(3);
+
+ let cwc = open_compose_with_forward();
+ let attachment = cwc.window.document.getElementById("attachmentBucket");
+
+ // On Linux and OSX, focus events don't seem to be sent to child elements properly if
+ // the parent window is not focused. This causes some random oranges for us.
+ // We use the force_focus function to "cheat" a bit, and trigger the function
+ // that focusing normally would fire. We do normal focusing for Windows.
+ function force_focus(aId) {
+ let element = cwc.window.document.getElementById(aId);
+ element.focus();
+
+ if (["linux", "macosx"].includes(AppConstants.platform)) {
+ // First, call the window's default controller's function.
+ cwc.window.defaultController.isCommandEnabled("cmd_delete");
+
+ // Walk up the DOM tree and call isCommandEnabled on the first controller
+ // that supports "cmd_delete".
+ while (element != cwc.window.document) {
+ // NOTE: html elements (like body) don't have controllers.
+ let numControllers = element.controllers?.getControllerCount() || 0;
+ for (let i = 0; numControllers; i++) {
+ let currController = element.controllers.getControllerAt(i);
+ if (currController.supportsCommand("cmd_delete")) {
+ currController.isCommandEnabled("cmd_delete");
+ return;
+ }
+ }
+ element = element.parentNode;
+ }
+ }
+ }
+
+ // Click on a portion of the attachmentBucket to focus on it. The last
+ // attachment should be selected since we don't handle any action on an empty
+ // bucket, and we always ensure that the last attached file is visible.
+ force_focus("attachmentBucket");
+
+ Assert.equal(
+ "Remove Attachment",
+ cwc.window.document.getElementById("cmd_delete").getAttribute("label"),
+ "attachmentBucket with last attachment is focused!"
+ );
+
+ // We opened a message with 2 attachments, so index 1 should be focused.
+ Assert.equal(attachment.selectedIndex, 1, "Last attachment is focused!");
+
+ // Select 1 attachment, and
+ // focus the subject to see the label change and to execute isCommandEnabled
+ attachment.selectedIndex = 0;
+ force_focus("msgSubject");
+ Assert.equal(
+ "Delete",
+ cwc.window.document.getElementById("cmd_delete").getAttribute("label"),
+ "attachmentBucket is not focused!"
+ );
+
+ // Focus back to the attachmentBucket
+ force_focus("attachmentBucket");
+ Assert.equal(
+ "Remove Attachment",
+ cwc.window.document.getElementById("cmd_delete").getAttribute("label"),
+ "Only 1 attachment is selected!"
+ );
+
+ // Select multiple attachments, and focus the identity for the same purpose
+ attachment.selectAll();
+ force_focus("msgIdentity");
+ Assert.equal(
+ "Delete",
+ cwc.window.document.getElementById("cmd_delete").getAttribute("label"),
+ "attachmentBucket is not focused!"
+ );
+
+ // Focus back to the attachmentBucket
+ force_focus("attachmentBucket");
+ Assert.equal(
+ "Remove Attachments",
+ cwc.window.document.getElementById("cmd_delete").getAttribute("label"),
+ "Multiple attachments are selected!"
+ );
+
+ close_compose_window(cwc);
+});
+
+add_task(async function test_delete_from_toolbar() {
+ await be_in_folder(folder);
+
+ // First, select the message with two attachments.
+ select_none();
+ select_click_row(3);
+
+ // Expand the attachment list.
+ assert_selected_and_displayed(3);
+ let aboutMessage = get_about_message();
+ if (aboutMessage.document.getElementById("attachmentList").collapsed) {
+ EventUtils.synthesizeMouseAtCenter(
+ aboutMessage.document.getElementById("attachmentToggle"),
+ { clickCount: 1 },
+ aboutMessage
+ );
+ }
+
+ let firstAttachment =
+ aboutMessage.document.getElementById("attachmentList").firstElementChild;
+ EventUtils.synthesizeMouseAtCenter(
+ firstAttachment,
+ { clickCount: 1 },
+ aboutMessage
+ );
+
+ // Make sure clicking the "Delete" toolbar button with an attachment focused
+ // deletes the *message*.
+ plan_to_wait_for_folder_events("DeleteOrMoveMsgCompleted");
+ EventUtils.synthesizeMouseAtCenter(
+ aboutMessage.document.getElementById("hdrTrashButton"),
+ { clickCount: 1 },
+ aboutMessage
+ );
+ wait_for_folder_events();
+}).skip();
+
+registerCleanupFunction(() => {
+ // Remove created folders.
+ folder.deleteSelf(null);
+});