From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../mail/test/browser/composition/browser_focus.js | 523 +++++++++++++++++++++ 1 file changed, 523 insertions(+) create mode 100644 comm/mail/test/browser/composition/browser_focus.js (limited to 'comm/mail/test/browser/composition/browser_focus.js') diff --git a/comm/mail/test/browser/composition/browser_focus.js b/comm/mail/test/browser/composition/browser_focus.js new file mode 100644 index 0000000000..852f2e99cc --- /dev/null +++ b/comm/mail/test/browser/composition/browser_focus.js @@ -0,0 +1,523 @@ +/* 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 cycling through the focus of the 3pane's panes works correctly. + */ + +"use strict"; + +var { add_attachments, close_compose_window, open_compose_new_mail } = + ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm"); +var { mc } = ChromeUtils.import( + "resource://testing-common/mozmill/FolderDisplayHelpers.jsm" +); + +requestLongerTimeout(3); + +/** + * Test the cycling of focus in the composition window through (Shift+)F6. + * + * @param {MozMillController} controller - Controller for the compose window. + * @param {object} options - Options to set for the test. + * @param {boolean} options.useTab - Whether to use Ctrl+Tab instead of F6. + * @param {boolean} options.attachment - Whether to add an attachment. + * @param {boolean} options.notifications - Whether to show notifications. + * @param {boolean} options.languageButton - Whether to show the language + * menu button. + * @param {boolean} options.contacts - Whether to show the contacts side pane. + * @param {string} otherHeader - The name of the custom header to show. + */ +async function checkFocusCycling(controller, options) { + let win = controller.window; + let doc = win.document; + let contactDoc; + let contactsInput; + let identityElement = doc.getElementById("msgIdentity"); + let bccButton = doc.getElementById("addr_bccShowAddressRowButton"); + let toInput = doc.getElementById("toAddrInput"); + let bccInput = doc.getElementById("bccAddrInput"); + let subjectInput = doc.getElementById("msgSubject"); + let editorElement = doc.getElementById("messageEditor"); + let attachmentElement = doc.getElementById("attachmentBucket"); + let extraMenuButton = doc.getElementById("extraAddressRowsMenuButton"); + let languageButton = doc.getElementById("languageStatusButton"); + let firstNotification; + let secondNotification; + + if (Services.ww.activeWindow != win) { + // Wait for the window to be in focus before beginning. + await BrowserTestUtils.waitForEvent(win, "activate"); + } + + let key = options.useTab ? "VK_TAB" : "VK_F6"; + let goForward = () => + EventUtils.synthesizeKey(key, { ctrlKey: options.useTab }, win); + let goBackward = () => + EventUtils.synthesizeKey( + key, + { ctrlKey: options.useTab, shiftKey: true }, + win + ); + + if (options.attachment) { + add_attachments(controller, "https://www.mozilla.org/"); + } + + if (options.contacts) { + // Open the contacts sidebar. + EventUtils.synthesizeKey("VK_F9", {}, win); + contactsInput = await TestUtils.waitForCondition(() => { + contactDoc = doc.getElementById("contactsBrowser").contentDocument; + return contactDoc.getElementById("peopleSearchInput"); + }, "Waiting for the contacts pane to load"); + } + + if (options.languageButton) { + // languageButton only shows if we have more than one dictionary, but we + // will show it anyway. + languageButton.hidden = false; + } + + // Show the bcc row by clicking the button. + EventUtils.synthesizeMouseAtCenter(bccButton, {}, win); + + // Show the custom row. + let otherRow = doc.querySelector( + `.address-row[data-recipienttype="${options.otherHeader}"]` + ); + // Show the input. + let menu = doc.getElementById("extraAddressRowsMenu"); + let promise = BrowserTestUtils.waitForEvent(menu, "popupshown"); + EventUtils.synthesizeMouseAtCenter(extraMenuButton, {}, win); + await promise; + promise = BrowserTestUtils.waitForEvent(menu, "popuphidden"); + menu.activateItem(doc.getElementById(otherRow.dataset.showSelfMenuitem)); + await promise; + let otherHeaderInput = otherRow.querySelector(".address-row-input"); + + // Move the initial focus back to the To input. + toInput.focus(); + + if (options.notifications) { + // Exceed the recipient threshold. + Assert.equal( + win.gComposeNotification.allNotifications.length, + 0, + "Should be no initial notifications" + ); + let notificationPromise = TestUtils.waitForCondition( + () => win.gComposeNotification.allNotifications[0], + "First notification shown" + ); + EventUtils.sendString("a@b.org,c@d.org", win); + EventUtils.synthesizeKey("KEY_Enter", {}, win); + firstNotification = await notificationPromise; + } + + // We start on the addressing widget and go from there. + + // From To to Subject. + goForward(); + Assert.ok(bccInput.matches(":focus"), "forward to bcc row"); + goForward(); + Assert.ok(otherHeaderInput.matches(":focus"), "forward to other row"); + goForward(); + Assert.ok(subjectInput.matches(":focus"), "forward to subject"); + + if (options.notifications && !options.attachment) { + // Include an attachment key word in the subject. + let notificationPromise = TestUtils.waitForCondition(() => { + let notifications = win.gComposeNotification.allNotifications; + if (notifications.length != 2) { + return null; + } + return notifications[1]; + }, "Second notification shown"); + EventUtils.sendString("My attached file", win); + secondNotification = await notificationPromise; + Assert.notEqual( + firstNotification, + secondNotification, + "New notification shown second" + ); + } + + // From Subject to Message Body. + goForward(); + // The editor's body will not match ":focus", even when it has focus, instead, + // we use the parent window's activeElement. + Assert.equal(editorElement, doc.activeElement, "forward to message body"); + + // From Message Body to Attachment bucket if visible. + goForward(); + if (options.attachment) { + Assert.ok(attachmentElement.matches(":focus"), "forward to attachments"); + goForward(); + } + + if (options.notifications) { + Assert.equal( + firstNotification, + doc.activeElement, + "forward to notification" + ); + goForward(); + } + + // From Message Body (or Attachment bucket) to Language button. + if (options.languageButton) { + Assert.ok(languageButton.matches(":focus"), "forward to status bar"); + goForward(); + } + + // From Language button to contacts pane. + if (options.contacts) { + Assert.ok( + contactsInput.matches(":focus-within"), + "forward to contacts pane" + ); + goForward(); + } + + // From contacts pane to identity. + Assert.ok(identityElement.matches(":focus"), "forward to 'from' row"); + + // Back to the To input. + goForward(); + Assert.ok(toInput.matches(":focus"), "forward to 'to' row"); + + // Reverse the direction. + + goBackward(); + Assert.ok(identityElement.matches(":focus"), "backward to 'from' row"); + + goBackward(); + if (options.contacts) { + Assert.ok( + contactsInput.matches(":focus-within"), + "backward to contacts pane" + ); + goBackward(); + } + + if (options.languageButton) { + Assert.ok(languageButton.matches(":focus"), "backward to status bar"); + goBackward(); + } + + if (options.notifications) { + Assert.equal( + firstNotification, + doc.activeElement, + "backward to notification" + ); + goBackward(); + } + + if (options.attachment) { + Assert.ok(attachmentElement.matches(":focus"), "backward to attachments"); + goBackward(); + } + + Assert.equal(editorElement, doc.activeElement, "backward to message body"); + goBackward(); + Assert.ok(subjectInput.matches(":focus"), "backward to subject"); + goBackward(); + Assert.ok(otherHeaderInput.matches(":focus"), "backward to other row"); + goBackward(); + Assert.ok(bccInput.matches(":focus"), "backward to bcc row"); + goBackward(); + + Assert.ok(toInput.matches(":focus"), "backward to 'to' row"); + + // Now test some other elements that aren't the main focus point of their + // areas. I.e. focusable elements that are within an area, but are not + // focused when the area is *entered* through F6 or Ctrl+Tab. When these + // elements have focus, we still want F6 or Ctrl+Tab to move the focus to the + // neighbouring area. + + // Focus the close button. + let bccCloseButton = doc.querySelector("#addressRowBcc .remove-field-button"); + bccCloseButton.focus(); + goForward(); + Assert.ok( + otherHeaderInput.matches(":focus"), + "from close bcc button to other row" + ); + goBackward(); + // The input is focused on return. + Assert.ok(bccInput.matches(":focus"), "back to bcc row"); + // Same the other way. + bccCloseButton.focus(); + goBackward(); + Assert.ok(toInput.matches(":focus"), "from close bcc button to 'to' row"); + + if (options.contacts) { + let addressBookList = contactDoc.getElementById("addressbookList"); + addressBookList.focus(); + goForward(); + Assert.ok( + identityElement.matches(":focus"), + "from addressbook selector to 'from' row" + ); + goBackward(); + // The input is focused on return. + Assert.ok(contactsInput.matches(":focus-within"), "back to contacts input"); + // Same the other way. + addressBookList.focus(); + goBackward(); + if (options.languageButton) { + Assert.ok( + languageButton.matches(":focus"), + "from addressbook selector to status bar" + ); + } else if (options.notifications) { + Assert.equal( + firstNotification, + doc.activeElement, + "from addressbook selector to notification" + ); + } else if (options.attachment) { + Assert.ok( + attachmentElement.matches(":focus"), + "from addressbook selector to attachments" + ); + } else { + Assert.equal( + editorElement, + doc.activeElement, + "from addressbook selector to message body" + ); + } + } + + // Cc button and extra address rows menu button are in the same area as the + // message identity. + let ccButton = doc.getElementById("addr_ccShowAddressRowButton"); + ccButton.focus(); + goBackward(); + if (options.contacts) { + Assert.ok( + contactsInput.matches(":focus-within"), + "from Cc button to contacts" + ); + } else if (options.languageButton) { + Assert.ok(languageButton.matches(":focus"), "from Cc button to status bar"); + } else if (options.notifications) { + Assert.equal( + firstNotification, + doc.activeElement, + "from Cc button to notification" + ); + } else if (options.attachment) { + Assert.ok( + attachmentElement.matches(":focus"), + "from Cc button to attachments" + ); + } else { + Assert.equal( + editorElement, + doc.activeElement, + "from Cc button to message body" + ); + } + goForward(); + // Return to the input. + Assert.ok(identityElement.matches(":focus"), "back to 'from' row"); + + // Try in the other direction with the extra menu button. + extraMenuButton.focus(); + goForward(); + Assert.ok(toInput.matches(":focus"), "from extra menu button to 'to' row"); + goBackward(); + // Return to the input. + Assert.ok(identityElement.matches(":focus"), "back to 'from' row again"); + + if (options.attachment) { + let attachmentArea = doc.getElementById("attachmentArea"); + let attachmentSummary = attachmentArea.querySelector("summary"); + Assert.ok(attachmentArea.open, "Attachment area should be open"); + for (let open of [true, false]) { + if (open) { + Assert.ok(attachmentArea.open, "Attachment area should be open"); + } else { + // Close the attachment bucket. In this case, the focus will move to the + // summary element (where the bucket can be shown again). + EventUtils.synthesizeMouseAtCenter(attachmentSummary, {}, win); + Assert.ok(!attachmentArea.open, "Attachment area should be closed"); + } + + // Focus the attachmentSummary. + attachmentSummary.focus(); + goBackward(); + Assert.equal( + editorElement, + doc.activeElement, + `backward from attachment summary (open: ${open}) to message body` + ); + goForward(); + if (open) { + // Focus returns to the bucket when it is open. + Assert.ok( + attachmentElement.matches(":focus"), + "forward to attachment bucket" + ); + } else { + // Otherwise, it returns to the summary. + Assert.ok( + attachmentSummary.matches(":focus"), + "forward to attachment summary" + ); + } + // Try reverse. + attachmentSummary.focus(); + goForward(); + if (options.notifications) { + Assert.equal( + firstNotification, + doc.activeElement, + `forward from attachment summary (open: ${open}) to notification` + ); + } else if (options.languageButton) { + Assert.ok( + languageButton.matches(":focus"), + `forward from attachment summary (open: ${open}) to status bar` + ); + } else if (options.contacts) { + Assert.ok( + contactsInput.matches(":focus-within"), + `forward from attachment summary (open: ${open}) to contacts pane` + ); + } else { + Assert.ok( + identityElement.matches(":focus"), + `forward from attachment summary (open: ${open}) to 'from' row` + ); + } + goBackward(); + if (open) { + Assert.ok( + attachmentElement.matches(":focus"), + "return to attachment bucket" + ); + } else { + Assert.ok( + attachmentSummary.matches(":focus"), + "return to attachment summary" + ); + // Open again. + EventUtils.synthesizeMouseAtCenter(attachmentSummary, {}, win); + Assert.ok(attachmentArea.open, "Attachment area should be open again"); + } + } + } + + if (options.notifications) { + // Focus inside the notification. + let closeButton = (secondNotification || firstNotification).closeButton; + closeButton.focus(); + + goBackward(); + + if (options.attachment) { + Assert.ok( + attachmentElement.matches(":focus"), + "backward from notification button to attachments" + ); + } else { + Assert.equal( + editorElement, + doc.activeElement, + "backward from notification button to message body" + ); + } + goForward(); + // Go to the first notification. + Assert.equal( + firstNotification, + doc.activeElement, + "forward to the first notification" + ); + + // Try reverse. + closeButton.focus(); + goForward(); + if (options.languageButton) { + Assert.ok( + languageButton.matches(":focus"), + "forward from notification button to status bar" + ); + } else if (options.contacts) { + Assert.ok( + contactsInput.matches(":focus-within"), + "forward from notification button to contacts pane" + ); + } else { + Assert.ok( + identityElement.matches(":focus"), + "forward from notification button to 'from' row" + ); + } + goBackward(); + Assert.equal( + firstNotification, + doc.activeElement, + "return to the first notification" + ); + } + + // Contacts pane is persistent, so we close it again. + if (options.contacts) { + // Close the contacts sidebar. + EventUtils.synthesizeKey("VK_F9", {}, win); + } +} + +add_task(async function test_jump_focus() { + // Make sure the accessibility tabfocus is set to 7 to enable normal Tab + // focus on non-input field elements. This is necessary only for macOS as + // the default value is 2 instead of the default 7 used on Windows and Linux. + Services.prefs.setIntPref("accessibility.tabfocus", 7); + let prevHeader = Services.prefs.getCharPref("mail.compose.other.header"); + let prevThreshold = Services.prefs.getIntPref( + "mail.compose.warn_public_recipients.threshold" + ); + // Set two custom headers, but only one is shown. + Services.prefs.setCharPref( + "mail.compose.other.header", + "X-Header2,X-Header1" + ); + Services.prefs.setIntPref("mail.compose.warn_public_recipients.threshold", 2); + for (let useTab of [false, true]) { + for (let attachment of [false, true]) { + for (let notifications of [false, true]) { + for (let languageButton of [false, true]) { + for (let contacts of [false, true]) { + let options = { + useTab, + attachment, + notifications, + languageButton, + contacts, + otherHeader: "X-Header1", + }; + info(`Test run: ${JSON.stringify(options)}`); + let controller = open_compose_new_mail(); + await checkFocusCycling(controller, options); + close_compose_window(controller); + } + } + } + } + } + + // Reset the preferences. + Services.prefs.clearUserPref("accessibility.tabfocus"); + Services.prefs.setCharPref("mail.compose.other.header", prevHeader); + Services.prefs.setIntPref( + "mail.compose.warn_public_recipients.threshold", + prevThreshold + ); +}); -- cgit v1.2.3