Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: debug
- Manifest: toolkit/components/antitracking/test/browser/browser.toml
/* vim: set ts=2 et sw=2 tw=80: */
/* 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";
// Tests that TrackingDBService's read paths force-flush live
// ContentBlockingLogs before querying, so the Protection Dashboard sees events
// from long-lived tabs that have not yet closed. Also verifies that repeat
// flushes (query-time + teardown) don't double-count via the per-origin
// Stringify cursor.
const TrackingDBService = Cc["@mozilla.org/tracking-db-service;1"].getService(
Ci.nsITrackingDBService
);
const DATE_FROM = Date.now() - 24 * 60 * 60 * 1000;
const DATE_TO = Date.now() + 24 * 60 * 60 * 1000;
async function queryAllTypes() {
const rows = await TrackingDBService.getEventsByDateRange(DATE_FROM, DATE_TO);
let total = 0;
for (const row of rows) {
total += row.getResultByName("count");
}
return total;
}
async function loadTrackerImage(browser, url) {
await SpecialPowers.spawn(browser, [url], async u => {
const img = content.document.createElement("img");
await new content.Promise(resolve => {
img.onload = resolve;
img.onerror = resolve;
img.src = u;
content.document.body.appendChild(img);
});
});
}
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [
["browser.contentblocking.database.enabled", true],
["browser.contentblocking.database.flushOnQuery.enabled", true],
["privacy.trackingprotection.enabled", true],
["privacy.trackingprotection.annotate_channels", true],
],
});
await UrlClassifierTestUtils.addTestTrackers();
await TrackingDBService.clearAll();
registerCleanupFunction(async () => {
await TrackingDBService.clearAll();
UrlClassifierTestUtils.cleanupTestTrackers();
});
});
add_task(async function flush_on_query_reflects_live_tab() {
const countBefore = await queryAllTypes();
is(countBefore, 0, "DB is empty at test start");
const tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
const browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
// Embed a third-party tracker. ETP should block it.
await loadTrackerImage(
browser,
TEST_3RD_PARTY_DOMAIN_TP + TEST_PATH + "raptor.jpg?" + Math.random()
);
// Confirm the in-memory log picked up a blocking event.
const log = JSON.parse(await browser.getContentBlockingLog());
is(Object.keys(log).length, 1, "ContentBlockingLog recorded one origin");
// Query the DB — this must flush the live log before reading.
const countLive = await queryAllTypes();
is(countLive, 1, "Open-tab events appear in DB via flush-on-query");
// Query again: same data should not double-count.
const countLive2 = await queryAllTypes();
is(
countLive2,
countLive,
"Repeat query does not double-count the same events (cursor works)"
);
// Close the tab: the teardown flush must also not double-count.
BrowserTestUtils.removeTab(tab);
// Give the teardown DeferredTask a chance to run; query again will await
// any pending writes via the flush barrier.
const countAfterClose = await queryAllTypes();
is(
countAfterClose,
countLive,
"Teardown flush is idempotent with a prior query-time flush"
);
await TrackingDBService.clearAll();
});
// Regression test for an early version of the delta-flush logic that tracked
// "already reported" at the LogEntry granularity (a per-origin cursor into
// mLogs). RecordLogInternal aggregates same-(type, blocked) events onto the
// last LogEntry by incrementing mRepeatCount in place rather than appending,
// so the cursor model silently dropped any post-flush increments. This test
// loads two same-origin tracker resources of the same type, querying the DB
// between them, and verifies the second flush picks up exactly one new event.
add_task(async function flush_on_query_handles_aggregated_repeat_count() {
const trackerImgPath = TEST_3RD_PARTY_DOMAIN_TP + TEST_PATH + "raptor.jpg";
const tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
const browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
// First blocked load. Creates a fresh LogEntry with mRepeatCount=1.
// Cache-bust per load so the URL classifier sees a fresh request.
await loadTrackerImage(browser, trackerImgPath + "?first=" + Math.random());
let log = JSON.parse(await browser.getContentBlockingLog());
const trackerOrigin = TEST_3RD_PARTY_DOMAIN_TP.replace(/\/$/, "");
ok(log[trackerOrigin], "Tracker origin recorded after first load");
// Flush the live log into the DB and capture the baseline count.
const countAfterFirst = await queryAllTypes();
Assert.greater(countAfterFirst, 0, "First load reaches the DB via flush");
// Second blocked load of the same type from the same origin. This is the
// path RecordLogInternal aggregates into ++last.mRepeatCount instead of
// appending a new entry, exactly the case the cursor model missed.
await loadTrackerImage(browser, trackerImgPath + "?second=" + Math.random());
// Confirm aggregation actually happened (mRepeatCount went up rather than a
// second LogEntry being appended). Without this, a passing count assertion
// below could just mean the implementation silently appended.
log = JSON.parse(await browser.getContentBlockingLog());
const entries = log[trackerOrigin];
let aggregatedRepeat = 0;
for (const item of entries) {
if (
item[0] === Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT &&
item[1] === true
) {
aggregatedRepeat = Math.max(aggregatedRepeat, item[2]);
}
}
Assert.greaterOrEqual(
aggregatedRepeat,
2,
"Second load aggregated onto an existing LogEntry (mRepeatCount >= 2)"
);
// The delta flush must report exactly the new repeat, not zero and not the
// entire mRepeatCount again.
const countAfterSecond = await queryAllTypes();
is(
countAfterSecond,
countAfterFirst + 1,
"Second flush reports exactly one additional aggregated event"
);
BrowserTestUtils.removeTab(tab);
await TrackingDBService.clearAll();
});
add_task(async function flush_disabled_pref_is_respected() {
await SpecialPowers.pushPrefEnv({
set: [["browser.contentblocking.database.flushOnQuery.enabled", false]],
});
const tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
const browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
await loadTrackerImage(
browser,
TEST_3RD_PARTY_DOMAIN_TP + TEST_PATH + "raptor.jpg?" + Math.random()
);
// With flushOnQuery disabled, the live log's events should NOT yet appear.
const countLive = await queryAllTypes();
is(countLive, 0, "Flush-on-query is gated by the pref");
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
await TrackingDBService.clearAll();
});