forked from mirrors/gecko-dev
		
	 7878082e5c
			
		
	
	
		7878082e5c
		
	
	
	
	
		
			
			* Move closeTab and openAndCloseTab to the helpers module and replace a couple of uses * Move waitForBrowserState/promiseBrowserState and replace a couple of uses Differential Revision: https://phabricator.services.mozilla.com/D187636
		
			
				
	
	
		
			690 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			690 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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/. */
 | |
| 
 | |
| const triggeringPrincipal_base64 = E10SUtils.SERIALIZED_SYSTEMPRINCIPAL;
 | |
| 
 | |
| const TAB_STATE_NEEDS_RESTORE = 1;
 | |
| const TAB_STATE_RESTORING = 2;
 | |
| 
 | |
| const ROOT = getRootDirectory(gTestPath);
 | |
| const HTTPROOT = ROOT.replace(
 | |
|   "chrome://mochitests/content/",
 | |
|   "http://example.com/"
 | |
| );
 | |
| const HTTPSROOT = ROOT.replace(
 | |
|   "chrome://mochitests/content/",
 | |
|   "https://example.com/"
 | |
| );
 | |
| 
 | |
| const { SessionSaver } = ChromeUtils.importESModule(
 | |
|   "resource:///modules/sessionstore/SessionSaver.sys.mjs"
 | |
| );
 | |
| const { SessionFile } = ChromeUtils.importESModule(
 | |
|   "resource:///modules/sessionstore/SessionFile.sys.mjs"
 | |
| );
 | |
| const { TabState } = ChromeUtils.importESModule(
 | |
|   "resource:///modules/sessionstore/TabState.sys.mjs"
 | |
| );
 | |
| const { TabStateFlusher } = ChromeUtils.importESModule(
 | |
|   "resource:///modules/sessionstore/TabStateFlusher.sys.mjs"
 | |
| );
 | |
| const { SessionStoreTestUtils } = ChromeUtils.importESModule(
 | |
|   "resource://testing-common/SessionStoreTestUtils.sys.mjs"
 | |
| );
 | |
| 
 | |
| const ss = SessionStore;
 | |
| SessionStoreTestUtils.init(this, window);
 | |
| 
 | |
| // Some tests here assume that all restored tabs are loaded without waiting for
 | |
| // the user to bring them to the foreground. We ensure this by resetting the
 | |
| // related preference (see the "firefox.js" defaults file for details).
 | |
| Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
 | |
| registerCleanupFunction(function () {
 | |
|   Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
 | |
| });
 | |
| 
 | |
| // Obtain access to internals
 | |
| Services.prefs.setBoolPref("browser.sessionstore.debug", true);
 | |
| registerCleanupFunction(function () {
 | |
|   Services.prefs.clearUserPref("browser.sessionstore.debug");
 | |
| });
 | |
| 
 | |
| // This kicks off the search service used on about:home and allows the
 | |
| // session restore tests to be run standalone without triggering errors.
 | |
| Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler).defaultArgs;
 | |
| 
 | |
| function provideWindow(aCallback, aURL, aFeatures) {
 | |
|   function callbackSoon(aWindow) {
 | |
|     executeSoon(function executeCallbackSoon() {
 | |
|       aCallback(aWindow);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   let win = openDialog(
 | |
|     AppConstants.BROWSER_CHROME_URL,
 | |
|     "",
 | |
|     aFeatures || "chrome,all,dialog=no",
 | |
|     aURL || "about:blank"
 | |
|   );
 | |
|   whenWindowLoaded(win, function onWindowLoaded(aWin) {
 | |
|     if (!aURL) {
 | |
|       info("Loaded a blank window.");
 | |
|       callbackSoon(aWin);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     aWin.gBrowser.selectedBrowser.addEventListener(
 | |
|       "load",
 | |
|       function () {
 | |
|         callbackSoon(aWin);
 | |
|       },
 | |
|       { capture: true, once: true }
 | |
|     );
 | |
|   });
 | |
| }
 | |
| 
 | |
| // This assumes that tests will at least have some state/entries
 | |
| function waitForBrowserState(aState, aSetStateCallback) {
 | |
|   return SessionStoreTestUtils.waitForBrowserState(aState, aSetStateCallback);
 | |
| }
 | |
| 
 | |
| function promiseBrowserState(aState) {
 | |
|   return SessionStoreTestUtils.promiseBrowserState(aState);
 | |
| }
 | |
| 
 | |
| function promiseTabState(tab, state) {
 | |
|   if (typeof state != "string") {
 | |
|     state = JSON.stringify(state);
 | |
|   }
 | |
| 
 | |
|   let promise = promiseTabRestored(tab);
 | |
|   ss.setTabState(tab, state);
 | |
|   return promise;
 | |
| }
 | |
| 
 | |
| function promiseWindowRestoring(win) {
 | |
|   return new Promise(resolve =>
 | |
|     win.addEventListener("SSWindowRestoring", resolve, { once: true })
 | |
|   );
 | |
| }
 | |
| 
 | |
| function promiseWindowRestored(win) {
 | |
|   return new Promise(resolve =>
 | |
|     win.addEventListener("SSWindowRestored", resolve, { once: true })
 | |
|   );
 | |
| }
 | |
| 
 | |
| async function setBrowserState(state, win = window) {
 | |
|   ss.setBrowserState(typeof state != "string" ? JSON.stringify(state) : state);
 | |
|   await promiseWindowRestored(win);
 | |
| }
 | |
| 
 | |
| async function setWindowState(win, state, overwrite = false) {
 | |
|   ss.setWindowState(
 | |
|     win,
 | |
|     typeof state != "string" ? JSON.stringify(state) : state,
 | |
|     overwrite
 | |
|   );
 | |
|   await promiseWindowRestored(win);
 | |
| }
 | |
| 
 | |
| function waitForTopic(aTopic, aTimeout, aCallback) {
 | |
|   let observing = false;
 | |
|   function removeObserver() {
 | |
|     if (!observing) {
 | |
|       return;
 | |
|     }
 | |
|     Services.obs.removeObserver(observer, aTopic);
 | |
|     observing = false;
 | |
|   }
 | |
| 
 | |
|   let timeout = setTimeout(function () {
 | |
|     removeObserver();
 | |
|     aCallback(false);
 | |
|   }, aTimeout);
 | |
| 
 | |
|   function observer(subject, topic, data) {
 | |
|     removeObserver();
 | |
|     timeout = clearTimeout(timeout);
 | |
|     executeSoon(() => aCallback(true));
 | |
|   }
 | |
| 
 | |
|   registerCleanupFunction(function () {
 | |
|     removeObserver();
 | |
|     if (timeout) {
 | |
|       clearTimeout(timeout);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   observing = true;
 | |
|   Services.obs.addObserver(observer, aTopic);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Wait until session restore has finished collecting its data and is
 | |
|  * has written that data ("sessionstore-state-write-complete").
 | |
|  *
 | |
|  * @param {function} aCallback If sessionstore-state-write-complete is sent
 | |
|  * within buffering interval + 100 ms, the callback is passed |true|,
 | |
|  * otherwise, it is passed |false|.
 | |
|  */
 | |
| function waitForSaveState(aCallback) {
 | |
|   let timeout =
 | |
|     100 + Services.prefs.getIntPref("browser.sessionstore.interval");
 | |
|   return waitForTopic("sessionstore-state-write-complete", timeout, aCallback);
 | |
| }
 | |
| function promiseSaveState() {
 | |
|   return new Promise((resolve, reject) => {
 | |
|     waitForSaveState(isSuccessful => {
 | |
|       if (!isSuccessful) {
 | |
|         reject(new Error("Save state timeout"));
 | |
|       } else {
 | |
|         resolve();
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| }
 | |
| function forceSaveState() {
 | |
|   return SessionSaver.run();
 | |
| }
 | |
| 
 | |
| function promiseRecoveryFileContents() {
 | |
|   let promise = forceSaveState();
 | |
|   return promise.then(function () {
 | |
|     return IOUtils.readUTF8(SessionFile.Paths.recovery, {
 | |
|       decompress: true,
 | |
|     });
 | |
|   });
 | |
| }
 | |
| 
 | |
| var promiseForEachSessionRestoreFile = async function (cb) {
 | |
|   for (let key of SessionFile.Paths.loadOrder) {
 | |
|     let data = "";
 | |
|     try {
 | |
|       data = await IOUtils.readUTF8(SessionFile.Paths[key], {
 | |
|         decompress: true,
 | |
|       });
 | |
|     } catch (ex) {
 | |
|       // Ignore missing files
 | |
|       if (!(DOMException.isInstance(ex) && ex.name == "NotFoundError")) {
 | |
|         throw ex;
 | |
|       }
 | |
|     }
 | |
|     cb(data, key);
 | |
|   }
 | |
| };
 | |
| 
 | |
| function promiseBrowserLoaded(
 | |
|   aBrowser,
 | |
|   ignoreSubFrames = true,
 | |
|   wantLoad = null
 | |
| ) {
 | |
|   return BrowserTestUtils.browserLoaded(aBrowser, !ignoreSubFrames, wantLoad);
 | |
| }
 | |
| 
 | |
| function whenWindowLoaded(aWindow, aCallback) {
 | |
|   aWindow.addEventListener(
 | |
|     "load",
 | |
|     function () {
 | |
|       executeSoon(function executeWhenWindowLoaded() {
 | |
|         aCallback(aWindow);
 | |
|       });
 | |
|     },
 | |
|     { once: true }
 | |
|   );
 | |
| }
 | |
| function promiseWindowLoaded(aWindow) {
 | |
|   return new Promise(resolve => whenWindowLoaded(aWindow, resolve));
 | |
| }
 | |
| 
 | |
| var gUniqueCounter = 0;
 | |
| function r() {
 | |
|   return Date.now() + "-" + ++gUniqueCounter;
 | |
| }
 | |
| 
 | |
| function* BrowserWindowIterator() {
 | |
|   for (let currentWindow of Services.wm.getEnumerator("navigator:browser")) {
 | |
|     if (!currentWindow.closed) {
 | |
|       yield currentWindow;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| var gWebProgressListener = {
 | |
|   _callback: null,
 | |
| 
 | |
|   setCallback(aCallback) {
 | |
|     if (!this._callback) {
 | |
|       window.gBrowser.addTabsProgressListener(this);
 | |
|     }
 | |
|     this._callback = aCallback;
 | |
|   },
 | |
| 
 | |
|   unsetCallback() {
 | |
|     if (this._callback) {
 | |
|       this._callback = null;
 | |
|       window.gBrowser.removeTabsProgressListener(this);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
 | |
|     if (
 | |
|       aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
 | |
|       aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
 | |
|       aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW
 | |
|     ) {
 | |
|       this._callback(aBrowser);
 | |
|     }
 | |
|   },
 | |
| };
 | |
| 
 | |
| registerCleanupFunction(function () {
 | |
|   gWebProgressListener.unsetCallback();
 | |
| });
 | |
| 
 | |
| var gProgressListener = {
 | |
|   _callback: null,
 | |
| 
 | |
|   setCallback(callback) {
 | |
|     Services.obs.addObserver(this, "sessionstore-debug-tab-restored");
 | |
|     this._callback = callback;
 | |
|   },
 | |
| 
 | |
|   unsetCallback() {
 | |
|     if (this._callback) {
 | |
|       this._callback = null;
 | |
|       Services.obs.removeObserver(this, "sessionstore-debug-tab-restored");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   observe(browser, topic, data) {
 | |
|     gProgressListener.onRestored(browser);
 | |
|   },
 | |
| 
 | |
|   onRestored(browser) {
 | |
|     if (ss.getInternalObjectState(browser) == TAB_STATE_RESTORING) {
 | |
|       let args = [browser].concat(gProgressListener._countTabs());
 | |
|       gProgressListener._callback.apply(gProgressListener, args);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _countTabs() {
 | |
|     let needsRestore = 0,
 | |
|       isRestoring = 0,
 | |
|       wasRestored = 0;
 | |
| 
 | |
|     for (let win of BrowserWindowIterator()) {
 | |
|       for (let i = 0; i < win.gBrowser.tabs.length; i++) {
 | |
|         let browser = win.gBrowser.tabs[i].linkedBrowser;
 | |
|         let state = ss.getInternalObjectState(browser);
 | |
|         if (browser.isConnected && !state) {
 | |
|           wasRestored++;
 | |
|         } else if (state == TAB_STATE_RESTORING) {
 | |
|           isRestoring++;
 | |
|         } else if (state == TAB_STATE_NEEDS_RESTORE || !browser.isConnected) {
 | |
|           needsRestore++;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return [needsRestore, isRestoring, wasRestored];
 | |
|   },
 | |
| };
 | |
| 
 | |
| registerCleanupFunction(function () {
 | |
|   gProgressListener.unsetCallback();
 | |
| });
 | |
| 
 | |
| // Close all but our primary window.
 | |
| function promiseAllButPrimaryWindowClosed() {
 | |
|   let windows = [];
 | |
|   for (let win of BrowserWindowIterator()) {
 | |
|     if (win != window) {
 | |
|       windows.push(win);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return Promise.all(windows.map(BrowserTestUtils.closeWindow));
 | |
| }
 | |
| 
 | |
| // Forget all closed windows.
 | |
| function forgetClosedWindows() {
 | |
|   while (ss.getClosedWindowCount() > 0) {
 | |
|     ss.forgetClosedWindow(0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Forget all closed tabs for a window
 | |
| function forgetClosedTabs(win) {
 | |
|   while (ss.getClosedTabCountForWindow(win) > 0) {
 | |
|     ss.forgetClosedTab(win, 0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * When opening a new window it is not sufficient to wait for its load event.
 | |
|  * We need to use whenDelayedStartupFinshed() here as the browser window's
 | |
|  * delayedStartup() routine is executed one tick after the window's load event
 | |
|  * has been dispatched. browser-delayed-startup-finished might be deferred even
 | |
|  * further if parts of the window's initialization process take more time than
 | |
|  * expected (e.g. reading a big session state from disk).
 | |
|  */
 | |
| function whenNewWindowLoaded(aOptions, aCallback) {
 | |
|   let features = "";
 | |
|   let url = "about:blank";
 | |
| 
 | |
|   if ((aOptions && aOptions.private) || false) {
 | |
|     features = ",private";
 | |
|     url = "about:privatebrowsing";
 | |
|   }
 | |
| 
 | |
|   let win = openDialog(
 | |
|     AppConstants.BROWSER_CHROME_URL,
 | |
|     "",
 | |
|     "chrome,all,dialog=no" + features,
 | |
|     url
 | |
|   );
 | |
|   let delayedStartup = promiseDelayedStartupFinished(win);
 | |
| 
 | |
|   let browserLoaded = new Promise(resolve => {
 | |
|     if (url == "about:blank") {
 | |
|       resolve();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     win.addEventListener(
 | |
|       "load",
 | |
|       function () {
 | |
|         let browser = win.gBrowser.selectedBrowser;
 | |
|         promiseBrowserLoaded(browser).then(resolve);
 | |
|       },
 | |
|       { once: true }
 | |
|     );
 | |
|   });
 | |
| 
 | |
|   Promise.all([delayedStartup, browserLoaded]).then(() => aCallback(win));
 | |
| }
 | |
| function promiseNewWindowLoaded(aOptions) {
 | |
|   return new Promise(resolve => whenNewWindowLoaded(aOptions, resolve));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * This waits for the browser-delayed-startup-finished notification of a given
 | |
|  * window. It indicates that the windows has loaded completely and is ready to
 | |
|  * be used for testing.
 | |
|  */
 | |
| function whenDelayedStartupFinished(aWindow, aCallback) {
 | |
|   Services.obs.addObserver(function observer(aSubject, aTopic) {
 | |
|     if (aWindow == aSubject) {
 | |
|       Services.obs.removeObserver(observer, aTopic);
 | |
|       executeSoon(aCallback);
 | |
|     }
 | |
|   }, "browser-delayed-startup-finished");
 | |
| }
 | |
| function promiseDelayedStartupFinished(aWindow) {
 | |
|   return new Promise(resolve => whenDelayedStartupFinished(aWindow, resolve));
 | |
| }
 | |
| 
 | |
| function promiseTabRestored(tab) {
 | |
|   return BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
 | |
| }
 | |
| 
 | |
| function promiseTabRestoring(tab) {
 | |
|   return BrowserTestUtils.waitForEvent(tab, "SSTabRestoring");
 | |
| }
 | |
| 
 | |
| // Removes the given tab immediately and returns a promise that resolves when
 | |
| // all pending status updates (messages) of the closing tab have been received.
 | |
| function promiseRemoveTabAndSessionState(tab) {
 | |
|   let sessionUpdatePromise = BrowserTestUtils.waitForSessionStoreUpdate(tab);
 | |
|   BrowserTestUtils.removeTab(tab);
 | |
|   return sessionUpdatePromise;
 | |
| }
 | |
| 
 | |
| // Write DOMSessionStorage data to the given browser.
 | |
| function modifySessionStorage(browser, storageData, storageOptions = {}) {
 | |
|   let browsingContext = browser.browsingContext;
 | |
|   if (storageOptions && "frameIndex" in storageOptions) {
 | |
|     browsingContext = browsingContext.children[storageOptions.frameIndex];
 | |
|   }
 | |
| 
 | |
|   return SpecialPowers.spawn(
 | |
|     browsingContext,
 | |
|     [[storageData, storageOptions]],
 | |
|     async function ([data, options]) {
 | |
|       let frame = content;
 | |
|       let keys = new Set(Object.keys(data));
 | |
|       let isClearing = !keys.size;
 | |
|       let storage = frame.sessionStorage;
 | |
| 
 | |
|       return new Promise(resolve => {
 | |
|         docShell.chromeEventHandler.addEventListener(
 | |
|           "MozSessionStorageChanged",
 | |
|           function onStorageChanged(event) {
 | |
|             if (event.storageArea == storage) {
 | |
|               keys.delete(event.key);
 | |
|             }
 | |
| 
 | |
|             if (keys.size == 0) {
 | |
|               docShell.chromeEventHandler.removeEventListener(
 | |
|                 "MozSessionStorageChanged",
 | |
|                 onStorageChanged,
 | |
|                 true
 | |
|               );
 | |
|               resolve();
 | |
|             }
 | |
|           },
 | |
|           true
 | |
|         );
 | |
| 
 | |
|         if (isClearing) {
 | |
|           storage.clear();
 | |
|         } else {
 | |
|           for (let key of keys) {
 | |
|             frame.sessionStorage[key] = data[key];
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     }
 | |
|   );
 | |
| }
 | |
| 
 | |
| function pushPrefs(...aPrefs) {
 | |
|   return SpecialPowers.pushPrefEnv({ set: aPrefs });
 | |
| }
 | |
| 
 | |
| function popPrefs() {
 | |
|   return SpecialPowers.popPrefEnv();
 | |
| }
 | |
| 
 | |
| function setScrollPosition(bc, x, y) {
 | |
|   return SpecialPowers.spawn(bc, [x, y], (childX, childY) => {
 | |
|     return new Promise(resolve => {
 | |
|       content.addEventListener(
 | |
|         "mozvisualscroll",
 | |
|         function onScroll(event) {
 | |
|           if (content.document.ownerGlobal.visualViewport == event.target) {
 | |
|             content.removeEventListener("mozvisualscroll", onScroll, {
 | |
|               mozSystemGroup: true,
 | |
|             });
 | |
|             resolve();
 | |
|           }
 | |
|         },
 | |
|         { mozSystemGroup: true }
 | |
|       );
 | |
|       content.scrollTo(childX, childY);
 | |
|     });
 | |
|   });
 | |
| }
 | |
| 
 | |
| async function checkScroll(tab, expected, msg) {
 | |
|   let browser = tab.linkedBrowser;
 | |
|   await TabStateFlusher.flush(browser);
 | |
| 
 | |
|   let scroll = JSON.parse(ss.getTabState(tab)).scroll || null;
 | |
|   is(JSON.stringify(scroll), JSON.stringify(expected), msg);
 | |
| }
 | |
| 
 | |
| function whenDomWindowClosedHandled(aCallback) {
 | |
|   Services.obs.addObserver(function observer(aSubject, aTopic) {
 | |
|     Services.obs.removeObserver(observer, aTopic);
 | |
|     aCallback();
 | |
|   }, "sessionstore-debug-domwindowclosed-handled");
 | |
| }
 | |
| 
 | |
| function getPropertyOfFormField(browserContext, selector, propName) {
 | |
|   return SpecialPowers.spawn(
 | |
|     browserContext,
 | |
|     [selector, propName],
 | |
|     (selectorChild, propNameChild) => {
 | |
|       return content.document.querySelector(selectorChild)[propNameChild];
 | |
|     }
 | |
|   );
 | |
| }
 | |
| 
 | |
| function setPropertyOfFormField(browserContext, selector, propName, newValue) {
 | |
|   return SpecialPowers.spawn(
 | |
|     browserContext,
 | |
|     [selector, propName, newValue],
 | |
|     (selectorChild, propNameChild, newValueChild) => {
 | |
|       let node = content.document.querySelector(selectorChild);
 | |
|       node[propNameChild] = newValueChild;
 | |
| 
 | |
|       let event = node.ownerDocument.createEvent("UIEvents");
 | |
|       event.initUIEvent("input", true, true, node.ownerGlobal, 0);
 | |
|       node.dispatchEvent(event);
 | |
|     }
 | |
|   );
 | |
| }
 | |
| 
 | |
| function promiseOnHistoryReplaceEntry(browser) {
 | |
|   if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
 | |
|     return new Promise(resolve => {
 | |
|       let sessionHistory = browser.browsingContext?.sessionHistory;
 | |
|       if (sessionHistory) {
 | |
|         var historyListener = {
 | |
|           OnHistoryNewEntry() {},
 | |
|           OnHistoryGotoIndex() {},
 | |
|           OnHistoryPurge() {},
 | |
|           OnHistoryReload() {
 | |
|             return true;
 | |
|           },
 | |
| 
 | |
|           OnHistoryReplaceEntry() {
 | |
|             resolve();
 | |
|           },
 | |
| 
 | |
|           QueryInterface: ChromeUtils.generateQI([
 | |
|             "nsISHistoryListener",
 | |
|             "nsISupportsWeakReference",
 | |
|           ]),
 | |
|         };
 | |
| 
 | |
|         sessionHistory.addSHistoryListener(historyListener);
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   return SpecialPowers.spawn(browser, [], () => {
 | |
|     return new Promise(resolve => {
 | |
|       var historyListener = {
 | |
|         OnHistoryNewEntry() {},
 | |
|         OnHistoryGotoIndex() {},
 | |
|         OnHistoryPurge() {},
 | |
|         OnHistoryReload() {
 | |
|           return true;
 | |
|         },
 | |
| 
 | |
|         OnHistoryReplaceEntry() {
 | |
|           resolve();
 | |
|         },
 | |
| 
 | |
|         QueryInterface: ChromeUtils.generateQI([
 | |
|           "nsISHistoryListener",
 | |
|           "nsISupportsWeakReference",
 | |
|         ]),
 | |
|       };
 | |
| 
 | |
|       var { sessionHistory } = this.docShell.QueryInterface(
 | |
|         Ci.nsIWebNavigation
 | |
|       );
 | |
|       if (sessionHistory) {
 | |
|         sessionHistory.legacySHistory.addSHistoryListener(historyListener);
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| }
 | |
| 
 | |
| function loadTestSubscript(filePath) {
 | |
|   Services.scriptloader.loadSubScript(new URL(filePath, gTestPath).href, this);
 | |
| }
 | |
| 
 | |
| function addCoopTask(aFile, aTest, aUrlRoot) {
 | |
|   async function taskToBeAdded() {
 | |
|     info(`File ${aFile} has COOP headers enabled`);
 | |
|     let filePath = `browser/browser/components/sessionstore/test/${aFile}`;
 | |
|     let url = aUrlRoot + `coopHeaderCommon.sjs?fileRoot=${filePath}`;
 | |
|     await aTest(url);
 | |
|   }
 | |
|   Object.defineProperty(taskToBeAdded, "name", { value: aTest.name });
 | |
|   add_task(taskToBeAdded);
 | |
| }
 | |
| 
 | |
| function addNonCoopTask(aFile, aTest, aUrlRoot) {
 | |
|   async function taskToBeAdded() {
 | |
|     await aTest(aUrlRoot + aFile);
 | |
|   }
 | |
|   Object.defineProperty(taskToBeAdded, "name", { value: aTest.name });
 | |
|   add_task(taskToBeAdded);
 | |
| }
 | |
| 
 | |
| function openAndCloseTab(window, url) {
 | |
|   return SessionStoreTestUtils.openAndCloseTab(window, url);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * This is regrettable, but when `promiseBrowserState` resolves, we're still
 | |
|  * midway through loading the tabs. To avoid race conditions in URLs for tabs
 | |
|  * being available, wait for all the loads to finish:
 | |
|  */
 | |
| function promiseSessionStoreLoads(numberOfLoads) {
 | |
|   let loadsSeen = 0;
 | |
|   return new Promise(resolve => {
 | |
|     Services.obs.addObserver(function obs(browser) {
 | |
|       loadsSeen++;
 | |
|       if (loadsSeen == numberOfLoads) {
 | |
|         resolve();
 | |
|       }
 | |
|       // The typeof check is here to avoid one test messing with everything else by
 | |
|       // keeping the observer indefinitely.
 | |
|       if (typeof info == "undefined" || loadsSeen >= numberOfLoads) {
 | |
|         Services.obs.removeObserver(obs, "sessionstore-debug-tab-restored");
 | |
|       }
 | |
|       info("Saw load for " + browser.currentURI.spec);
 | |
|     }, "sessionstore-debug-tab-restored");
 | |
|   });
 | |
| }
 | |
| 
 | |
| function triggerClickOn(target, options) {
 | |
|   let promise = BrowserTestUtils.waitForEvent(target, "click");
 | |
|   if (AppConstants.platform == "macosx") {
 | |
|     options.metaKey = options.ctrlKey;
 | |
|     delete options.ctrlKey;
 | |
|   }
 | |
|   EventUtils.synthesizeMouseAtCenter(target, options);
 | |
|   return promise;
 | |
| }
 | |
| 
 | |
| async function openTabMenuFor(tab) {
 | |
|   let tabMenu = tab.ownerDocument.getElementById("tabContextMenu");
 | |
| 
 | |
|   let tabMenuShown = BrowserTestUtils.waitForEvent(tabMenu, "popupshown");
 | |
|   EventUtils.synthesizeMouseAtCenter(
 | |
|     tab,
 | |
|     { type: "contextmenu" },
 | |
|     tab.ownerGlobal
 | |
|   );
 | |
|   await tabMenuShown;
 | |
| 
 | |
|   return tabMenu;
 | |
| }
 |