<!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>