summaryrefslogtreecommitdiffstats
path: root/dom/base/test/browser_bug1058164.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/test/browser_bug1058164.js')
-rw-r--r--dom/base/test/browser_bug1058164.js231
1 files changed, 231 insertions, 0 deletions
diff --git a/dom/base/test/browser_bug1058164.js b/dom/base/test/browser_bug1058164.js
new file mode 100644
index 0000000000..a5e3c61b72
--- /dev/null
+++ b/dom/base/test/browser_bug1058164.js
@@ -0,0 +1,231 @@
+/* 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/. */
+
+"use strict";
+
+SpecialPowers.pushPrefEnv({
+ set: [["security.allow_eval_with_system_principal", true]],
+});
+
+const PAGE =
+ "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+
+let gListenerId = 0;
+
+/**
+ * Adds a content event listener on the given browser element. NOTE: this test
+ * is checking the behavior of pageshow and pagehide as seen by frame scripts,
+ * so it is specifically implemented using the message message manager.
+ * Similar to BrowserTestUtils.waitForContentEvent, but the listener will fire
+ * until it is removed. A callable object is returned that, when called, removes
+ * the event listener. Note that this function works even if the browser's
+ * frameloader is swapped.
+ *
+ * @param {xul:browser} browser
+ * The browser element to listen for events in.
+ * @param {string} eventName
+ * Name of the event to listen to.
+ * @param {function} listener
+ * Function to call in parent process when event fires.
+ * Not passed any arguments.
+ * @param {function} checkFn [optional]
+ * Called with the Event object as argument, should return true if the
+ * event is the expected one, or false if it should be ignored and
+ * listening should continue. If not specified, the first event with
+ * the specified name resolves the returned promise. This is called
+ * within the content process and can have no closure environment.
+ *
+ * @returns function
+ * If called, the return value will remove the event listener.
+ */
+function addContentEventListenerWithMessageManager(
+ browser,
+ eventName,
+ listener,
+ checkFn
+) {
+ let id = gListenerId++;
+ let checkFnSource = checkFn
+ ? encodeURIComponent(escape(checkFn.toSource()))
+ : "";
+
+ // To correctly handle frameloader swaps, we load a frame script
+ // into all tabs but ignore messages from the ones not related to
+ // |browser|.
+
+ /* eslint-disable no-eval */
+ function frameScript(innerId, innerEventName, innerCheckFnSource) {
+ let innerCheckFn;
+ if (innerCheckFnSource) {
+ innerCheckFn = eval(`(() => (${unescape(innerCheckFnSource)}))()`);
+ }
+
+ function innerListener(event) {
+ if (innerCheckFn && !innerCheckFn(event)) {
+ return;
+ }
+ sendAsyncMessage("ContentEventListener:Run", innerId);
+ }
+ function removeListener(msg) {
+ if (msg.data == innerId) {
+ removeMessageListener("ContentEventListener:Remove", removeListener);
+ removeEventListener(innerEventName, innerListener);
+ }
+ }
+ addMessageListener("ContentEventListener:Remove", removeListener);
+ addEventListener(innerEventName, innerListener);
+ }
+ /* eslint-enable no-eval */
+
+ let frameScriptSource = `data:,(${frameScript.toString()})(${id}, "${eventName}",
+ "${checkFnSource}")`;
+
+ let mm = Services.mm;
+
+ function runListener(msg) {
+ if (msg.data == id && msg.target == browser) {
+ listener();
+ }
+ }
+ mm.addMessageListener("ContentEventListener:Run", runListener);
+
+ let needCleanup = true;
+
+ let unregisterFunction = function() {
+ if (!needCleanup) {
+ return;
+ }
+ needCleanup = false;
+ mm.removeMessageListener("ContentEventListener:Run", runListener);
+ mm.broadcastAsyncMessage("ContentEventListener:Remove", id);
+ mm.removeDelayedFrameScript(frameScriptSource);
+ };
+
+ function cleanupObserver(subject, topic, data) {
+ if (subject == browser.messageManager) {
+ unregisterFunction();
+ }
+ }
+
+ mm.loadFrameScript(frameScriptSource, true);
+
+ return unregisterFunction;
+}
+
+/**
+ * Returns a Promise that resolves when it sees a pageshow and
+ * pagehide events in a particular order, where each event must
+ * have the persisted property set to true. Will cause a test
+ * failure to be logged if it sees an event out of order.
+ *
+ * @param browser (<xul:browser>)
+ * The browser to expect the events from.
+ * @param expectedOrder (array)
+ * An array of strings describing what order the pageshow
+ * and pagehide events should be in.
+ * Example:
+ * ["pageshow", "pagehide", "pagehide", "pageshow"]
+ * @returns Promise
+ */
+function prepareForVisibilityEvents(browser, expectedOrder) {
+ return new Promise(resolve => {
+ let order = [];
+
+ let rmvHide, rmvShow;
+
+ let checkSatisfied = () => {
+ if (order.length < expectedOrder.length) {
+ // We're still waiting...
+ return;
+ } else {
+ rmvHide();
+ rmvShow();
+
+ for (let i = 0; i < expectedOrder.length; ++i) {
+ is(order[i], expectedOrder[i], "Got expected event");
+ }
+ resolve();
+ }
+ };
+
+ let eventListener = type => {
+ order.push(type);
+ checkSatisfied();
+ };
+
+ let checkFn = e => e.persisted;
+
+ rmvHide = addContentEventListenerWithMessageManager(
+ browser,
+ "pagehide",
+ () => eventListener("pagehide"),
+ checkFn
+ );
+ rmvShow = addContentEventListenerWithMessageManager(
+ browser,
+ "pageshow",
+ () => eventListener("pageshow"),
+ checkFn
+ );
+ });
+}
+
+/**
+ * Tests that frame scripts get pageshow / pagehide events when
+ * swapping browser frameloaders (which occurs when moving a tab
+ * into a different window).
+ */
+add_task(async function test_swap_frameloader_pagevisibility_events() {
+ // Load a new tab that we'll tear out...
+ let tab = BrowserTestUtils.addTab(gBrowser, PAGE);
+ gBrowser.selectedTab = tab;
+ let firstBrowser = tab.linkedBrowser;
+ await BrowserTestUtils.browserLoaded(firstBrowser);
+
+ // Swap the browser out to a new window
+ let newWindow = gBrowser.replaceTabWithWindow(tab);
+
+ // We have to wait for the window to load so we can get the selected browser
+ // to listen to.
+ await BrowserTestUtils.waitForEvent(newWindow, "DOMContentLoaded");
+ let newWindowBrowser = newWindow.gBrowser.selectedBrowser;
+
+ // Wait for the expected pagehide and pageshow events on the initial browser
+ await prepareForVisibilityEvents(newWindowBrowser, ["pagehide", "pageshow"]);
+
+ // Now let's send the browser back to the original window
+
+ // First, create a new, empty browser tab to replace the window with
+ let newTab = BrowserTestUtils.addTab(gBrowser);
+ gBrowser.selectedTab = newTab;
+ let emptyBrowser = newTab.linkedBrowser;
+
+ // Wait for that initial doc to be visible because if its pageshow hasn't
+ // happened we don't confuse it with the other expected events.
+ await ContentTask.spawn(emptyBrowser, null, async () => {
+ if (content.document.visibilityState === "hidden") {
+ info("waiting for hidden emptyBrowser to be visible");
+ await ContentTaskUtils.waitForEvent(
+ content.document,
+ "visibilitychange",
+ {}
+ );
+ }
+ });
+
+ info("emptyBrowser is shown now.");
+
+ // The empty tab we just added show now fire a pagehide as its replaced,
+ // and a pageshow once the swap is finished.
+ let emptyBrowserPromise = prepareForVisibilityEvents(emptyBrowser, [
+ "pagehide",
+ "pageshow",
+ ]);
+
+ gBrowser.swapBrowsersAndCloseOther(newTab, newWindow.gBrowser.selectedTab);
+
+ await emptyBrowserPromise;
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+});