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 --- comm/mail/test/browser/session-store/browser.ini | 11 + .../browser/session-store/browser_sessionStore.js | 680 +++++++++++++++++++++ 2 files changed, 691 insertions(+) create mode 100644 comm/mail/test/browser/session-store/browser.ini create mode 100644 comm/mail/test/browser/session-store/browser_sessionStore.js (limited to 'comm/mail/test/browser/session-store') diff --git a/comm/mail/test/browser/session-store/browser.ini b/comm/mail/test/browser/session-store/browser.ini new file mode 100644 index 0000000000..e4fcbec796 --- /dev/null +++ b/comm/mail/test/browser/session-store/browser.ini @@ -0,0 +1,11 @@ +[DEFAULT] +prefs = + mail.provider.suppress_dialog_on_startup=true + mail.spotlight.firstRunDone=true + mail.winsearch.firstRunDone=true + mailnews.start_page.override_url=about:blank + mailnews.start_page.url=about:blank + datareporting.policy.dataSubmissionPolicyBypassNotification=true +subsuite = thunderbird + +[browser_sessionStore.js] diff --git a/comm/mail/test/browser/session-store/browser_sessionStore.js b/comm/mail/test/browser/session-store/browser_sessionStore.js new file mode 100644 index 0000000000..92a14a51fa --- /dev/null +++ b/comm/mail/test/browser/session-store/browser_sessionStore.js @@ -0,0 +1,680 @@ +/* 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/. */ + +/** + * Session Storage Tests. Session Restoration Tests are currently implemented in + * folder-display/browser_messagePaneVisibility.js. + */ + +"use strict"; + +var { mailTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/MailTestUtils.jsm" +); + +var EventUtils = ChromeUtils.import( + "resource://testing-common/mozmill/EventUtils.jsm" +); +var utils = ChromeUtils.import("resource://testing-common/mozmill/utils.jsm"); + +var { + assert_message_pane_hidden, + assert_message_pane_visible, + assert_pane_layout, + be_in_folder, + create_folder, + kClassicMailLayout, + kVerticalMailLayout, + make_message_sets_in_folders, + mc, + set_mc, + set_pane_layout, + toggle_message_pane, +} = ChromeUtils.import( + "resource://testing-common/mozmill/FolderDisplayHelpers.jsm" +); +var { + close_window, + plan_for_new_window, + plan_for_window_close, + wait_for_new_window, + wait_for_window_close, +} = ChromeUtils.import("resource://testing-common/mozmill/WindowHelpers.jsm"); + +var { SessionStoreManager } = ChromeUtils.import( + "resource:///modules/SessionStoreManager.jsm" +); + +var folderA, folderB; + +// Default JSONFile save delay with saveSoon(). +var kSaveDelayMs = 1500; + +// With async file writes, use a delay larger than the session autosave timer. +var asyncFileWriteDelayMS = 3000; + +/* ........ Helper Functions ................*/ + +/** + * Reads the contents of the session file into a JSON object. + */ +async function readFile2() { + try { + return await IOUtils.readJSON(SessionStoreManager.sessionFile.path); + } catch (ex) { + if (!["NotFoundError"].includes(ex.name)) { + console.error(ex); + } + // fall through and return null if the session file cannot be read + // or is bad + dump(ex + "\n"); + } + return null; +} + +/** + * Reads the contents of the session file into a JSON object. + * FIXME: readFile2 should really be used instead. For some weird reason using + * that, OR making this function async (+ await the results) will + * not work - seem like the file reading just dies (???) + * So use the sync file reading for now... + */ +function readFile() { + let data = mailTestUtils.loadFileToString(SessionStoreManager.sessionFile); + return JSON.parse(data); +} + +async function waitForFileRefresh() { + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, kSaveDelayMs)); + TestUtils.waitForCondition( + () => SessionStoreManager.sessionFile.exists(), + "session file should exist" + ); + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, asyncFileWriteDelayMS)); +} + +function open3PaneWindow() { + plan_for_new_window("mail:3pane"); + Services.ww.openWindow( + null, + "chrome://messenger/content/messenger.xhtml", + "", + "all,chrome,dialog=no,status,toolbar", + null + ); + return wait_for_new_window("mail:3pane"); +} + +function openActivityManager() { + plan_for_new_window("Activity:Manager"); + window.openActivityMgr(); + return wait_for_new_window("Activity:Manager"); +} + +/* :::::::: The Tests ::::::::::::::: */ + +add_setup(async function () { + folderA = await create_folder("SessionStoreA"); + await make_message_sets_in_folders([folderA], [{ count: 3 }]); + + folderB = await create_folder("SessionStoreB"); + await make_message_sets_in_folders([folderB], [{ count: 3 }]); + + SessionStoreManager.stopPeriodicSave(); + + // Opt out of calendar promotion so we don't show the "ligthing now + // integrated" notification bar (which gives us unexpected heights). + Services.prefs.setBoolPref("calendar.integration.notify", false); +}); + +registerCleanupFunction(function () { + folderA.server.rootFolder.propagateDelete(folderA, true); + folderB.server.rootFolder.propagateDelete(folderB, true); + + // Some tests that open new windows don't return focus to the main window + // in a way that satisfies mochitest, and the test times out. + Services.focus.focusedWindow = window; + // Focus an element in the main window, then blur it again to avoid it + // hijacking keypresses. + let mainWindowElement = document.getElementById("button-appmenu"); + mainWindowElement.focus(); + mainWindowElement.blur(); +}); + +add_task(async function test_periodic_session_persistence_simple() { + // delete the session file if it exists + let sessionFile = SessionStoreManager.sessionFile; + if (sessionFile.exists()) { + sessionFile.remove(false); + } + + utils.waitFor(() => !sessionFile.exists(), "session file should not exist"); + + // change some state to guarantee the file will be recreated + // if periodic session persistence works + await be_in_folder(folderA); + + // if periodic session persistence is working, the file should be + // re-created + SessionStoreManager._saveState(); + await waitForFileRefresh(); +}); + +add_task(async function test_periodic_nondirty_session_persistence() { + // This changes state. + await be_in_folder(folderB); + + SessionStoreManager._saveState(); + await waitForFileRefresh(); + + // delete the session file + let sessionFile = SessionStoreManager.sessionFile; + sessionFile.remove(false); + + // Since the state of the session hasn't changed since last _saveState(), + // the session file should not be re-created. + SessionStoreManager._saveState(); + + await new Promise(resolve => + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(resolve, kSaveDelayMs + asyncFileWriteDelayMS) + ); + + utils.waitFor(() => !sessionFile.exists(), "session file should not exist"); +}); + +add_task(async function test_single_3pane_periodic_session_persistence() { + await be_in_folder(folderA); + + // get the state object. this assumes there is one and only one + // 3pane window. + let mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane"); + let state = mail3PaneWindow.getWindowStateForSessionPersistence(); + + SessionStoreManager._saveState(); + await waitForFileRefresh(); + + // load the saved state from disk + let loadedState = readFile(); + Assert.ok(loadedState, "previously saved state should be non-null"); + + // get the state object for the one and only one 3pane window + let windowState = loadedState.windows[0]; + Assert.ok( + JSON.stringify(windowState) == JSON.stringify(state), + "saved state and loaded state should be equal" + ); +}); + +async function test_restore_single_3pane_persistence() { + await be_in_folder(folderA); + toggle_message_pane(); + assert_message_pane_hidden(); + + // get the state object. this assumes there is one and only one + // 3pane window. + let mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane"); + + // make sure we have a different window open, so that we don't start shutting + // down just because the last window was closed + let amController = openActivityManager(); + + // close the 3pane window + mail3PaneWindow.close(); + // Wait for window close async session write to finish. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, asyncFileWriteDelayMS)); + + mc = open3PaneWindow(); + set_mc(mc); + await be_in_folder(folderA); + assert_message_pane_hidden(); + // restore message pane. + toggle_message_pane(); + + // We don't need the address book window any more. + plan_for_window_close(amController); + amController.window.close(); + wait_for_window_close(); +} +add_task(test_restore_single_3pane_persistence).skip(); // Bug 1753963. + +add_task(async function test_restore_single_3pane_persistence_again() { + // test that repeating the save w/o changing the state restores + // correctly. + await test_restore_single_3pane_persistence(); +}).skip(); // Bug 1753963. + +add_task(async function test_message_pane_height_persistence() { + await be_in_folder(folderA); + assert_message_pane_visible(); + assert_pane_layout(kClassicMailLayout); + + // Get the state object. This assumes there is one and only one + // 3pane window. + let mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane"); + + let oldHeight = mc.window.document.getElementById( + "messagepaneboxwrapper" + ).clientHeight; + let minHeight = Math.floor( + mc.window.document + .getElementById("messagepaneboxwrapper") + .getAttribute("minheight") + ); + let newHeight = Math.floor((minHeight + oldHeight) / 2); + let diffHeight = oldHeight - newHeight; + + Assert.notEqual( + oldHeight, + newHeight, + "To really perform a test the new message pane height should be " + + "should be different from the old one but they are the same: " + + newHeight + ); + + _move_splitter( + mc.window.document.getElementById("threadpane-splitter"), + 0, + diffHeight + ); + + // Check that the moving of the threadpane-splitter resulted in the correct height. + let actualHeight = mc.window.document.getElementById( + "messagepaneboxwrapper" + ).clientHeight; + + Assert.equal( + newHeight, + actualHeight, + "The message pane height should be " + + newHeight + + ", but is actually " + + actualHeight + + ". The oldHeight was: " + + oldHeight + ); + + // Make sure we have a different window open, so that we don't start shutting + // down just because the last window was closed. + let amController = openActivityManager(); + + // The 3pane window is closed. + mail3PaneWindow.close(); + // Wait for window close async session write to finish. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, asyncFileWriteDelayMS)); + + mc = open3PaneWindow(); + set_mc(mc); + await be_in_folder(folderA); + assert_message_pane_visible(); + + actualHeight = mc.window.document.getElementById( + "messagepaneboxwrapper" + ).clientHeight; + + Assert.equal( + newHeight, + actualHeight, + "The message pane height should be " + + newHeight + + ", but is actually " + + actualHeight + + ". The oldHeight was: " + + oldHeight + ); + + // The old height is restored. + _move_splitter( + mc.window.document.getElementById("threadpane-splitter"), + 0, + -diffHeight + ); + + // The 3pane window is closed. + close_window(mc); + // Wait for window close async session write to finish. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, asyncFileWriteDelayMS)); + + mc = open3PaneWindow(); + set_mc(mc); + await be_in_folder(folderA); + assert_message_pane_visible(); + + actualHeight = mc.window.document.getElementById( + "messagepaneboxwrapper" + ).clientHeight; + Assert.equal( + oldHeight, + actualHeight, + "The message pane height should be " + + oldHeight + + ", but is actually " + + actualHeight + ); + + // We don't need the address book window any more. + plan_for_window_close(amController); + amController.window.close(); + wait_for_window_close(); +}).skip(); // Bug 1753963. + +add_task(async function test_message_pane_width_persistence() { + await be_in_folder(folderA); + assert_message_pane_visible(); + + // At the beginning we are in classic layout. We will switch to + // vertical layout to test the width, and then back to classic layout. + assert_pane_layout(kClassicMailLayout); + set_pane_layout(kVerticalMailLayout); + assert_pane_layout(kVerticalMailLayout); + + // Get the state object. This assumes there is one and only one + // 3pane window. + let mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane"); + + let oldWidth = mc.window.document.getElementById( + "messagepaneboxwrapper" + ).clientWidth; + let minWidth = Math.floor( + mc.window.document + .getElementById("messagepaneboxwrapper") + .getAttribute("minwidth") + ); + let newWidth = Math.floor((minWidth + oldWidth) / 2); + let diffWidth = oldWidth - newWidth; + + Assert.notEqual( + newWidth, + oldWidth, + "To really perform a test the new message pane width should be " + + "should be different from the old one but they are the same: " + + newWidth + ); + + // We move the threadpane-splitter and not the folderpane_splitter because + // we are in vertical layout. + _move_splitter( + mc.window.document.getElementById("threadpane-splitter"), + diffWidth, + 0 + ); + // Check that the moving of the folderpane_splitter resulted in the correct width. + let actualWidth = mc.window.document.getElementById( + "messagepaneboxwrapper" + ).clientWidth; + + // FIXME: For whatever reasons the new width is off by one pixel on Mac OSX + // But this test case is not for testing moving around a splitter but for + // persistency. Therefore it is enough if the actual width is equal to the + // the requested width plus/minus one pixel. + assert_equals_fuzzy( + newWidth, + actualWidth, + 1, + "The message pane width should be " + + newWidth + + ", but is actually " + + actualWidth + + ". The oldWidth was: " + + oldWidth + ); + newWidth = actualWidth; + + // Make sure we have a different window open, so that we don't start shutting + // down just because the last window was closed + let amController = openActivityManager(); + + // The 3pane window is closed. + mail3PaneWindow.close(); + // Wait for window close async session write to finish. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, asyncFileWriteDelayMS)); + + mc = open3PaneWindow(); + set_mc(mc); + await be_in_folder(folderA); + assert_message_pane_visible(); + assert_pane_layout(kVerticalMailLayout); + + actualWidth = mc.window.document.getElementById( + "messagepaneboxwrapper" + ).clientWidth; + Assert.equal( + newWidth, + actualWidth, + "The message pane width should be " + + newWidth + + ", but is actually " + + actualWidth + ); + + // The old width is restored. + _move_splitter( + mc.window.document.getElementById("threadpane-splitter"), + -diffWidth, + 0 + ); + actualWidth = mc.window.document.getElementById( + "messagepaneboxwrapper" + ).clientWidth; + + // FIXME: For whatever reasons the new width is off by two pixels on Mac OSX + // But this test case is not for testing moving around a splitter but for + // persistency. Therefore it is enough if the actual width is equal to the + // the requested width plus/minus two pixels. + assert_equals_fuzzy( + oldWidth, + actualWidth, + 2, + "The message pane width should be " + + oldWidth + + ", but is actually " + + actualWidth + ); + oldWidth = actualWidth; + + // The 3pane window is closed. + close_window(mc); + // Wait for window close async session write to finish. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, asyncFileWriteDelayMS)); + + mc = open3PaneWindow(); + set_mc(mc); + await be_in_folder(folderA); + assert_message_pane_visible(); + assert_pane_layout(kVerticalMailLayout); + + actualWidth = mc.window.document.getElementById( + "messagepaneboxwrapper" + ).clientWidth; + Assert.equal( + oldWidth, + actualWidth, + "The message pane width should be " + + oldWidth + + ", but is actually " + + actualWidth + ); + + // The layout is reset to classical mail layout. + set_pane_layout(kClassicMailLayout); + assert_pane_layout(kClassicMailLayout); + + // We don't need the address book window any more. + plan_for_window_close(amController); + amController.window.close(); + wait_for_window_close(); +}).skip(); // Bug 1753963. + +add_task(async function test_multiple_3pane_periodic_session_persistence() { + // open a few more 3pane windows + for (var i = 0; i < 3; ++i) { + open3PaneWindow(); + } + + // then get the state objects for each window + let state = []; + for (let window of Services.wm.getEnumerator("mail:3pane")) { + state.push(window.getWindowStateForSessionPersistence()); + } + + SessionStoreManager._saveState(); + await waitForFileRefresh(); + + // load the saved state from disk + let loadedState = readFile(); + + Assert.ok(loadedState, "previously saved state should be non-null"); + + Assert.equal( + loadedState.windows.length, + state.length, + "number of windows in saved state and loaded state should be equal" + ); + + for (let i = 0; i < state.length; ++i) { + Assert.ok( + JSON.stringify(loadedState.windows[i]) == JSON.stringify(state[i]), + "saved state and loaded state should be equal" + ); + } + + // close all but one 3pane window + let windows = Services.wm.getEnumerator("mail:3pane"); + for (let win of windows) { + win.close(); + } +}).skip(); // Bug 1753963. + +async function test_bad_session_file_simple() { + // forcefully write a bad session file + let data = "BAD SESSION FILE"; + let fos = FileUtils.openSafeFileOutputStream(SessionStoreManager.sessionFile); + fos.write(data, data.length); + FileUtils.closeSafeFileOutputStream(fos); + + // tell the session store manager to try loading the bad session file. + // NOTE: periodic session persistence is not enabled in this test + SessionStoreManager._store = null; + await SessionStoreManager._loadSessionFile(); + + // since the session file is bad, the session store manager's state field + // should be null + Assert.ok( + !SessionStoreManager._initialState, + "saved state is bad so state object should be null" + ); + + // The bad session file should now not exist. + utils.waitFor( + () => !SessionStoreManager.sessionFile.exists(), + "session file should now not exist" + ); +} + +add_task(async function test_clean_shutdown_session_persistence_simple() { + // open a few more 3pane windows + for (var i = 0; i < 3; ++i) { + open3PaneWindow(); + } + + // make sure we have a different window open, so that we don't start shutting + // down just because the last window was closed + let amController = openActivityManager(); + + // close all the 3pane windows + let lastWindowState = null; + let enumerator = Services.wm.getEnumerator("mail:3pane"); + for (let window of enumerator) { + if (!enumerator.hasMoreElements()) { + lastWindowState = window.getWindowStateForSessionPersistence(); + } + window.close(); + } + + // Wait for session file to be created (removed in prior test) after + // all 3pane windows close and for session write to finish. + await waitForFileRefresh(); + + // load the saved state from disk + let loadedState = readFile(); + Assert.ok(loadedState, "previously saved state should be non-null"); + + Assert.equal( + loadedState.windows.length, + 1, + "only the state of the last 3pane window should have been saved" + ); + + // get the state object for the one and only one 3pane window + let windowState = loadedState.windows[0]; + Assert.ok( + JSON.stringify(windowState) == JSON.stringify(lastWindowState), + "saved state and loaded state should be equal" + ); + + open3PaneWindow(); + + // We don't need the address book window any more. + plan_for_window_close(amController); + amController.window.close(); + wait_for_window_close(); +}).skip(); // Bug 1753963. + +/* + * A set of private helper functions for drag'n'drop + * These functions are inspired by tabmail/test-tabmail-dragndrop.js + */ + +function _move_splitter(aSplitter, aDiffX, aDiffY) { + // catch the splitter in the middle + let rect = aSplitter.getBoundingClientRect(); + let middleX = Math.round(rect.width / 2); + let middleY = Math.round(rect.height / 2); + EventUtils.synthesizeMouse( + aSplitter, + middleX, + middleY, + { type: "mousedown" }, + mc.window + ); + EventUtils.synthesizeMouse( + aSplitter, + aDiffX + middleX, + aDiffY + middleY, + { type: "mousemove" }, + mc.window + ); + // release the splitter + EventUtils.synthesizeMouse(aSplitter, 0, 0, { type: "mouseup" }, mc.window); +} + +/** + * Helper function that checks the fuzzy equivalence of two numeric + * values against some given tolerance. + * + * @param aLeft one value to check equivalence with + * @param aRight the other value to check equivalence with + * @param aTolerance how fuzzy can our equivalence be? + * @param aMessage the message to give off if we're outside of tolerance. + */ +function assert_equals_fuzzy(aLeft, aRight, aTolerance, aMessage) { + Assert.ok(Math.abs(aLeft - aRight) <= aTolerance, aMessage); +} + +// XXX todo +// - crash test: not sure if this test should be here. restoring a crashed +// session depends on periodically saved session data (there is +// already a test for this). session restoration tests do not +// belong here. see test-message-pane-visibility. +// when testing restoration in test-message-pane-visibility, also +// include test of bad session file. +// ..............maybe we should move all session restoration related tests +// ..............here. -- cgit v1.2.3