Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Errors
- This test gets skipped with pattern: http3 OR http2
- This test failed 1 times in the preceding 30 days. quicksearch this test
- Manifest: docshell/test/navigation/mochitest.toml
<!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.effectiveIsolationStrategy() == SpecialPowers.ISOLATION_STRATEGY.IsolateEverything) {
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();
}
});
});
},
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) => {
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() {
return promisePageShow(true);
}
function promisePageShowNotFromBFCache() {
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) {
// 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>