/* 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/. */ import { BrowserTestUtils } from "resource://testing-common/BrowserTestUtils.sys.mjs"; import { Assert } from "resource://testing-common/Assert.sys.mjs"; const initialStates = new WeakMap(); let gTestScope; /** * Helpers for working with the sidebar in browser chrome tests. */ class _SidebarTestUtils { /** * Initializes the utils. * * @param {object} scope * The global JS scope where tests are being run. This allows the instance * to access test helpers like `info` and `registerCleanupFunction` that are available in the scope. */ init(scope) { if (!scope) { throw new Error("SidebarTestUtils.init() must be called with a scope"); } gTestScope = scope; scope.registerCleanupFunction?.(() => { gTestScope = null; }); } /** * Capture the initial sidebar state for later restoration. * Call this while setting up any test which interacts with the sidebar, so that * we can restore it to its original state in a cleanup function when * the test completes. * * @param {ChromeWindow} win */ restoreStateAtCleanup(win) { if (initialStates.has(win)) { throw new Error( "SidebarTestUtils.restoreStateAtCleanup already called for this window" ); } initialStates.set(win, { ...win.SidebarController.getUIState(), command: "", }); const testScopeInfo = gTestScope.info; gTestScope.registerCleanupFunction(async () => { // Close any sidebar panel which got left open if (!win.document.getElementById("sidebar-box").hidden) { testScopeInfo( `Sidebar ${win.SidebarController.currentID} was left open, closing it in cleanup function` ); this.closePanel(win); } await this.restoreToInitialState(win); }); } /** * Clean up and restore any sidebar state captured by restoreStateAtCleanup. * * @param {ChromeWindow} win */ async restoreToInitialState(win) { let state = initialStates.get(win); if (state) { // When a sidebar panel is toggled with `hide()` the lastOpenedId is left populated // and that panel will re-open when next toggled. In tests, that's not normally // expected so we reset here. win.SidebarController.lastOpenedId = null; // Restore sidebar launcher back to whatever state it was in initially. await win.SidebarController.updateUIState(state); initialStates.delete(win); } } /** * Show a sidebar panel and wait for it to be focused. * * @param {ChromeWindow} win * @param {string} commandID */ async showPanel(win, commandID) { let promiseFocused = BrowserTestUtils.waitForEvent(win, "SidebarFocused"); await win.SidebarController.show(commandID); await promiseFocused; } /** * Close sidebar panel * * @param {ChromeWindow} win */ closePanel(win) { let sidebarBox = win.document.getElementById("sidebar-box"); if (sidebarBox.hidden) { return; } // This is equivalent to the user clicking the "X" to close a panel. // That's not the same as toggling the sidebar with `hide()` which leaves // lastOpenedId populated. win.SidebarController.hide({ dismissPanel: true }); } async _ensureLauncherShowing(win, visible = true) { let { promiseInitialized, sidebarContainer } = win.SidebarController; await promiseInitialized; let hidden = !visible; if (sidebarContainer.hidden !== hidden) { // The command handler for the sidebar-button sets up the sidebar and button // state we want. But we can't always guarantee the sidebar-button is present, // so we call the handler directly. win.SidebarController.handleToolbarButtonClick(); await BrowserTestUtils.waitForMutationCondition( sidebarContainer, { attributes: true, attributeFilter: ["hidden"] }, () => sidebarContainer.hidden === hidden ); await win.SidebarController.waitUntilStable(); } } /** * Ensure the sidebar launcher is visible, opening it if necessary. * * @param {ChromeWindow} win */ async ensureLauncherVisible(win) { await this._ensureLauncherShowing(win, true); Assert.ok( BrowserTestUtils.isVisible(win.SidebarController.sidebarContainer), "Sidebar launcher is visible" ); } /** * Ensure the sidebar launcher is hidden, closing it if necessary. * * @param {ChromeWindow} win */ async ensureLauncherHidden(win) { await this._ensureLauncherShowing(win, false); Assert.ok( BrowserTestUtils.isHidden(win.SidebarController.sidebarContainer), "Sidebar launcher is hidden" ); } /** * Wait for the sidebar to be initialized in a window. * * @param {ChromeWindow} win */ async waitForInitialized(win) { await BrowserTestUtils.waitForCondition( () => win.SidebarController?.uiStateInitialized, "Waiting for sidebar UI state to be initialized" ); } /** * Wait for the tabstrip orientation to settle. * * @param {ChromeWindow} win * @param {string} [toOrientation="vertical"] */ async waitForTabstripOrientation(win, toOrientation = "vertical") { await win.SidebarController.promiseInitialized; await BrowserTestUtils.waitForMutationCondition( win.gBrowser.tabContainer, { attributes: true, attributeFilter: ["orient"] }, () => win.gBrowser.tabContainer.getAttribute("orient") == toOrientation ); await win.SidebarController.sidebarMain?.updateComplete; } } export const SidebarTestUtils = new _SidebarTestUtils();