<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>Blocking pages from entering BFCache</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
</head>
<body onload="">
<script>

const getUserMediaPrefs = {
  set: [
    ["media.devices.insecure.enabled", true],
    ["media.getusermedia.insecure.enabled", true],
    ["media.navigator.permission.disabled", true],
  ],
};
const msePrefs = {
  set: [
    ["media.mediasource.enabled", true],
    ["media.audio-max-decode-error", 0],
    ["media.video-max-decode-error", 0],
  ]
};

const blockBFCacheTests = [
  {
    name: "Request",
    test: () => {
      return new Promise((resolve) => {
        const xhr = new XMLHttpRequest();
        xhr.open("GET", "slow.sjs");
        xhr.addEventListener("progress", () => { resolve(xhr); }, { once: true });
        xhr.send();
      });
    },
  },
  {
    name: "Background request",
    test: () => {
      return new Promise((resolve) => {
        const xhr = new XMLHttpRequest();
        xhr.open("GET", "slow.sjs");
        xhr.addEventListener("readystatechange", () => { if (xhr.readyState == xhr.HEADERS_RECEIVED) resolve(xhr); });
        xhr.send();
      });
    },
  },
  {
    name: "getUserMedia",
    prefs: getUserMediaPrefs,
    test: () => {
      return navigator.mediaDevices.getUserMedia({ audio: true, fake: true });
    },
  },
  {
    name: "RTCPeerConnection",
    test: () => {
      let pc = new RTCPeerConnection();
      return pc.createOffer();
    },
  },
  {
    name: "MSE",
    prefs: msePrefs,
    test: () => {
      const ms = new MediaSource();
      const el = document.createElement("video");
      el.src = URL.createObjectURL(ms);
      el.preload = "auto";
      return el;
    },
  },
  {
    name: "WebSpeech",
    test: () => {
      return new Promise((resolve) => {
        const utterance = new SpeechSynthesisUtterance('bfcache');
        utterance.lang = 'it-IT-noend';
        utterance.addEventListener('start', () => { resolve(utterance); })
        speechSynthesis.speak(utterance);
      });
    },
  },
  {
    name: "WebVR",
    prefs: {
      set: [
        ["dom.vr.test.enabled", true],
        ["dom.vr.puppet.enabled", true],
        ["dom.vr.require-gesture", false],
      ],
    },
    test: () => {
      return navigator.requestVRServiceTest();
    }
  },
];

if (SpecialPowers.Services.appinfo.fissionAutostart) {
  blockBFCacheTests.push({
    name: "Loading OOP iframe",
    test: () => {
      return new Promise((resolve) => {
        const el = document.body.appendChild(document.createElement("iframe"));
        el.id = "frame";
        addEventListener("message", ({ data }) => {
          if (data == "onload") {
            resolve();
          }
        });
        el.src = "https://example.com/tests/docshell/test/navigation/iframe_slow_onload.html";
      });
    },
    waitForDone: () => {
      SimpleTest.requestFlakyTimeout("Test has a loop in an onload handler that runs for 5000ms, we need to make sure the loop is done before moving to the next test.");
      return new Promise(resolve => {
        setTimeout(resolve, 5000);
      });
    },
  });
}

const dontBlockBFCacheTests = [
  {
    name: "getUserMedia",
    prefs: getUserMediaPrefs,
    test: () => {
      return navigator.mediaDevices.getUserMedia({ video: true, fake: true }).then(stream => {
        stream.getTracks().forEach(track => track.stop());
        return stream;
      });
    },
  },
/*
  Disabled because MediaKeys rely on being destroyed by the CC before they
  notify their window, so the test would intermittently fail depending on
  when the CC runs.

  {
    name: "MSE",
    prefs: msePrefs,
    test: () => {
      return new Promise((resolve) => {
        const ms = new MediaSource();
        const el = document.createElement("video");
        ms.addEventListener("sourceopen", () => { resolve(el) }, { once: true });
        el.src = URL.createObjectURL(ms);
        el.preload = "auto";
      }).then(el => {
        el.src = "";
        return el;
      });
    },
  },
*/
];



function executeTest() {

  let bc = new BroadcastChannel("bfcache_blocking");

  function promiseMessage(type) {
    return new Promise((resolve, reject) => {
      bc.addEventListener("message", (e) => {
        if (e.data.type == type) {
          resolve(e.data);
        }
      }, { once: true });
    });
  }

  function promisePageShow(shouldBePersisted) {
    return promiseMessage("pageshow").then(data => data.persisted == shouldBePersisted);
  }

  function promisePageShowFromBFCache(e) {
    return promisePageShow(true);
  }

  function promisePageShowNotFromBFCache(e) {
    return promisePageShow(false);
  }

  function runTests(testArray, shouldBlockBFCache) {
    for (const { name, prefs = {}, test, waitForDone } of testArray) {
      add_task(async function() {
        await SpecialPowers.pushPrefEnv(prefs, async function() {
          // Load a mostly blank page that we can communicate with over
          // BroadcastChannel (though it will close the BroadcastChannel after
          // receiving the next "load" message, to avoid blocking BFCache).
          let loaded = promisePageShowNotFromBFCache();
          window.open("file_blockBFCache.html", "", "noopener");
          await loaded;

          // Load the same page with a different URL.
          loaded = promisePageShowNotFromBFCache();
          bc.postMessage({ message: "load", url: `file_blockBFCache.html?${name}_${shouldBlockBFCache}` });
          await loaded;

          // Run test script in the second page.
          bc.postMessage({ message: "runScript", fun: test.toString() });
          await promiseMessage("runScriptDone");

          // Go back to the first page (this should just come from the BFCache).
          let goneBack = promisePageShowFromBFCache();
          bc.postMessage({ message: "back" });
          await goneBack;

          // Go forward again to the second page and check that it does/doesn't come
          // from the BFCache.
          let goneForward = promisePageShow(!shouldBlockBFCache);
          bc.postMessage({ message: "forward" });
          let result = await goneForward;
          ok(result, `Page ${shouldBlockBFCache ? "should" : "should not"} have been blocked from going into the BFCache (${name})`);

          // If the test will keep running after navigation, then we need to make
          // sure it's completely done before moving to the next test, to avoid
          // interfering with any following tests. If waitForDone is defined then
          // it'll return a Promise that we can use to wait for the end of the
          // test.
          if (waitForDone) {
            await waitForDone();
          }

          // Do a similar test, but replace the bfcache test page with a new page,
          // not a page coming from the session history.

          // Load the same page with a different URL.
          loaded = promisePageShowNotFromBFCache();
          bc.postMessage({ message: "load", url: `file_blockBFCache.html?p2_${name}_${shouldBlockBFCache}` });
          await loaded;

          // Run the test script.
          bc.postMessage({ message: "runScript", fun: test.toString() });
          await promiseMessage("runScriptDone");

          // Load a new page.
          loaded = promisePageShowNotFromBFCache();
          bc.postMessage({ message: "load", url: "file_blockBFCache.html" });
          await loaded;

          // Go back to the previous page and check that it does/doesn't come
          // from the BFCache.
          goneBack = promisePageShow(!shouldBlockBFCache);
          bc.postMessage({ message: "back" });
          result = await goneBack;
          ok(result, `Page ${shouldBlockBFCache ? "should" : "should not"} have been blocked from going into the BFCache (${name})`);

          if (waitForDone) {
            await waitForDone();
          }

          bc.postMessage({ message: "close" });

          SpecialPowers.popPrefEnv();
        });
      });
    }
  }

  // If Fission is disabled, the pref is no-op.
  SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
    runTests(blockBFCacheTests, true);
    runTests(dontBlockBFCacheTests, false);
  });
}

if (isXOrigin) {
  // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5)
  // Acquire storage access permission here so that the BroadcastChannel used to
  // communicate with the opened windows works in xorigin tests. Otherwise,
  // the iframe containing this page is isolated from first-party storage access,
  // which isolates BroadcastChannel communication.
  SpecialPowers.wrap(document).notifyUserGestureActivation();
  SpecialPowers.addPermission("storageAccessAPI", true, window.location.href).then(() => {
    SpecialPowers.wrap(document).requestStorageAccess().then(() => {
      SpecialPowers.pushPrefEnv({
        set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]]
      }).then(() => {
        executeTest();
      });
    });
  });
} else {
  executeTest();
}

</script>
</body>
</html>
