/* 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/. */ "use strict"; /** * Test rearanging tabs via drag'n'drop. */ if ( AppConstants.MOZ_CODE_COVERAGE || AppConstants.ASAN || AppConstants.DEBUG || AppConstants.TSAN ) { requestLongerTimeout(3); } else if (AppConstants.platform == "macosx") { requestLongerTimeout(2); } var { assert_number_of_tabs_open, assert_selected_and_displayed, be_in_folder, close_popup, create_folder, open_folder_in_new_window, open_selected_message_in_new_tab, select_click_row, switch_tab, wait_for_message_display_completion, } = ChromeUtils.importESModule( "resource://testing-common/mail/FolderDisplayHelpers.sys.mjs" ); var { make_message_sets_in_folders } = ChromeUtils.importESModule( "resource://testing-common/mail/MessageInjectionHelpers.sys.mjs" ); var { drag_n_drop_element, synthesize_drag_end, synthesize_drag_start, synthesize_drag_over, } = ChromeUtils.importESModule( "resource://testing-common/mail/MouseEventHelpers.sys.mjs" ); var { promise_new_window } = ChromeUtils.importESModule( "resource://testing-common/mail/WindowHelpers.sys.mjs" ); var folder; var msgHdrsInFolder = []; // The number of messages in folder. var NUM_MESSAGES_IN_FOLDER = 15; add_setup(async function () { folder = await create_folder("MessageFolder"); await make_message_sets_in_folders( [folder], [{ count: NUM_MESSAGES_IN_FOLDER }] ); msgHdrsInFolder = [...folder.messages]; folder.markAllMessagesRead(null); await be_in_folder(folder); }); registerCleanupFunction(async function () { folder.deleteSelf(null); }); /** * Tests reordering tabs by drag'n'drop within the tabbar * * It opens additional movable and closable tabs. The picks the first * movable tab and drops it onto the third movable tab. */ add_task(async function test_tab_reorder_tabbar() { const tabmail = document.getElementById("tabmail"); // Ensure only one tab is open, otherwise our test most likely fail anyway. tabmail.closeOtherTabs(0); assert_number_of_tabs_open(1); await be_in_folder(folder); // Open four tabs for (let idx = 0; idx < 4; idx++) { await select_click_row(idx); await open_selected_message_in_new_tab(true); } // Check if every thing is correctly initialized assert_number_of_tabs_open(5); Assert.equal( tabmail.tabModes.mailMessageTab.tabs[0], tabmail.tabInfo[1], " tabMode.tabs and tabInfo out of sync" ); Assert.equal( tabmail.tabModes.mailMessageTab.tabs[1], document.getElementById("tabmail").tabInfo[2], " tabMode.tabs and tabInfo out of sync" ); Assert.equal( tabmail.tabModes.mailMessageTab.tabs[2], tabmail.tabInfo[3], " tabMode.tabs and tabInfo out of sync" ); // Start dragging the first tab await switch_tab(1); await assert_selected_and_displayed(msgHdrsInFolder[0]); const tab1 = tabmail.tabContainer.allTabs[1]; const tab3 = tabmail.tabContainer.allTabs[3]; drag_n_drop_element( tab1, window, tab3, window, 0.75, 0.0, tabmail.tabContainer ); await wait_for_message_display_completion(window); // if every thing went well... assert_number_of_tabs_open(5); // ... we should find tab1 at the third position... Assert.equal(tab1, tabmail.tabContainer.allTabs[3], "Moving tab1 failed"); await switch_tab(3); await assert_selected_and_displayed(msgHdrsInFolder[0]); // ... while tab3 moves one up and gets second. Assert.equal(tab3, tabmail.tabContainer.allTabs[2], "Moving tab3 failed"); await switch_tab(2); await assert_selected_and_displayed(msgHdrsInFolder[2]); // we have one "message" tab and three "folder" tabs, thus tabInfo[1-3] and // tabMode["message"].tabs[0-2] have to be same, otherwise something went // wrong while moving tabs around Assert.equal( tabmail.tabModes.mailMessageTab.tabs[0], tabmail.tabInfo[1], " tabMode.tabs and tabInfo out of sync" ); Assert.equal( tabmail.tabModes.mailMessageTab.tabs[1], tabmail.tabInfo[2], " tabMode.tabs and tabInfo out of sync" ); Assert.equal( tabmail.tabModes.mailMessageTab.tabs[2], tabmail.tabInfo[3], " tabMode.tabs and tabInfo out of sync" ); teardownTest(); }); /** * Tests drag'n'drop tab reordering between windows */ add_task(async function test_tab_reorder_window() { const tabmail = document.getElementById("tabmail"); // Ensure only one tab is open, otherwise our test most likely fail anyway. tabmail.closeOtherTabs(0); assert_number_of_tabs_open(1); let mc2 = null; await be_in_folder(folder); // Open a new tab... await select_click_row(1); await open_selected_message_in_new_tab(false); assert_number_of_tabs_open(2); await switch_tab(1); await assert_selected_and_displayed(msgHdrsInFolder[1]); // ...and then a new 3 pane as our drop target. mc2 = await open_folder_in_new_window(folder); const tabmail2 = mc2.document.getElementById("tabmail"); await TestUtils.waitForCondition( () => tabmail2.currentAbout3Pane.gDBView, "waiting for the new window to be ready" ); // Start dragging the first tab ... const tabA = tabmail.tabContainer.allTabs[1]; Assert.ok(tabA, "No movable Tab"); // We drop onto the Folder Tab, it is guaranteed to exist. const tabB = tabmail2.tabContainer.allTabs[0]; Assert.ok(tabB, "No movable Tab"); drag_n_drop_element(tabA, window, tabB, mc2, 0.75, 0.0, tabmail.tabContainer); Assert.equal( tabmail.tabContainer.allTabs.length, 1, "tab should be removed from the old window" ); Assert.equal( tabmail2.tabContainer.allTabs.length, 2, "a new tab should have opened in then new window" ); Assert.equal( tabmail2.tabInfo[1].mode.name, "mailMessageTab", "new tab should be a message tab" ); Assert.equal( tabmail2.currentTabInfo, tabmail2.tabInfo[1], "new tab should be selected" ); await TestUtils.waitForCondition( () => tabmail2.currentAboutMessage.gDBView, "waiting for the new tab to be ready" ); await assert_selected_and_displayed(mc2, msgHdrsInFolder[1]); teardownTest(); }); /** * Tests detaching tabs into windows via drag'n'drop */ add_task(async function test_tab_reorder_detach() { const tabmail = document.getElementById("tabmail"); // Ensure only one tab is open, otherwise our test most likely fail anyway. tabmail.closeOtherTabs(0); assert_number_of_tabs_open(1); let mc2 = null; await be_in_folder(folder); // Open a new tab... await select_click_row(2); await open_selected_message_in_new_tab(false); assert_number_of_tabs_open(2); // ... if every thing works we should expect a new window... const newWindowPromise = promise_new_window("mail:3pane"); // ... now start dragging tabmail.switchToTab(1); const tab1 = tabmail.tabContainer.allTabs[1]; const dropContent = document.getElementById("tabpanelcontainer"); const dt = synthesize_drag_start(window, tab1, tabmail.tabContainer); synthesize_drag_over(window, dropContent, dt); // notify tab1 drag has ended const dropRect = dropContent.getBoundingClientRect(); synthesize_drag_end(window, dropContent, tab1, dt, { screenX: dropContent.screenX + dropRect.width / 2, screenY: dropContent.screenY + dropRect.height / 2, }); // ... and wait for the new window mc2 = await newWindowPromise; const tabmail2 = mc2.document.getElementById("tabmail"); await TestUtils.waitForCondition( () => tabmail2.tabInfo[1]?.chromeBrowser, "waiting for a second tab to open in the new window" ); await wait_for_message_display_completion(mc2, true); Assert.equal( tabmail.tabContainer.allTabs.length, 1, "tab should be removed from the old window" ); Assert.equal( tabmail2.tabContainer.allTabs.length, 2, "a new tab should have opened in then new window" ); Assert.equal( tabmail2.currentTabInfo, tabmail2.tabInfo[1], "new tab should be selected" ); await TestUtils.waitForCondition( () => tabmail2.currentAboutMessage?.gDBView, "waiting for the new tab to be ready" ); await assert_selected_and_displayed(mc2, msgHdrsInFolder[2]); teardownTest(); }); /** * Test undo of recently closed tabs. */ add_task(async function test_tab_undo() { const tabmail = document.getElementById("tabmail"); // Ensure only one tab is open, otherwise our test most likely fail anyway. tabmail.closeOtherTabs(0); assert_number_of_tabs_open(1); await be_in_folder(folder); // Open five tabs... for (let idx = 0; idx < 5; idx++) { await select_click_row(idx); await open_selected_message_in_new_tab(true); } assert_number_of_tabs_open(6); await switch_tab(2); await assert_selected_and_displayed(msgHdrsInFolder[1]); tabmail.closeTab(2); // This tab should not be added to recently closed tabs... // ... thus it can't be restored tabmail.closeTab(2, true); tabmail.closeTab(2); assert_number_of_tabs_open(3); await assert_selected_and_displayed(window, msgHdrsInFolder[4]); tabmail.undoCloseTab(); await wait_for_message_display_completion(); assert_number_of_tabs_open(4); await assert_selected_and_displayed(window, msgHdrsInFolder[3]); // msgHdrsInFolder[2] won't be restored, it was closed with disabled undo. tabmail.undoCloseTab(); await wait_for_message_display_completion(); assert_number_of_tabs_open(5); await assert_selected_and_displayed(window, msgHdrsInFolder[1]); teardownTest(); }); async function _synthesizeRecentlyClosedMenu() { const tab = document.getElementById("tabmail").tabContainer.allTabs[1]; EventUtils.synthesizeMouseAtCenter( tab, { type: "contextmenu", button: 2 }, tab.ownerGlobal ); const tabContextMenu = document.getElementById("tabContextMenu"); await BrowserTestUtils.waitForPopupEvent(tabContextMenu, "shown"); const recentlyClosedTabs = document.getElementById( "tabContextMenuRecentlyClosed" ); recentlyClosedTabs.openMenu(true); await BrowserTestUtils.waitForPopupEvent( recentlyClosedTabs.menupopup, "shown" ); return recentlyClosedTabs; } async function _teardownRecentlyClosedMenu() { const menu = document.getElementById("tabContextMenu"); await close_popup(window, menu); } /** * Tests the recently closed tabs menu. */ add_task(async function test_tab_recentlyClosed() { const tabmail = document.getElementById("tabmail"); // Ensure only one tab is open, otherwise our test most likely fail anyway. tabmail.closeOtherTabs(0, true); assert_number_of_tabs_open(1); // We start with a clean tab history. tabmail.clearRecentlyClosedTabs(); Assert.equal(tabmail.recentlyClosedTabs.length, 0); // The history is cleaned so let's open 15 tabs... await be_in_folder(folder); for (let idx = 0; idx < 15; idx++) { await select_click_row(idx); await open_selected_message_in_new_tab(true); } assert_number_of_tabs_open(16); await switch_tab(2); await assert_selected_and_displayed(msgHdrsInFolder[1]); // ... and store the tab titles, to ensure they match with the menu items. const tabTitles = []; for (let idx = 0; idx < 16; idx++) { tabTitles.unshift(tabmail.tabInfo[idx].title); } // Start the test by closing all tabs except the first two tabs... for (let idx = 0; idx < 14; idx++) { tabmail.closeTab(2); } assert_number_of_tabs_open(2); await TestUtils.waitForTick(); // ...then open the context menu. const menu = await _synthesizeRecentlyClosedMenu(); // Check if the context menu was populated correctly... Assert.equal(menu.itemCount, 12, "Failed to populate context menu"); for (let idx = 0; idx < 10; idx++) { Assert.equal( tabTitles[idx], menu.getItemAtIndex(idx).label, `Tab Title ${idx} does not match Menu item` ); } // Restore the most recently closed tab let tabOpenPromise = BrowserTestUtils.waitForEvent( tabmail.tabContainer, "TabOpen" ); menu.menupopup.activateItem(menu.getItemAtIndex(0)); await _teardownRecentlyClosedMenu(); await new Promise(resolve => setTimeout(resolve)); await tabOpenPromise; await wait_for_message_display_completion(window); assert_number_of_tabs_open(3); await assert_selected_and_displayed(msgHdrsInFolder[14]); // The context menu should now contain one item less. await _synthesizeRecentlyClosedMenu(); Assert.equal(menu.itemCount, 11, "Failed to populate context menu again"); for (let idx = 0; idx < 9; idx++) { Assert.equal( tabTitles[idx + 1], menu.getItemAtIndex(idx).label, `Tab Title ${idx} does not match Menu item 2` ); } // Now we restore an "random" tab. tabOpenPromise = BrowserTestUtils.waitForEvent( tabmail.tabContainer, "TabOpen" ); menu.menupopup.activateItem(menu.getItemAtIndex(5)); await _teardownRecentlyClosedMenu(); await new Promise(resolve => setTimeout(resolve)); await tabOpenPromise; await wait_for_message_display_completion(window); assert_number_of_tabs_open(4); await assert_selected_and_displayed(msgHdrsInFolder[8]); // finally restore all tabs await _synthesizeRecentlyClosedMenu(); Assert.equal(menu.itemCount, 10, "Failed to populate context menu 3"); Assert.equal( tabTitles[1], menu.getItemAtIndex(0).label, "Second tab title does not match menu item" ); Assert.equal( tabTitles[7], menu.getItemAtIndex(5).label, "Eighth tab title does not match menu item" ); tabOpenPromise = BrowserTestUtils.waitForEvent( tabmail.tabContainer, "TabOpen" ); menu.menupopup.activateItem(menu.getItemAtIndex(menu.itemCount - 1)); await _teardownRecentlyClosedMenu(); await new Promise(resolve => setTimeout(resolve)); await tabOpenPromise; await wait_for_message_display_completion(window); // out of the 16 tab, we closed all except two. As the history can store // only 10 items we have to endup with exactly 10 + 2 tabs. assert_number_of_tabs_open(12); teardownTest(); }); function teardownTest() { // Some test cases open new windows, thus we need to ensure all // opened windows get closed. for (const win of Services.wm.getEnumerator("mail:3pane")) { if (win != window) { win.close(); } } // clean up the tabbbar document.getElementById("tabmail").closeOtherTabs(0); assert_number_of_tabs_open(1); }