Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
/* 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
"use strict";
let { UrlClassifierTestUtils } = ChromeUtils.importESModule(
);
const ANNOTATION_TRACKER_IMAGE =
ANNOTATION_TRACKER_DOMAIN + TEST_PATH + "raptor.jpg";
const ANY_TRACKING =
Ci.nsIClassifiedChannel.CLASSIFIED_ANY_BASIC_TRACKING |
Ci.nsIClassifiedChannel.CLASSIFIED_ANY_STRICT_TRACKING;
function recordChannelLifecycle(expectedURLPrePath) {
let events = [];
let topics = [
"http-on-modify-request",
"http-on-examine-response",
"http-on-stop-request",
];
let observers = topics.map(topic => {
let observer = subject => {
let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
if (!httpChannel.URI.spec.startsWith(expectedURLPrePath)) {
return;
}
let classified = subject.QueryInterface(Ci.nsIClassifiedChannel);
events.push({
topic,
time: Date.now(),
flags: classified.classificationFlags,
});
};
Services.obs.addObserver(observer, topic);
return { topic, observer };
});
return {
events,
cleanup() {
for (let { topic, observer } of observers) {
try {
Services.obs.removeObserver(observer, topic);
} catch (e) {
// already removed
}
}
},
};
}
add_setup(async function () {
await UrlClassifierTestUtils.addTestTrackers();
registerCleanupFunction(function () {
UrlClassifierTestUtils.cleanupTestTrackers();
});
await SpecialPowers.pushPrefEnv({
set: [
["privacy.trackingprotection.enabled", true],
["privacy.trackingprotection.annotate_channels", true],
["privacy.trackingprotection.fingerprinting.enabled", false],
["privacy.trackingprotection.cryptomining.enabled", false],
["privacy.trackingprotection.emailtracking.enabled", false],
["privacy.trackingprotection.socialtracking.enabled", false],
["privacy.trackingprotection.antifraud.annotate_channels", false],
["privacy.trackingprotection.consentmanager.annotate_channels", false],
["privacy.trackingprotection.defer_annotation.enabled", true],
],
});
});
add_task(async function test_third_party_annotation_lifecycle() {
// Pref ON, third-party annotation tracker: the channel must suspend after
// examine-response if classification hasn't finished yet, then resume when
// the annotation phase callback fires. We can't deterministically force the
// race ordering, so we assert invariants that hold for both orderings:
// - the load completes (page receives the image),
// - by the time onStopRequest fires, the annotation flag is set.
let lifecycle = recordChannelLifecycle(ANNOTATION_TRACKER_DOMAIN);
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE);
let loaded = await loadImage(tab.linkedBrowser, ANNOTATION_TRACKER_IMAGE);
ok(loaded, "Annotation tracker image loads after suspend/resume");
// Wait for the stop event to be recorded (loadImage resolves on image
// onload, which is delivered after onStopRequest, but be defensive).
await TestUtils.waitForCondition(
() => lifecycle.events.some(e => e.topic === "http-on-stop-request"),
"http-on-stop-request fired for the annotation tracker"
);
let modify = lifecycle.events.find(e => e.topic === "http-on-modify-request");
let examine = lifecycle.events.find(
e => e.topic === "http-on-examine-response"
);
let stop = lifecycle.events.find(e => e.topic === "http-on-stop-request");
ok(modify, "http-on-modify-request observed");
ok(examine, "http-on-examine-response observed");
ok(stop, "http-on-stop-request observed");
Assert.lessOrEqual(
modify.time,
examine.time,
"modify-request precedes examine-response"
);
Assert.lessOrEqual(
examine.time,
stop.time,
"examine-response precedes stop-request"
);
is(
modify.flags & ANY_TRACKING,
0,
"Annotation flag is NOT set at http-on-modify-request (pref ON)"
);
ok(
stop.flags & ANY_TRACKING,
"Annotation flag IS set by http-on-stop-request (pref ON)"
);
lifecycle.cleanup();
await BrowserTestUtils.removeTab(tab);
});
add_task(async function test_first_party_top_level_no_hang() {
// Pref ON, top-level navigation to a tracker domain. The load is
// first-party, so the deferred annotation phase has an effectively-empty
// feature set for AntiFraud/ConsentManager, and the part-6 fixup
// (CheckChannelHelper returning NS_OK without dispatching a worker task)
// must still fire the resume callback. The load must complete.
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
ANNOTATION_TRACKER_DOMAIN + TEST_PATH + "page.html"
);
// If the resume callback didn't fire, BrowserTestUtils.openNewForegroundTab
// would time out waiting for the load to finish. Reaching this line is the
// assertion.
ok(true, "Top-level tracker-domain page loaded with defer pref ON");
await BrowserTestUtils.removeTab(tab);
});