diff options
Diffstat (limited to 'comm/mail/test/browser/attachment/browser_attachment.js')
-rw-r--r-- | comm/mail/test/browser/attachment/browser_attachment.js | 764 |
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); +}); |