SessionStoreTestUtils.sys.mjs (6262B)
1 const lazy = {}; 2 ChromeUtils.defineESModuleGetters(lazy, { 3 BrowserTestUtils: "resource://testing-common/BrowserTestUtils.sys.mjs", 4 SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", 5 TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.sys.mjs", 6 TestUtils: "resource://testing-common/TestUtils.sys.mjs", 7 }); 8 9 export var SessionStoreTestUtils = { 10 /** 11 * Running this init allows helpers to access test scope helpers, like Assert 12 * and SimpleTest. 13 * Tests should call this init() before using the helpers which rely on properties assign here. 14 * 15 * @param {object} scope The global scope where tests are being run. 16 * @param {DOmWindow} scope The global window object, for acessing gBrowser etc. 17 */ 18 init(scope, windowGlobal) { 19 if (!scope) { 20 throw new Error( 21 "Must initialize SessionStoreTestUtils with a test scope" 22 ); 23 } 24 if (!windowGlobal) { 25 throw new Error("this.windowGlobal must be defined when we init"); 26 } 27 this.info = scope.info; 28 this.registerCleanupFunction = scope.registerCleanupFunction; 29 this.windowGlobal = windowGlobal; 30 }, 31 32 async closeTab(tab) { 33 await lazy.TabStateFlusher.flush(tab.linkedBrowser); 34 let sessionUpdatePromise = 35 lazy.BrowserTestUtils.waitForSessionStoreUpdate(tab); 36 lazy.BrowserTestUtils.removeTab(tab); 37 await sessionUpdatePromise; 38 }, 39 40 async openAndCloseTab(window, url) { 41 let { updatePromise } = await lazy.BrowserTestUtils.withNewTab( 42 { url, gBrowser: window.gBrowser }, 43 async browser => { 44 return { 45 updatePromise: lazy.BrowserTestUtils.waitForSessionStoreUpdate({ 46 linkedBrowser: browser, 47 }), 48 }; 49 } 50 ); 51 await updatePromise; 52 return lazy.TestUtils.topicObserved("sessionstore-closed-objects-changed"); 53 }, 54 55 // This assumes that tests will at least have some state/entries 56 waitForBrowserState(aState, aSetStateCallback) { 57 if (typeof aState == "string") { 58 aState = JSON.parse(aState); 59 } 60 if (typeof aState != "object") { 61 throw new TypeError( 62 "Argument must be an object or a JSON representation of an object" 63 ); 64 } 65 if (!this.windowGlobal) { 66 throw new Error( 67 "no windowGlobal defined, please call init() first with the scope and window object" 68 ); 69 } 70 let windows = [this.windowGlobal]; 71 let tabsRestored = 0; 72 let expectedTabsRestored = 0; 73 let expectedWindows = aState.windows.length; 74 let windowsOpen = 1; 75 let listening = false; 76 let windowObserving = false; 77 let restoreHiddenTabs = Services.prefs.getBoolPref( 78 "browser.sessionstore.restore_hidden_tabs" 79 ); 80 // This should match the |restoreTabsLazily| value that 81 // SessionStore.restoreWindow() uses. 82 let restoreTabsLazily = 83 Services.prefs.getBoolPref("browser.sessionstore.restore_on_demand") && 84 Services.prefs.getBoolPref("browser.sessionstore.restore_tabs_lazily"); 85 86 aState.windows.forEach(function (winState) { 87 winState.tabs.forEach(function (tabState) { 88 if (!restoreTabsLazily && (restoreHiddenTabs || !tabState.hidden)) { 89 expectedTabsRestored++; 90 } 91 }); 92 }); 93 94 // If there are only hidden tabs and restoreHiddenTabs = false, we still 95 // expect one of them to be restored because it gets shown automatically. 96 // Otherwise if lazy tab restore there will only be one tab restored per window. 97 if (!expectedTabsRestored) { 98 expectedTabsRestored = 1; 99 } else if (restoreTabsLazily) { 100 expectedTabsRestored = aState.windows.length; 101 } 102 103 function onSSTabRestored() { 104 if (++tabsRestored == expectedTabsRestored) { 105 // Remove the event listener from each window 106 windows.forEach(function (win) { 107 win.gBrowser.tabContainer.removeEventListener( 108 "SSTabRestored", 109 onSSTabRestored, 110 true 111 ); 112 }); 113 listening = false; 114 SessionStoreTestUtils.info("running " + aSetStateCallback.name); 115 lazy.TestUtils.executeSoon(aSetStateCallback); 116 } 117 } 118 119 // Used to add our listener to further windows so we can catch SSTabRestored 120 // coming from them when creating a multi-window state. 121 function windowObserver(aSubject, aTopic) { 122 if (aTopic == "domwindowopened") { 123 let newWindow = aSubject; 124 newWindow.addEventListener( 125 "load", 126 function () { 127 if (++windowsOpen == expectedWindows) { 128 Services.ww.unregisterNotification(windowObserver); 129 windowObserving = false; 130 } 131 132 // Track this window so we can remove the progress listener later 133 windows.push(newWindow); 134 // Add the progress listener 135 newWindow.gBrowser.tabContainer.addEventListener( 136 "SSTabRestored", 137 onSSTabRestored, 138 true 139 ); 140 }, 141 { once: true } 142 ); 143 } 144 } 145 146 // We only want to register the notification if we expect more than 1 window 147 if (expectedWindows > 1) { 148 this.registerCleanupFunction(function () { 149 if (windowObserving) { 150 Services.ww.unregisterNotification(windowObserver); 151 } 152 }); 153 windowObserving = true; 154 Services.ww.registerNotification(windowObserver); 155 } 156 157 this.registerCleanupFunction(function () { 158 if (listening) { 159 windows.forEach(function (win) { 160 win.gBrowser.tabContainer.removeEventListener( 161 "SSTabRestored", 162 onSSTabRestored, 163 true 164 ); 165 }); 166 } 167 }); 168 // Add the event listener for this window as well. 169 listening = true; 170 this.windowGlobal.gBrowser.tabContainer.addEventListener( 171 "SSTabRestored", 172 onSSTabRestored, 173 true 174 ); 175 176 // Ensure setBrowserState() doesn't remove the initial tab. 177 this.windowGlobal.gBrowser.selectedTab = this.windowGlobal.gBrowser.tabs[0]; 178 179 // Finally, call setBrowserState 180 lazy.SessionStore.setBrowserState(JSON.stringify(aState)); 181 }, 182 183 promiseBrowserState(aState) { 184 return new Promise(resolve => this.waitForBrowserState(aState, resolve)); 185 }, 186 };