forked from mirrors/gecko-dev
		
	* 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;
 | 
						|
}
 |