Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- Manifest: dom/serviceworkers/test/browser-dFPI.toml includes dom/serviceworkers/test/browser-common.toml
- Manifest: dom/serviceworkers/test/browser.toml includes dom/serviceworkers/test/browser-common.toml
"use strict";
/* import-globals-from browser_head.js */
Services.scriptloader.loadSubScript(
this
);
// This test verifies that nsIClearDataService removes service worker
// registrations, and their Cache API quota storage, for the targeted site while
// leaving an unrelated origin's service worker intact.
//
// Existing coverage this does not repeat: browser_pbm_cleanup.js covers
// clearPrivateBrowsingData(); browser_serviceworkers.js and test_quota.js under
// toolkit/components/cleardata/tests cover the principal-, site- and
// host-scoped entry points of the quota cleaner; and
// browser/base/content/test/sanitize/browser_sanitize-offlineData.js covers
// time-range clearing of a service worker through Sanitizer.sanitize(),
// including sparing an origin moved out of the window.
//
// What those leave out, and this covers: deleteDataInTimeRange() with
// CLEAR_DOM_QUOTA, which nothing else calls directly, and the origin's Cache
// API usage asserted in bytes rather than by the registration alone
// (test_quota.js seeds localStorage and IndexedDB, browser_quota.js simpleDB,
// and neither involves a service worker).
const SW_SCRIPT = "empty.js";
const SAS = Cc["@mozilla.org/storage/activity-service;1"].getService(
Ci.nsIStorageActivityService
);
// nsIStorageActivityService works in microseconds (PRTime).
const ONE_HOUR_US = 3600 * 1000 * 1000;
let scopeCounter = 0;
function nextScope() {
return `empty.html?clearData_${scopeCounter++}`;
}
function deleteDataFromPrincipal(principal, flags) {
return new Promise(resolve => {
Services.clearData.deleteDataFromPrincipal(
principal,
false,
flags,
resolve
);
});
}
function deleteDataInTimeRange(from, to, flags) {
return new Promise(resolve => {
Services.clearData.deleteDataInTimeRange(from, to, false, flags, resolve);
});
}
function findActivePrincipal(origin, from, to) {
let principals = SAS.getActiveOrigins(from, to);
for (let i = 0; i < principals.length; i++) {
let principal = principals.queryElementAt(i, Ci.nsIPrincipal);
if (principal.origin === origin) {
return principal;
}
}
return null;
}
add_setup(async function () {
// testing.enabled allows SW registration on the mochitest server's http
// origins.
await SpecialPowers.pushPrefEnv({
set: [
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true],
["dom.serviceWorkers.exemptFromPerDomainMax", true],
],
});
// The tasks below leave service workers and quota storage on the test
// origins, and the time-range task drives the storage-activity table. Clear
// both at the end of the file, after every task and on every exit path, so
// nothing reaches the next file in the manifest. Clearing data records fresh
// activity, so the reset has to come second.
registerCleanupFunction(async () => {
await new Promise(resolve => {
Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
});
SAS.testOnlyReset();
});
});
add_task(async function test_clear_by_principal_isolates_other_origin() {
let scopeA = nextScope();
let scopeB = nextScope();
let regA = await install_sw({
origin: ORIGIN_A,
script: SW_SCRIPT,
scope: scopeA,
});
ok(regA, "Service worker registered on origin A");
let regB = await install_sw({
origin: ORIGIN_B,
script: SW_SCRIPT,
scope: scopeB,
});
ok(regB, "Service worker registered on origin B");
let flags = await deleteDataFromPrincipal(
regA.principal,
Ci.nsIClearDataService.CLEAR_DOM_QUOTA
);
Assert.equal(flags, 0, "deleteDataFromPrincipal completed without failure");
Assert.equal(
countRegistrationsForOrigin(ORIGIN_A),
0,
"Origin A service worker removed by principal clearing"
);
Assert.greaterOrEqual(
countRegistrationsForOrigin(ORIGIN_B),
1,
"Origin B service worker preserved"
);
await deleteDataFromPrincipal(
regB.principal,
Ci.nsIClearDataService.CLEAR_DOM_QUOTA
);
});
add_task(async function test_clear_by_site_removes_sw() {
let scope = nextScope();
let reg = await install_sw({
origin: ORIGIN_A,
script: SW_SCRIPT,
scope,
});
ok(reg, "Service worker registered on origin A");
await clear_qm_origin_group_via_clearData(ORIGIN_A);
Assert.equal(
countRegistrationsForOrigin(ORIGIN_A),
0,
"Origin A service worker removed by site clearing"
);
});
add_task(async function test_clear_also_purges_cache_storage() {
let scope = nextScope();
let reg = await install_sw({
origin: ORIGIN_A,
script: SW_SCRIPT,
scope,
});
ok(reg, "Service worker registered on origin A");
// Read before clearing: reg.activeWorker is gone once the registration is.
const scriptCache = reg.activeWorker.cacheName;
Assert.ok(
(await get_sw_script_cache_names(ORIGIN_A)).includes(scriptCache),
"Origin A has a service worker script cache before clearing"
);
await consume_storage(ORIGIN_A, { cacheBytes: 1024 * 1024, idbBytes: 0 });
let usageBefore = await get_qm_origin_usage(ORIGIN_A);
Assert.ok(
!is_minimum_origin_usage(usageBefore),
`Origin A has Cache API storage before clearing (${usageBefore} bytes)`
);
let flags = await deleteDataFromPrincipal(
reg.principal,
Ci.nsIClearDataService.CLEAR_DOM_QUOTA
);
Assert.equal(flags, 0, "deleteDataFromPrincipal completed without failure");
Assert.equal(
countRegistrationsForOrigin(ORIGIN_A),
0,
"Origin A service worker removed"
);
let usageAfter = await get_qm_origin_usage(ORIGIN_A);
Assert.ok(
is_minimum_origin_usage(usageAfter),
`Origin A Cache API storage cleared (${usageAfter} bytes)`
);
Assert.ok(
!(await get_sw_script_cache_names(ORIGIN_A)).includes(scriptCache),
"Origin A service worker script cache removed by clearing"
);
});
add_task(async function test_clear_by_time_range_scopes_to_window() {
// Only the origins registered below may fall inside the clearing window.
SAS.testOnlyReset();
let resetTime = Date.now() * 1000;
Assert.equal(
SAS.getActiveOrigins(resetTime - ONE_HOUR_US, resetTime + ONE_HOUR_US)
.length,
0,
"Storage activity state is empty after the reset"
);
let scopeA = nextScope();
let scopeB = nextScope();
let regA = await install_sw({
origin: ORIGIN_A,
script: SW_SCRIPT,
scope: scopeA,
});
ok(regA, "Service worker registered on origin A");
let regB = await install_sw({
origin: ORIGIN_B,
script: SW_SCRIPT,
scope: scopeB,
});
ok(regB, "Service worker registered on origin B");
// Activity is stamped with PR_Now() after this millisecond-truncated reading,
// so the upper bound sits in the future rather than racing it.
let now = Date.now() * 1000;
let windowStart = now - ONE_HOUR_US;
let windowEnd = now + ONE_HOUR_US;
await TestUtils.waitForCondition(
() => findActivePrincipal(ORIGIN_A, windowStart, windowEnd),
"Origin A is active within the clearing window"
);
let activeB = await TestUtils.waitForCondition(
() => findActivePrincipal(ORIGIN_B, windowStart, windowEnd),
"Origin B is active before we move it out of the window"
);
// Move origin B out of the window and check it really left: MoveOriginInTime
// stores the timestamp in seconds into a table of microseconds, so the entry
// may end up evicted rather than moved.
SAS.moveOriginInTime(activeB, now - 5 * ONE_HOUR_US);
Assert.ok(
!findActivePrincipal(ORIGIN_B, windowStart, windowEnd),
"Origin B is no longer active within the clearing window"
);
let flags = await deleteDataInTimeRange(
windowStart,
windowEnd,
Ci.nsIClearDataService.CLEAR_DOM_QUOTA
);
Assert.equal(flags, 0, "deleteDataInTimeRange completed without failure");
Assert.equal(
countRegistrationsForOrigin(ORIGIN_A),
0,
"Origin A service worker (active in window) removed by time-range clearing"
);
Assert.greaterOrEqual(
countRegistrationsForOrigin(ORIGIN_B),
1,
"Origin B service worker (outside window) preserved by time-range clearing"
);
});