/* 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 spaces toolbar features. */ /* globals gSpacesToolbar */ var folderA; var folderB; var testAccount; add_setup(function () { // Set up two folders. window.MailServices.accounts.createLocalMailAccount(); testAccount = window.MailServices.accounts.accounts[0]; let rootFolder = testAccount.incomingServer.rootFolder; rootFolder.createSubfolder("spacesToolbarA", null); folderA = rootFolder.findSubFolder("spacesToolbarA"); rootFolder.createSubfolder("spacesToolbarB", null); folderB = rootFolder.findSubFolder("spacesToolbarB"); }); registerCleanupFunction(async () => { window.MailServices.accounts.removeAccount(testAccount, true); // Close all opened tabs. let tabmail = document.getElementById("tabmail"); tabmail.closeOtherTabs(tabmail.tabInfo[0]); // Reset the spaces toolbar to its default visible state. window.gSpacesToolbar.toggleToolbar(false); // Reset the menubar visibility. let menubar = document.getElementById("toolbar-menubar"); menubar.removeAttribute("autohide"); menubar.removeAttribute("inactive"); await new Promise(resolve => requestAnimationFrame(resolve)); }); async function assertMailShown(win = window) { await TestUtils.waitForCondition( () => win.document.getElementById("tabmail").currentTabInfo.mode.name == "mail3PaneTab", "The mail tab should be visible" ); } async function assertAddressBookShown(win = window) { await TestUtils.waitForCondition(() => { let panel = win.document.querySelector( // addressBookTabWrapper0, addressBookTabWrapper1, etc "#tabpanelcontainer > [id^=addressBookTabWrapper][selected]" ); if (!panel) { return false; } let browser = panel.querySelector("[id^=addressBookTabBrowser]"); return browser.contentDocument.readyState == "complete"; }, "The address book tab should be visible and loaded"); } async function assertChatShown(win = window) { await TestUtils.waitForCondition( () => win.document.getElementById("chatTabPanel").hasAttribute("selected"), "The chat tab should be visible" ); } async function assertCalendarShown(win = window) { await TestUtils.waitForCondition(() => { return ( win.document .getElementById("calendarTabPanel") .hasAttribute("selected") && !win.document.getElementById("calendar-view-box").collapsed ); }, "The calendar view should be visible"); } async function assertTasksShown(win = window) { await TestUtils.waitForCondition(() => { return ( win.document .getElementById("calendarTabPanel") .hasAttribute("selected") && !win.document.getElementById("calendar-task-box").collapsed ); }, "The task view should be visible"); } async function assertSettingsShown(win = window) { await TestUtils.waitForCondition(() => { let panel = win.document.querySelector( // preferencesTabWrapper0, preferencesTabWrapper1, etc "#tabpanelcontainer > [id^=preferencesTabWrapper][selected]" ); if (!panel) { return false; } let browser = panel.querySelector("[id^=preferencesTabBrowser]"); return browser.contentDocument.readyState == "complete"; }, "The settings tab should be visible and loaded"); } async function assertContentShown(url, win = window) { await TestUtils.waitForCondition(() => { let panel = win.document.querySelector( // contentTabWrapper0, contentTabWrapper1, etc "#tabpanelcontainer > [id^=contentTabWrapper][selected]" ); if (!panel) { return false; } let doc = panel.querySelector("[id^=contentTabBrowser]").contentDocument; return doc.URL == url && doc.readyState == "complete"; }, `The selected content tab should show ${url}`); } async function sub_test_cycle_through_primary_tabs() { // We can't really cycle through all buttons and tabs with a simple for loop // since some tabs are actual collapsing views and other tabs are separate // pages. We can improve this once the new 3pane tab is actually a standalone // tab. // Switch to address book. EventUtils.synthesizeMouseAtCenter( document.getElementById("addressBookButton"), {}, window ); await assertAddressBookShown(); // Switch to calendar. EventUtils.synthesizeMouseAtCenter( document.getElementById("calendarButton"), {}, window ); await assertCalendarShown(); // Switch to Mail. EventUtils.synthesizeMouseAtCenter( document.getElementById("mailButton"), {}, window ); await assertMailShown(); // Switch to Tasks. EventUtils.synthesizeMouseAtCenter( document.getElementById("tasksButton"), {}, window ); await assertTasksShown(); // Switch to chat. EventUtils.synthesizeMouseAtCenter( document.getElementById("chatButton"), {}, window ); await assertChatShown(); // Switch to Settings. EventUtils.synthesizeMouseAtCenter( document.getElementById("settingsButton"), {}, window ); await assertSettingsShown(); // Switch to Mail. EventUtils.synthesizeMouseAtCenter( document.getElementById("mailButton"), {}, window ); await assertMailShown(); window.tabmail.closeOtherTabs(window.tabmail.tabInfo[0]); } add_task(async function testSpacesToolbarVisibility() { let spacesToolbar = document.getElementById("spacesToolbar"); let toggleButton = document.getElementById("spacesToolbarReveal"); let pinnedButton = document.getElementById("spacesPinnedButton"); Assert.ok(spacesToolbar, "The spaces toolbar exists"); let assertVisibility = async function (isHidden, msg) { await TestUtils.waitForCondition( () => spacesToolbar.hidden == !isHidden, `The spaces toolbar should be ${!isHidden ? "visible" : "hidden"}: ${msg}` ); await TestUtils.waitForCondition( () => toggleButton.hidden == isHidden, `The toggle button should be ${isHidden ? "hidden" : "visible"}: ${msg}` ); await TestUtils.waitForCondition( () => pinnedButton.hidden == isHidden, `The pinned button should be ${isHidden ? "hidden" : "visible"}: ${msg}` ); }; async function toggleVisibilityWithAppMenu(expectChecked) { let appMenu = document.getElementById("appMenu-popup"); let menuShownPromise = BrowserTestUtils.waitForEvent(appMenu, "popupshown"); EventUtils.synthesizeMouseAtCenter( document.getElementById("button-appmenu"), {}, window ); await menuShownPromise; let viewShownPromise = BrowserTestUtils.waitForEvent( appMenu.querySelector("#appMenu-viewView"), "ViewShown" ); EventUtils.synthesizeMouseAtCenter( appMenu.querySelector("#appmenu_View"), {}, window ); await viewShownPromise; let toolbarShownPromise = BrowserTestUtils.waitForEvent( appMenu.querySelector("#appMenu-toolbarsView"), "ViewShown" ); EventUtils.synthesizeMouseAtCenter( appMenu.querySelector("#appmenu_Toolbars"), {}, window ); await toolbarShownPromise; let appMenuButton = document.getElementById("appmenu_spacesToolbar"); Assert.equal( appMenuButton.checked, expectChecked, `The app menu item should ${expectChecked ? "not " : ""}be checked` ); EventUtils.synthesizeMouseAtCenter(appMenuButton, {}, window); // Close the appmenu. EventUtils.synthesizeMouseAtCenter( document.getElementById("button-appmenu"), {}, window ); } await assertVisibility(true, "on initial load"); // Collapse with a mouse click. let activeElement = document.activeElement; let collapseButton = document.getElementById("collapseButton"); EventUtils.synthesizeMouseAtCenter(collapseButton, {}, window); await assertVisibility(false, "after clicking collapse button"); await toggleVisibilityWithAppMenu(false); await assertVisibility(true, "after revealing with the app menu"); // We already clicked the collapse button, so it should already be the // focusButton for the gSpacesToolbar, and thus focusable. collapseButton.focus(); Assert.ok( collapseButton.matches(":focus"), "Collapse button should be focusable" ); // Hide the spaces toolbar using the collapse button, which already has focus. EventUtils.synthesizeKey(" ", {}, window); await assertVisibility(false, "after closing with space key press"); Assert.ok( pinnedButton.matches(":focus"), "Pinned button should be focused after closing with a key press" ); // Show using the pinned button menu. let pinnedMenu = document.getElementById("spacesButtonMenuPopup"); let pinnedMenuShown = BrowserTestUtils.waitForEvent(pinnedMenu, "popupshown"); EventUtils.synthesizeKey("KEY_Enter", {}, window); await pinnedMenuShown; pinnedMenu.activateItem(document.getElementById("spacesPopupButtonReveal")); await assertVisibility(true, "after opening with pinned menu"); Assert.ok( collapseButton.matches(":focus"), "Collapse button should be focused again after showing with the pinned menu" ); // Move focus to the mail button. let mailButton = document.getElementById("mailButton"); // Loop around from the collapse button to the mailButton. EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); Assert.ok( mailButton.matches(":focus"), "Mail button should become focused after pressing key down" ); Assert.ok( spacesToolbar.matches(":focus-within"), "Spaces toolbar should contain the focus" ); // Now move focus elsewhere. EventUtils.synthesizeKey("KEY_Tab", {}, window); activeElement = document.activeElement; Assert.ok( !mailButton.matches(":focus"), "Mail button should no longer be focused" ); Assert.ok( !spacesToolbar.matches(":focus-within"), "Spaces toolbar should no longer contain the focus" ); // Hide the spaces toolbar using the app menu. await toggleVisibilityWithAppMenu(true); await assertVisibility(false, "after hiding with the app menu"); // macOS by default doesn't move the focus when clicking on toolbar buttons. if (AppConstants.platform != "macosx") { Assert.notEqual( document.activeElement, activeElement, "The focus moved from the previous element" ); // Focus should be on the main app menu since we used the mouse to toggle the // spaces toolbar. Assert.equal( document.activeElement, document.getElementById("button-appmenu"), "Active element is on the app menu" ); } else { Assert.equal( document.activeElement, activeElement, "The focus didn't move from the previous element" ); } // Now click the status bar toggle button to reveal the toolbar again. toggleButton.focus(); Assert.ok( toggleButton.matches(":focus"), "Toggle button should be focusable" ); EventUtils.synthesizeKey("KEY_Enter", {}, window); await assertVisibility(true, "after showing with the toggle button"); // Focus is restored to the mailButton. Assert.ok( mailButton.matches(":focus"), "Mail button should become focused again" ); // Clicked buttons open or move to the correct tab, starting with just one tab // open. await sub_test_cycle_through_primary_tabs(); }); add_task(async function testSpacesToolbarContextMenu() { let tabmail = document.getElementById("tabmail"); let firstMailTabInfo = tabmail.currentTabInfo; firstMailTabInfo.folder = folderB; // Fetch context menu elements. let contextMenu = document.getElementById("spacesContextMenu"); let newTabItem = document.getElementById("spacesContextNewTabItem"); let newWindowItem = document.getElementById("spacesContextNewWindowItem"); let settingsMenu = document.getElementById("settingsContextMenu"); let settingsItem = document.getElementById("settingsContextOpenSettingsItem"); let accountItem = document.getElementById( "settingsContextOpenAccountSettingsItem" ); let addonsItem = document.getElementById("settingsContextOpenAddonsItem"); /** * Open the context menu, test its state, select an action and wait for it to * close. * * @param {object} input - Input data. * @param {Element} input.button - The button whose context menu should be * opened. * @param {Element} [input.item] - The context menu item to select. Either * this or switchItem must be given. * @param {number} [input.switchItem] - The nth switch-to-tab item to select. * @param {object} expect - The expected state of the context menu when * opened. * @param {boolean} [expect.settings=false] - Whether we expect the settings * context menu. If this is true, the other values are ignored. * @param {boolean} [expect.newTab=false] - Whether we expect the "Open in new * tab" item to be visible. * @param {boolean} [expect.newWindow=false] - Whether we expect the "Open in * new window" item to be visible. * @param {number} [expect.numSwitch=0] - The expected number of switch-to-tab * items. * @param {string} msg - A message to use in tests. */ async function useContextMenu(input, expect, msg) { let menu = expect.settings ? settingsMenu : contextMenu; let shownPromise = BrowserTestUtils.waitForEvent(menu, "popupshown"); EventUtils.synthesizeMouseAtCenter( input.button, { type: "contextmenu" }, window ); await shownPromise; let item = input.item; if (!expect.settings) { Assert.equal( BrowserTestUtils.is_visible(newTabItem), expect.newTab || false, `Open in new tab item visibility: ${msg}` ); Assert.equal( BrowserTestUtils.is_visible(newWindowItem), expect.newWindow || false, `Open in new window item visibility: ${msg}` ); let switchItems = menu.querySelectorAll(".switch-to-tab"); Assert.equal( switchItems.length, expect.numSwitch || 0, `Should have the expected number of switch items: ${msg}` ); if (!item) { item = switchItems[input.switchItem]; } } let hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden"); menu.activateItem(item); await hiddenPromise; } let tabScroll = document.getElementById("tabmail-arrowscrollbox").scrollbox; /** * Ensure the tab is scrolled into view. * * @param {MozTabmailTab} - The tab to scroll into view. */ async function scrollToTab(tab) { function tabInView() { let tabRect = tab.getBoundingClientRect(); let scrollRect = tabScroll.getBoundingClientRect(); return ( tabRect.left >= scrollRect.left && tabRect.right <= scrollRect.right ); } if (tabInView()) { info(`Tab ${tab.label} already in view`); return; } tab.scrollIntoView(); await TestUtils.waitForCondition( tabInView, "Tab should be scrolled into view: " + tab.label ); info(`Tab ${tab.label} was scrolled into view`); } let numTabs = 0; /** * Wait for and return the latest tab. * * This should be called every time a tab is created so the test can keep * track of the expected number of tabs. * * @returns {MozTabmailTab} - The last tab. */ async function waitForNewTab() { numTabs++; let tabs; await TestUtils.waitForCondition(() => { tabs = document.querySelectorAll("tab.tabmail-tab"); return tabs.length == numTabs; }, `Waiting for ${numTabs} tabs`); return tabs[numTabs - 1]; } /** * Close a tab and wait for it to close. * * This should be used alongside waitForNewTab so the test can keep track of * the expected number of tabs. * * @param {MozTabmailTab} - The tab to close. */ async function closeTab(tab) { numTabs--; await scrollToTab(tab); EventUtils.synthesizeMouseAtCenter( tab.querySelector(".tab-close-button"), {}, window ); await TestUtils.waitForCondition( () => document.querySelectorAll("tab.tabmail-tab").length == numTabs, "Waiting for tab to close" ); } let toolbar = document.getElementById("spacesToolbar"); /** * Verify the current tab and space match. * * @param {MozTabmailTab} tab - The expected tab. * @param {Element} spaceButton - The expected button to be shown as the * current space in the spaces toolbar. * @param {string} msg - A message to use in tests. */ async function assertTab(tab, spaceButton, msg) { await TestUtils.waitForCondition( () => tab.selected, `Tab should be selected: ${msg}` ); let current = toolbar.querySelectorAll("button.current"); Assert.equal(current.length, 1, `Should have one current space: ${msg}`); Assert.equal( current[0], spaceButton, `Current button ${current[0].id} should match: ${msg}` ); } /** * Click on a tab and verify we have switched tabs and spaces. * * @param {MozTabmailTab} tab - The tab to click. * @param {Element} spaceButton - The expected button to be shown as the * current space after clicking the tab. * @param {string} msg - A message to use in tests. */ async function switchTab(tab, spaceButton, msg) { await scrollToTab(tab); EventUtils.synthesizeMouseAtCenter(tab, {}, window); await assertTab(tab, spaceButton, msg); } // -- Test initial tab -- let mailButton = document.getElementById("mailButton"); let firstTab = await waitForNewTab(); await assertTab(firstTab, mailButton, "First tab is mail tab"); await assertMailShown(); // -- Test spaces that only open one tab -- let calendarTab; let calendarButton = document.getElementById("calendarButton"); for (let { name, button, assertShown } of [ { name: "address book", button: document.getElementById("addressBookButton"), assertShown: assertAddressBookShown, }, { name: "calendar", button: calendarButton, assertShown: assertCalendarShown, }, { name: "tasks", button: document.getElementById("tasksButton"), assertShown: assertTasksShown, }, { name: "chat", button: document.getElementById("chatButton"), assertShown: assertChatShown, }, ]) { info(`Testing ${name} space`); // Only have option to open in new tab. await useContextMenu( { button, item: newTabItem }, { newTab: true }, `Opening ${name} tab` ); let newTab = await waitForNewTab(); if (name == "calendar") { calendarTab = newTab; } await assertTab(newTab, button, `Opened ${name} tab`); await assertShown(); // Only have option to switch tabs. // Doing this from the same tab does nothing. await useContextMenu( { button, switchItem: 0 }, { numSwitch: 1 }, `When ${name} tab is open` ); // Wait one tick to allow tabs to potentially change. await TestUtils.waitForTick(); // However, the same tab should remain shown. await assertShown(); // Switch to first tab and back. await switchTab(firstTab, mailButton, `${name} to first tab`); await assertMailShown(); await useContextMenu( { button, switchItem: 0 }, { numSwitch: 1 }, `Switching from first tab to ${name}` ); await assertTab(newTab, button, `Switched from first tab to ${name}`); await assertShown(); } // -- Test opening mail space in a new tab -- // Open new mail tabs whilst we are still in a non-mail tab. await useContextMenu( { button: mailButton, item: newTabItem }, { newWindow: true, newTab: true, numSwitch: 1 }, "Opening the second mail tab" ); let secondMailTab = await waitForNewTab(); await assertTab(secondMailTab, mailButton, "Opened second mail tab"); await assertMailShown(); // Displayed folder should be the same as in the first mail tab. let [, secondMailTabInfo] = tabmail._getTabContextForTabbyThing(secondMailTab); await TestUtils.waitForCondition( () => secondMailTabInfo.folder?.URI == folderB.URI, "Should display folder B in the second mail tab" ); secondMailTabInfo.folder = folderA; // Open a new mail tab whilst in a mail tab. await useContextMenu( { button: mailButton, item: newTabItem }, { newWindow: true, newTab: true, numSwitch: 2 }, "Opening the third mail tab" ); let thirdMailTab = await waitForNewTab(); await assertTab(thirdMailTab, mailButton, "Opened third mail tab"); await assertMailShown(); // Displayed folder should be the same as in the mail tab that was in view // when the context menu was opened, rather than the folder in the first tab. let [, thirdMailTabInfo] = tabmail._getTabContextForTabbyThing(thirdMailTab); await TestUtils.waitForCondition( () => thirdMailTabInfo.folder?.URI == folderA.URI, "Should display folder A in the third mail tab" ); // -- Test switching between the multiple mail tabs -- await useContextMenu( { button: mailButton, switchItem: 1 }, { newWindow: true, newTab: true, numSwitch: 3 }, "Switching to second mail tab" ); await assertTab(secondMailTab, mailButton, "Switch to second mail tab"); await assertMailShown(); await useContextMenu( { button: mailButton, switchItem: 0 }, { newWindow: true, newTab: true, numSwitch: 3 }, "Switching to first mail tab" ); await assertTab(firstTab, mailButton, "Switch to first mail tab"); await assertMailShown(); await switchTab(calendarTab, calendarButton, "First mail to calendar tab"); await useContextMenu( { button: mailButton, switchItem: 2 }, { newWindow: true, newTab: true, numSwitch: 3 }, "Switching to third mail tab" ); await assertTab(thirdMailTab, mailButton, "Switch to third mail tab"); await assertMailShown(); // -- Test the mail button with multiple mail tabs -- // Clicking the mail button whilst in the mail space does nothing. // Specifically, we do not want it to take us to the first tab. EventUtils.synthesizeMouseAtCenter(mailButton, {}, window); // Wait one cycle to see if the tab would change. await TestUtils.waitForTick(); await assertTab(thirdMailTab, mailButton, "Remain in third tab"); await assertMailShown(); Assert.equal( thirdMailTabInfo.folder.URI, folderA.URI, "Still display folder A in the third mail tab" ); // Clicking the mail button whilst in a different space takes us to the first // mail tab. await switchTab(calendarTab, calendarButton, "Third mail to calendar tab"); EventUtils.synthesizeMouseAtCenter(mailButton, {}, window); await assertTab(firstTab, mailButton, "Switch to the first mail tab"); await assertMailShown(); Assert.equal( firstMailTabInfo.folder.URI, folderB.URI, "Still display folder B in the first mail tab" ); // -- Test opening the mail space in a new window -- let windowPromise = BrowserTestUtils.domWindowOpenedAndLoaded(); await useContextMenu( { button: mailButton, item: newWindowItem }, { newWindow: true, newTab: true, numSwitch: 3 }, "Opening mail tab in new window" ); let newMailWindow = await windowPromise; let newTabmail = newMailWindow.document.getElementById("tabmail"); // Expect the same folder as the previously focused tab. await TestUtils.waitForCondition( () => newTabmail.currentTabInfo.folder?.URI == folderB.URI, "Waiting for folder B to be displayed in the new window" ); Assert.equal( newMailWindow.document.querySelectorAll("tab.tabmail-tab").length, 1, "Should only have one tab in the new window" ); await assertMailShown(newMailWindow); // -- Test opening different tabs that belong to the settings space -- let settingsButton = document.getElementById("settingsButton"); await useContextMenu( { button: settingsButton, item: accountItem }, { settings: true }, "Opening account settings" ); let accountTab = await waitForNewTab(); // Shown as part of the settings space. await assertTab(accountTab, settingsButton, "Opened account settings tab"); await assertContentShown("about:accountsettings"); await useContextMenu( { button: settingsButton, item: settingsItem }, { settings: true }, "Opening settings" ); let settingsTab = await waitForNewTab(); // Shown as part of the settings space. await assertTab(settingsTab, settingsButton, "Opened settings tab"); await assertSettingsShown(); await useContextMenu( { button: settingsButton, item: addonsItem }, { settings: true }, "Opening add-ons" ); let addonsTab = await waitForNewTab(); // Shown as part of the settings space. await assertTab(addonsTab, settingsButton, "Opened add-ons tab"); await assertContentShown("about:addons"); // -- Test the settings button with multiple settings tabs -- // Clicking the settings button whilst in the settings space does nothing. EventUtils.synthesizeMouseAtCenter(settingsButton, {}, window); // Wait one cycle to see if the tab would change. await TestUtils.waitForTick(); await assertTab(addonsTab, settingsButton, "Remain in add-ons tab"); await assertContentShown("about:addons"); // Clicking the settings button whilst in a different space takes us to the // settings tab, rather than the first tab, since this is the primary tab for // the space. await switchTab(calendarTab, calendarButton, "Add-ons to calendar tab"); EventUtils.synthesizeMouseAtCenter(settingsButton, {}, window); await assertTab(settingsTab, settingsButton, "Switch to the settings tab"); await assertSettingsShown(); // Clicking the settings button whilst in a different space and no settings // tab will open a new settings tab, rather than switch to another tab in the // settings space because they are not the primary tab for the space. await closeTab(settingsTab); await switchTab(calendarTab, calendarButton, "Settings to calendar tab"); EventUtils.synthesizeMouseAtCenter(settingsButton, {}, window); settingsTab = await waitForNewTab(); await assertTab(settingsTab, settingsButton, "Re-opened settings tab"); await assertSettingsShown(); // -- Test opening different settings tabs when they already exist -- await useContextMenu( { button: settingsButton, item: addonsItem }, { settings: true }, "Switching to add-ons" ); await assertTab(addonsTab, settingsButton, "Switched to add-ons"); await assertContentShown("about:addons"); await useContextMenu( { button: settingsButton, item: accountItem }, { settings: true }, "Switching to account settings" ); await assertTab(accountTab, settingsButton, "Switched to account settings"); await assertContentShown("about:accountsettings"); await useContextMenu( { button: settingsButton, item: settingsItem }, { settings: true }, "Switching to settings" ); await assertTab(settingsTab, settingsButton, "Switched to settings"); await assertSettingsShown(); // -- Test clicking the spaces buttons when all the tabs are already open. await sub_test_cycle_through_primary_tabs(); // Tidy up the opened window. // FIXME: Closing the window earlier in the test causes a test failure on the // osx build on the try server. await BrowserTestUtils.closeWindow(newMailWindow); }); add_task(async function testSpacesToolbarMenubar() { document.getElementById("toolbar-menubar").removeAttribute("autohide"); let spacesToolbar = document.getElementById("spacesToolbar"); EventUtils.synthesizeMouseAtCenter( document.getElementById("collapseButton"), {}, window ); Assert.ok(spacesToolbar.hidden, "The spaces toolbar is hidden"); Assert.ok( !document.getElementById("spacesToolbarReveal").hidden, "The status bar toggle button is visible" ); // Test the menubar button. let viewShownPromise = BrowserTestUtils.waitForEvent( document.getElementById("menu_View_Popup"), "popupshown" ); EventUtils.synthesizeMouseAtCenter( document.getElementById("menu_View"), {}, window ); await viewShownPromise; let toolbarsShownPromise = BrowserTestUtils.waitForEvent( document.getElementById("view_toolbars_popup"), "popupshown" ); EventUtils.synthesizeMouseAtCenter( document.getElementById("menu_Toolbars"), {}, window ); await toolbarsShownPromise; let menuButton = document.getElementById("viewToolbarsPopupSpacesToolbar"); Assert.ok( menuButton.getAttribute("checked") != "true", "The menu item is not checked" ); let viewHiddenPromise = BrowserTestUtils.waitForEvent( document.getElementById("menu_View_Popup"), "popuphidden" ); EventUtils.synthesizeMouseAtCenter(menuButton, {}, window); await viewHiddenPromise; Assert.ok( menuButton.getAttribute("checked") == "true", "The menu item is checked" ); }).__skipMe = AppConstants.platform == "macosx"; // Can't click menu bar on Mac. add_task(async function testSpacesToolbarOSX() { let size = document .getElementById("spacesToolbar") .getBoundingClientRect().width; // By default, macOS shouldn't need any custom styling. Assert.ok( !document.getElementById("titlebar").hasAttribute("style"), "The custom styling was cleared from all toolbars" ); let styleAppliedPromise = BrowserTestUtils.waitForCondition( () => document.getElementById("tabmail-tabs").getAttribute("style") == `margin-inline-start: ${size}px;`, "The correct style was applied to the tabmail" ); // Force full screen. window.fullScreen = true; await new Promise(resolve => requestAnimationFrame(resolve)); await styleAppliedPromise; let styleRemovedPromise = BrowserTestUtils.waitForCondition( () => !document.getElementById("tabmail-tabs").hasAttribute("style"), "The custom styling was cleared from all toolbars" ); // Restore original window size. window.fullScreen = false; await new Promise(resolve => requestAnimationFrame(resolve)); await styleRemovedPromise; }).__skipMe = AppConstants.platform != "macosx"; add_task(async function testSpacesToolbarClearedAlignment() { // Hide the spaces toolbar to check if the style it's cleared. window.gSpacesToolbar.toggleToolbar(true); Assert.ok( !document.getElementById("titlebar").hasAttribute("style") && !document.getElementById("navigation-toolbox").hasAttribute("style"), "The custom styling was cleared from all toolbars" ); }); add_task(async function testSpacesToolbarExtension() { window.gSpacesToolbar.toggleToolbar(false); for (let i = 0; i < 6; i++) { await window.gSpacesToolbar.createToolbarButton(`testButton${i}`, { title: `Title ${i}`, url: `https://test.invalid/${i}`, iconStyles: new Map([ [ "--webextension-toolbar-image", 'url("chrome://messenger/content/extension.svg")', ], ]), }); let button = document.getElementById(`testButton${i}`); Assert.ok(button); Assert.equal(button.title, `Title ${i}`); let img = button.querySelector("img"); Assert.equal( img.style.getPropertyValue("--webextension-toolbar-image"), `url("chrome://messenger/content/extension.svg")`, `Button image should have the correct icon.` ); let menuitem = document.getElementById(`testButton${i}-menuitem`); Assert.ok(menuitem); Assert.equal(menuitem.label, `Title ${i}`); Assert.equal( menuitem.style.getPropertyValue("--webextension-toolbar-image"), `url("chrome://messenger/content/extension.svg")`, `Menuitem should have the correct icon.` ); let space = window.gSpacesToolbar.spaces.find( space => space.name == `testButton${i}` ); Assert.ok(space); Assert.equal( space.url, `https://test.invalid/${i}`, "Added url should be correct." ); } for (let i = 0; i < 6; i++) { await window.gSpacesToolbar.updateToolbarButton(`testButton${i}`, { title: `Modified Title ${i}`, url: `https://test.invalid/${i + 1}`, iconStyles: new Map([ [ "--webextension-toolbar-image", 'url("chrome://messenger/skin/icons/new-addressbook.svg")', ], ]), }); let button = document.getElementById(`testButton${i}`); Assert.ok(button); Assert.equal(button.title, `Modified Title ${i}`); let img = button.querySelector("img"); Assert.equal( img.style.getPropertyValue("--webextension-toolbar-image"), `url("chrome://messenger/skin/icons/new-addressbook.svg")`, `Button image should have the correct icon.` ); let menuitem = document.getElementById(`testButton${i}-menuitem`); Assert.ok(menuitem); Assert.equal( menuitem.label, `Modified Title ${i}`, "Updated title should be correct." ); Assert.equal( menuitem.style.getPropertyValue("--webextension-toolbar-image"), `url("chrome://messenger/skin/icons/new-addressbook.svg")`, `Menuitem should have the correct icon.` ); let space = window.gSpacesToolbar.spaces.find( space => space.name == `testButton${i}` ); Assert.ok(space); Assert.equal( space.url, `https://test.invalid/${i + 1}`, "Updated url should be correct." ); } let overflowButton = document.getElementById( "spacesToolbarAddonsOverflowButton" ); let originalHeight = window.outerHeight; // Set a ridiculous tiny height to be sure all add-on buttons are hidden. window.resizeTo(window.outerWidth, 300); await new Promise(resolve => requestAnimationFrame(resolve)); await BrowserTestUtils.waitForCondition( () => !overflowButton.hidden, "The overflow button is visible" ); let overflowPopup = document.getElementById("spacesToolbarAddonsPopup"); let popupshown = BrowserTestUtils.waitForEvent(overflowPopup, "popupshown"); overflowButton.click(); await popupshown; Assert.ok(overflowPopup.hasChildNodes()); let popuphidden = BrowserTestUtils.waitForEvent(overflowPopup, "popuphidden"); // Restore the original height. window.resizeTo(window.outerWidth, originalHeight); await new Promise(resolve => requestAnimationFrame(resolve)); await popuphidden; await BrowserTestUtils.waitForCondition( () => overflowButton.hidden, "The overflow button is hidden" ); // Remove all previously added toolbar buttons and make sure all previously // generate elements are properly cleared. for (let i = 0; i < 6; i++) { await window.gSpacesToolbar.removeToolbarButton(`testButton${i}`); let space = window.gSpacesToolbar.spaces.find( space => space.name == `testButton${i}` ); Assert.ok(!space); let button = document.getElementById(`testButton${i}`); Assert.ok(!button); let menuitem = document.getElementById(`testButton${i}-menuitem`); Assert.ok(!menuitem); } }); add_task(function testPinnedSpacesBadge() { window.gSpacesToolbar.toggleToolbar(true); let spacesPinnedButton = document.getElementById("spacesPinnedButton"); let spacesPopupButtonChat = document.getElementById("spacesPopupButtonChat"); window.gSpacesToolbar.updatePinnedBadgeState(); Assert.ok( !spacesPinnedButton.classList.contains("has-badge"), "Pinned button does not indicate badged items without any" ); spacesPopupButtonChat.classList.add("has-badge"); window.gSpacesToolbar.updatePinnedBadgeState(); Assert.ok( spacesPinnedButton.classList.contains("has-badge"), "Pinned button indicates it has badged items" ); spacesPopupButtonChat.classList.remove("has-badge"); window.gSpacesToolbar.updatePinnedBadgeState(); Assert.ok( !spacesPinnedButton.classList.contains("has-badge"), "Badge state is reset from pinned button" ); }); add_task(async function testSpacesToolbarFocusRing() { // Make sure the spaces toolbar is visible. window.gSpacesToolbar.toggleToolbar(false); // Move the focus ring on the mail toolbar button. document.getElementById("mailButton").focus(); // Collect an array of all currently visible buttons. let buttons = [ ...document.querySelectorAll(".spaces-toolbar-button:not([hidden])"), ]; // Simulate the Arrow Down keypress to make sure the correct button gets the // focus. for (let i = 1; i < buttons.length; i++) { let previousElement = document.activeElement; EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); Assert.equal( document.activeElement.id, buttons[i].id, "The next button is focused" ); Assert.ok( document.activeElement.tabIndex == 0 && previousElement.tabIndex == -1, "The roving tab index was updated" ); } // Do the same with the Arrow Up key press but reversing the array. buttons.reverse(); for (let i = 1; i < buttons.length; i++) { let previousElement = document.activeElement; EventUtils.synthesizeKey("KEY_ArrowUp", {}, window); Assert.equal( document.activeElement.id, buttons[i].id, "The previous button is focused" ); Assert.ok( document.activeElement.tabIndex == 0 && previousElement.tabIndex == -1, "The roving tab index was updated" ); } // Pressing the END key should move the focus down to the last available // button. EventUtils.synthesizeKey("KEY_End", {}, window); Assert.equal( document.activeElement.id, "collapseButton", "The last button is focused" ); // Pressing the HOME key should move the focus up to the first available // button. EventUtils.synthesizeKey("KEY_Home", {}, window); Assert.equal( document.activeElement.id, "mailButton", "The first button is focused" ); // Focus follows the mouse click. EventUtils.synthesizeMouseAtCenter( document.getElementById("calendarButton"), {}, window ); Assert.equal( document.activeElement.id, "calendarButton", "Focus should move to the clicked calendar button" ); // Now press a key to make sure roving index was updated with the click. EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); Assert.equal( document.activeElement.id, "tasksButton", "Focus should move to the tasks button" ); });