Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

"use strict";
const { PermissionTestUtils } = ChromeUtils.importESModule(
);
const { UrlClassifierTestUtils } = ChromeUtils.importESModule(
);
const PHISH_TABLE = "moztest-phish-simple";
const DYNAMIC_PHISH_TABLE = "mochitest-phish-simple";
const NORMAL_URL =
const PERMISSION_NAME = "desktop-notification";
async function getTelemetryRate() {
await Services.fog.testFlushAllChildren();
return (
Glean.webNotification.showSafeBrowsingBlock.testGetValue() ?? {
numerator: 0,
denominator: 0,
}
);
}
function waitForDBInit() {
let principal = Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(PHISH_URL),
{}
);
let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
Ci.nsIUrlClassifierDBService
);
return BrowserTestUtils.waitForCondition(
() =>
new Promise(resolve => {
dbService.lookup(principal, PHISH_TABLE, value => {
resolve(value === PHISH_TABLE);
});
}),
"DB lookup confirmed phishing URL is in table"
);
}
function addHostToPhishingTable(host) {
let entry = host + "/";
let updateData = `n:1000\ni:${DYNAMIC_PHISH_TABLE}\nad:1\na:1:32:${entry.length}\n${entry}\n`;
return UrlClassifierTestUtils.useTestDatabase({
pref: "urlclassifier.phishTable",
name: DYNAMIC_PHISH_TABLE,
update: updateData,
});
}
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [
["urlclassifier.phishTable", PHISH_TABLE],
["browser.safebrowsing.phishing.enabled", true],
["dom.webnotifications.block_if_on_safebrowsing", true],
],
});
SafeBrowsing.init();
await waitForDBInit();
});
// Test 1: Normal site registers SW, sends notification - should succeed.
// This is a control test ensuring notifications work for non-phishing sites.
// Also verifies telemetry: denominator increments, numerator does not.
add_task(async function test_sw_notification_allowed_for_normal_site() {
Services.fog.testResetFOG();
let normalPrincipal = Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI("https://example.com"),
{}
);
PermissionTestUtils.add(
normalPrincipal,
PERMISSION_NAME,
Services.perms.ALLOW_ACTION
);
// Step 1 & 2: Visit page and register service worker
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: NORMAL_URL,
},
async browser => {
let result = await SpecialPowers.spawn(browser, [], async () => {
await content.navigator.serviceWorker.register(
"file_safebrowsing_test.serviceworker.js"
);
const reg = await content.navigator.serviceWorker.ready;
return new Promise(resolve => {
content.navigator.serviceWorker.onmessage = event => {
resolve(event.data);
};
reg.active.postMessage("show-notification");
});
});
ok(result.success, "Service worker notification should succeed");
}
);
// Verify permission is still intact
is(
PermissionTestUtils.testPermission(normalPrincipal, PERMISSION_NAME),
Services.perms.ALLOW_ACTION,
"Permission should still be granted for normal site"
);
// Verify telemetry: denominator should be 1, numerator should be 0
let rate = await getTelemetryRate();
is(rate.denominator, 1, "Denominator should be 1 (one notification checked)");
is(rate.numerator, 0, "Numerator should be 0 (no notifications blocked)");
// Cleanup
await SpecialPowers.removeAllServiceWorkerData();
PermissionTestUtils.remove(normalPrincipal, PERMISSION_NAME);
});
// Test 2: Site registers SW when clean, then becomes phishing, then tries to
// send notification via SW - should be blocked and permission revoked.
// This is the key scenario we're protecting against.
// Also verifies telemetry: both numerator and denominator increment.
add_task(
async function test_sw_notification_blocked_when_site_becomes_phishing() {
Services.fog.testResetFOG();
let testOrigin = "https://example.com";
let testPrincipal = Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(testOrigin),
{}
);
// Grant notification permission (simulating user granted permission when
// site was clean)
PermissionTestUtils.add(
testPrincipal,
PERMISSION_NAME,
Services.perms.ALLOW_ACTION
);
// Step 1 & 2: Visit clean page and register service worker
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: NORMAL_URL,
},
async browser => {
await SpecialPowers.spawn(browser, [], async () => {
await content.navigator.serviceWorker.register(
"file_safebrowsing_test.serviceworker.js"
);
await content.navigator.serviceWorker.ready;
});
}
);
// Step 3: Tab is now closed (withNewTab closes it)
// Step 4: Site becomes phishing - add example.com to phishing table
info("Adding example.com to phishing table");
await addHostToPhishingTable("example.com");
// Verify the URL is now in the phishing table
let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
Ci.nsIUrlClassifierDBService
);
let lookupResult = await new Promise(resolve => {
dbService.lookup(testPrincipal, DYNAMIC_PHISH_TABLE, value => {
resolve(value);
});
});
is(
lookupResult,
DYNAMIC_PHISH_TABLE,
"example.com should now be in phishing table"
);
// Verify permission is still there before we try the notification
is(
PermissionTestUtils.testPermission(testPrincipal, PERMISSION_NAME),
Services.perms.ALLOW_ACTION,
"Permission should still be granted before notification attempt"
);
// Step 5: SW tries to send notification (no tabs open).
// We need to trigger the SW to send a notification. We can do this by
// opening a new tab to the same origin and messaging the SW.
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: NORMAL_URL,
},
async browser => {
let result = await SpecialPowers.spawn(browser, [], async () => {
const reg = await content.navigator.serviceWorker.ready;
return new Promise(resolve => {
content.navigator.serviceWorker.onmessage = event => {
resolve(event.data);
};
reg.active.postMessage("show-notification");
});
});
ok(
!result.success,
"Service worker notification should fail for phishing site"
);
is(
result.error,
"Permission to show Notification denied.",
"Error message should be generic permission denial"
);
}
);
// Verify permission was revoked
is(
PermissionTestUtils.testPermission(testPrincipal, PERMISSION_NAME),
Services.perms.UNKNOWN_ACTION,
"Permission should be revoked after Safe Browsing block"
);
// Verify telemetry: both numerator and denominator should be 1
let rate = await getTelemetryRate();
is(
rate.denominator,
1,
"Denominator should be 1 (one notification checked)"
);
is(rate.numerator, 1, "Numerator should be 1 (one notification blocked)");
// Cleanup
await SpecialPowers.removeAllServiceWorkerData();
PermissionTestUtils.remove(testPrincipal, PERMISSION_NAME);
Services.prefs.setCharPref("urlclassifier.phishTable", PHISH_TABLE);
}
);