Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
"use strict";
// A bfcache history restore does not go through OnStartNavigation, so it must
// not overwrite the live record's finalHost while the armed client bounce
// detection timer keeps running. Otherwise the site the user just left is still
// in the bounce set but no longer the final host, and gets classified.
let bounceTrackingProtection = Cc[
"@mozilla.org/bounce-tracking-protection;1"
].getService(Ci.nsIBounceTrackingProtection);
// The history traversal has to land inside the pending finalization window.
// The test asserts that it did, so a slow run fails rather than passing without
// exercising the restore. The timer is long enough to not fire under chaos
// mode, so closing the tab finalizes the record.
const TIMER_PERIOD_MS = 60000;
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [
[
"privacy.bounceTrackingProtection.mode",
Ci.nsIBounceTrackingProtection.MODE_ENABLED,
],
["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
[
"privacy.bounceTrackingProtection.clientBounceDetectionTimerPeriodMS",
TIMER_PERIOD_MS,
],
// The defect requires the page being left to be bfcached.
["browser.sessionhistory.max_total_viewers", 10],
],
});
registerCleanupFunction(() => {
bounceTrackingProtection.clearAll();
});
});
function getStartURL(origin) {
return getBaseUrl(origin) + "file_start.html";
}
add_task(async function test_bfcache_restore_overwrites_finalhost() {
bounceTrackingProtection.clearAll();
let urlA = getStartURL(ORIGIN_A);
let urlB = getStartURL(ORIGIN_B);
let recordBounces;
await BrowserTestUtils.withNewTab(urlA, async browser => {
// Marker used to prove the traversal below was served from bfcache rather
// than being a fresh load, which would go through OnStartNavigation and not
// exhibit the defect.
await SpecialPowers.spawn(browser, [], () => {
content.window.wrappedJSObject.__btpBfcacheMarker = "present";
});
// A -> B with user activation. This ends A's extended navigation and starts a
// new one with initialHost A. B's response puts B in the bounce set and arms
// the finalization timer, and B's load sets finalHost to B, which is what
// would exempt B. finalHost is set on the parent's STATE_STOP, which can
// arrive after the content load event.
let loadedB = BrowserTestUtils.browserStopped(browser, urlB);
let timerArmedBefore = ChromeUtils.now();
await navigateLinkClick(browser, new URL(urlB));
await loadedB;
// Listen before going back, in case the restore finalizes the record. The
// A -> B finalization already ran synchronously in OnStartNavigation.
recordBounces = waitForRecordBounces(browser, 0);
// Go back to A. Deliberately chrome driven: clicking in B's content area
// would record user activation for B and exempt it, masking the defect.
let restored = BrowserTestUtils.waitForLocationChange(gBrowser, urlA);
browser.goBack();
await restored;
Assert.less(
ChromeUtils.now() - timerArmedBefore,
TIMER_PERIOD_MS,
"A should have been restored before the finalization timer fired, " +
"otherwise this test is not exercising the restore path."
);
let fromBfcache = await SpecialPowers.spawn(browser, [], () => {
return content.window.wrappedJSObject.__btpBfcacheMarker;
});
Assert.equal(
fromBfcache,
"present",
"A should have been restored from bfcache, otherwise this test is not " +
"exercising the restore path."
);
});
await recordBounces;
let candidates = bounceTrackingProtection
.testGetBounceTrackerCandidateHosts({})
.map(entry => entry.siteHost);
Assert.ok(
!candidates.includes(SITE_B),
`${SITE_B} was navigated to by the user and was the final host, so it should ` +
`not be classified after a Back to a bfcached page. ` +
`Got: ${JSON.stringify(candidates)}`
);
});
// Control for the task above: identical up to the history traversal, which is
// omitted. finalHost stays B, so B is exempt and nothing is classified. This is
// what isolates the restore as the cause rather than the navigation to B.
add_task(async function test_no_traversal_does_not_classify() {
bounceTrackingProtection.clearAll();
let urlB = getStartURL(ORIGIN_B);
let recordBounces;
await BrowserTestUtils.withNewTab(getStartURL(ORIGIN_A), async browser => {
let loadedB = BrowserTestUtils.browserStopped(browser, urlB);
await navigateLinkClick(browser, new URL(urlB));
await loadedB;
// No Back here. The same tab close finalizes the record.
recordBounces = waitForRecordBounces(browser, 0);
});
await recordBounces;
let candidates = bounceTrackingProtection
.testGetBounceTrackerCandidateHosts({})
.map(entry => entry.siteHost);
Assert.ok(
!candidates.includes(SITE_B),
`${SITE_B} should not be classified without the history traversal. ` +
`Got: ${JSON.stringify(candidates)}`
);
});