Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
"use strict";
const NET_ERROR_PAGE = "https://does-not-exist.test";
const BAD_CERT = "https://expired.example.com/";
const AUTH_ROUTE =
// eslint-disable-next-line sdl/no-insecure-url
const HTTP_AUTH_PREFS = [
["dom.security.https_first", false],
["network.http.basic_http_auth.enabled", false],
["browser.http.blank_page_with_error_response.enabled", true],
];
const VALID_CAPTIVE_PORTAL_STATES = [
"unknown",
"not_captive",
"unlocked_portal",
"locked_portal",
];
async function getGleanEvents(metric) {
await Services.fog.testFlushAllChildren();
return metric.testGetValue();
}
function assertNeterrorLoadEvent(
event,
{ value = "dnsNotFound", is_frame = "false" } = {}
) {
Assert.equal(event.extra.value, value, `value is ${value}`);
Assert.equal(event.extra.is_frame, is_frame, `is_frame is ${is_frame}`);
Assert.equal(
event.extra.no_connectivity,
"false",
"no_connectivity is false"
);
Assert.ok(
VALID_CAPTIVE_PORTAL_STATES.includes(event.extra.captive_portal_state),
`captive_portal_state "${event.extra.captive_portal_state}" is a valid state`
);
Assert.equal(event.extra.trr_only, "false", "trr_only is false");
}
async function openNetErrorPage(url, prefs = []) {
if (prefs.length) {
await SpecialPowers.pushPrefEnv({ set: prefs });
}
await Services.fog.testFlushAllChildren();
Services.fog.testResetFOG();
let pageLoaded;
let tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
() => {
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url);
pageLoaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
},
false
);
await pageLoaded;
return tab;
}
async function clickNetErrorCardButton(browser, buttonProp) {
await SpecialPowers.spawn(browser, [buttonProp], async prop => {
const card =
content.document.querySelector("net-error-card")?.wrappedJSObject;
await card.getUpdateComplete();
const el = await ContentTaskUtils.waitForCondition(
() => card[prop],
`Waiting for ${prop}`
);
el.click();
});
}
function assertNeterrorClickEvent(events, errorCode) {
Assert.equal(events.length, 1, "One click event recorded");
Assert.equal(events[0].extra.value, errorCode, `value is ${errorCode}`);
Assert.equal(events[0].extra.is_frame, "false", "Not in an iframe");
}
// Test: load_aboutneterror fires for a DNS error (top-level)
add_task(async function test_feltprivacy_neterror_load() {
await Services.fog.testFlushAllChildren();
Services.fog.testResetFOG();
let pageLoaded;
let tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
() => {
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, NET_ERROR_PAGE);
let browser = gBrowser.selectedBrowser;
pageLoaded = BrowserTestUtils.waitForErrorPage(browser);
},
false
);
await pageLoaded;
let events = await TestUtils.waitForCondition(
() => getGleanEvents(Glean.securityUiNeterror.loadAboutneterror),
"Waiting for load_aboutneterror Glean event"
);
Assert.equal(events.length, 1, "Exactly one load event recorded");
assertNeterrorLoadEvent(events[0]);
BrowserTestUtils.removeTab(tab);
});
// Test: load_aboutneterror fires with is_frame=true for iframe errors
add_task(async function test_feltprivacy_neterror_load_iframe() {
await Services.fog.testFlushAllChildren();
Services.fog.testResetFOG();
let tab = await openErrorPage(NET_ERROR_PAGE, true);
let events = await TestUtils.waitForCondition(
() => getGleanEvents(Glean.securityUiNeterror.loadAboutneterror),
"Waiting for load_aboutneterror Glean event in iframe"
);
Assert.equal(events.length, 1, "Exactly one load event recorded");
assertNeterrorLoadEvent(events[0], { is_frame: "true" });
BrowserTestUtils.removeTab(tab);
});
// Test: cert errors do NOT fire load_aboutneterror
add_task(async function test_feltprivacy_certerror_no_neterror() {
await Services.fog.testFlushAllChildren();
Services.fog.testResetFOG();
let tab = await openErrorPage(BAD_CERT, false);
// Wait for the cert error telemetry to confirm the page fully loaded
// and telemetry was processed, then verify no neterror event fired.
await TestUtils.waitForCondition(
() => getGleanEvents(Glean.securityUiCerterror.loadAboutcerterror),
"Waiting for cert error load event to confirm page loaded"
);
let events = Glean.securityUiNeterror.loadAboutneterror.testGetValue();
Assert.equal(events, null, "No neterror event for cert errors");
BrowserTestUtils.removeTab(tab);
});
// -- Legacy path (felt-privacy disabled) --
// Test: load_aboutneterror fires on the legacy path for a DNS error
add_task(async function test_legacy_neterror_load() {
await SpecialPowers.pushPrefEnv({
set: [["security.certerrors.felt-privacy-v1", false]],
});
await Services.fog.testFlushAllChildren();
Services.fog.testResetFOG();
let pageLoaded;
let tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
() => {
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, NET_ERROR_PAGE);
let browser = gBrowser.selectedBrowser;
pageLoaded = BrowserTestUtils.waitForErrorPage(browser);
},
false
);
await pageLoaded;
let events = await TestUtils.waitForCondition(
() => getGleanEvents(Glean.securityUiNeterror.loadAboutneterror),
"Waiting for load_aboutneterror Glean event (legacy path)"
);
Assert.equal(events.length, 1, "Exactly one load event recorded");
assertNeterrorLoadEvent(events[0]);
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});
// Test: cert errors do NOT fire load_aboutneterror on the legacy path
add_task(async function test_legacy_certerror_no_neterror() {
await SpecialPowers.pushPrefEnv({
set: [["security.certerrors.felt-privacy-v1", false]],
});
await Services.fog.testFlushAllChildren();
Services.fog.testResetFOG();
let tab = await openErrorPage(BAD_CERT, false);
// Wait for the cert error telemetry to confirm the page fully loaded
// and telemetry was processed, then verify no neterror event fired.
await TestUtils.waitForCondition(
() => getGleanEvents(Glean.securityUiCerterror.loadAboutcerterror),
"Waiting for cert error load event to confirm page loaded (legacy)"
);
let events = Glean.securityUiNeterror.loadAboutneterror.testGetValue();
Assert.equal(events, null, "No neterror event for cert errors (legacy)");
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});
// -- Felt Privacy net-error click events --
// Test: click_try_again_button fires on a net error page
add_task(async function test_feltprivacy_neterror_click_try_again() {
await SpecialPowers.pushPrefEnv({
set: [["browser.netError.searchCTA.enabled", false]],
});
let tab = await openNetErrorPage(NET_ERROR_PAGE);
let browser = gBrowser.selectedBrowser;
let nextErrorPage = BrowserTestUtils.waitForErrorPage(browser);
await clickNetErrorCardButton(browser, "tryAgainButton");
await nextErrorPage;
let events = await TestUtils.waitForCondition(
() => getGleanEvents(Glean.securityUiNeterror.clickTryAgainButton),
"Waiting for clickTryAgainButton Glean event"
);
assertNeterrorClickEvent(events, "dnsNotFound");
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});
// Test: click_learn_more_link fires on a net error page
add_task(async function test_feltprivacy_neterror_click_learn_more() {
let tab = await openNetErrorPage(NET_ERROR_PAGE);
let browser = gBrowser.selectedBrowser;
let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
await clickNetErrorCardButton(browser, "learnMoreLink");
let learnMoreTab = await newTabPromise;
let events = await TestUtils.waitForCondition(
() => getGleanEvents(Glean.securityUiNeterror.clickLearnMoreLink),
"Waiting for clickLearnMoreLink Glean event"
);
assertNeterrorClickEvent(events, "dnsNotFound");
BrowserTestUtils.removeTab(learnMoreTab);
BrowserTestUtils.removeTab(tab);
});
// Test: click_advanced_button fires on a net error page
add_task(async function test_feltprivacy_neterror_click_advanced_button() {
let tab = await openNetErrorPage(AUTH_ROUTE, HTTP_AUTH_PREFS);
let browser = gBrowser.selectedBrowser;
await clickNetErrorCardButton(browser, "advancedButton");
let events = await TestUtils.waitForCondition(
() => getGleanEvents(Glean.securityUiNeterror.clickAdvancedButton),
"Waiting for clickAdvancedButton Glean event"
);
assertNeterrorClickEvent(events, "basicHttpAuthDisabled");
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});
// Test: click_return_button_adv fires on a net error page
add_task(async function test_feltprivacy_neterror_click_return_button_adv() {
let tab = await openNetErrorPage(AUTH_ROUTE, HTTP_AUTH_PREFS);
let browser = gBrowser.selectedBrowser;
let navPromise = BrowserTestUtils.browserLoaded(browser);
await clickNetErrorCardButton(browser, "returnButton");
await navPromise;
let events = await TestUtils.waitForCondition(
() => getGleanEvents(Glean.securityUiNeterror.clickReturnButtonAdv),
"Waiting for clickReturnButtonAdv Glean event"
);
assertNeterrorClickEvent(events, "basicHttpAuthDisabled");
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});
// -- TRR-only mode (DoH warning page telemetry) --
function assertDohEventExtras(event, label) {
Assert.equal(event.extra.value, "TRROnlyFailure", `${label}: value`);
Assert.equal(event.extra.mode, "3", `${label}: mode`);
Assert.equal(
event.extra.provider_key,
"mozilla.cloudflare-dns.com",
`${label}: provider_key`
);
Assert.equal(
event.extra.skip_reason,
"TRR_UNKNOWN_CHANNEL_FAILURE",
`${label}: skip_reason`
);
}
// Test: DoH warning page telemetry for load and button clicks in TRR-only mode
add_task(async function test_trr_only_telemetry() {
await Services.fog.testFlushAllChildren();
Services.fog.testResetFOG();
let browser = await loadTRRErrorPage();
let loadEvents = await TestUtils.waitForCondition(
() => getGleanEvents(Glean.securityDohNeterror.loadDohwarning),
"Waiting for DoH neterror load Glean event"
);
Assert.equal(loadEvents.length, 1, "Exactly one DoH load event");
assertDohEventExtras(loadEvents[0], "load event");
let tabOpenPromise = BrowserTestUtils.waitForNewTab(
gBrowser,
"about:preferences#privacy-doh"
);
await SpecialPowers.spawn(browser, [], async function () {
const doc = content.document;
const netErrorCard = doc.querySelector("net-error-card")?.wrappedJSObject;
if (netErrorCard) {
await netErrorCard.getUpdateComplete();
const trrSettingsButton = await ContentTaskUtils.waitForCondition(
() => netErrorCard.shadowRoot.getElementById("trrSettingsButton"),
"Waiting for trrSettingsButton"
);
trrSettingsButton.click();
const tryAgainButton = await ContentTaskUtils.waitForCondition(
() => netErrorCard.tryAgainButton,
"Waiting for tryAgainButton"
);
tryAgainButton.click();
} else {
let buttons = ["trrSettingsButton", "neterrorTryAgainButton"];
for (let buttonId of buttons) {
let button = await ContentTaskUtils.waitForCondition(
() => doc.getElementById(buttonId),
`Waiting for button ${buttonId}`
);
button.click();
}
}
}).catch(e => {
if (e.message && !e.message.includes("Actor 'SpecialPowers' destroyed")) {
throw e;
}
});
await tabOpenPromise;
await BrowserTestUtils.waitForErrorPage(browser);
Assert.equal(
gBrowser.tabs.length,
3,
"Should open about:preferences#privacy-doh in another tab"
);
let settingsEvents = await TestUtils.waitForCondition(
() => getGleanEvents(Glean.securityDohNeterror.clickSettingsButton),
"Waiting for DoH settings click Glean event"
);
Assert.equal(settingsEvents.length, 1, "Exactly one settings click event");
assertDohEventExtras(settingsEvents[0], "settings click event");
let tryAgainEvents = await TestUtils.waitForCondition(
() => getGleanEvents(Glean.securityDohNeterror.clickTryAgainButton),
"Waiting for DoH try-again click Glean event"
);
Assert.equal(tryAgainEvents.length, 1, "Exactly one try-again click event");
assertDohEventExtras(tryAgainEvents[0], "try-again click event");
BrowserTestUtils.removeTab(gBrowser.tabs[2]);
BrowserTestUtils.removeTab(gBrowser.tabs[1]);
resetTRRPrefs();
});