commit 40a93f44a616ced3f032554cebc1f8e1a309eb44 parent 186a57072c7dc730136d47268d2b4f42d9f08b75 Author: Jack Brown <jbrown@mozilla.com> Date: Wed, 31 Dec 2025 23:50:56 +0000 Bug 2003587 - Pref security.certerrors.felt-privacy-v1 enabled by default - r=niklas,places-reviewers Differential Revision: https://phabricator.services.mozilla.com/D275665 Diffstat:
20 files changed, 420 insertions(+), 94 deletions(-)
diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js @@ -2913,7 +2913,7 @@ pref("browser.toolbars.bookmarks.showOtherBookmarks", true); // Felt Privacy pref to control simplified private browsing UI pref("browser.privatebrowsing.felt-privacy-v1", false); -pref("security.certerrors.felt-privacy-v1", false); +pref("security.certerrors.felt-privacy-v1", true); // Prefs to control the Firefox Account toolbar menu. // This pref will surface existing Firefox Account information diff --git a/browser/base/content/test/about/browser_aboutCertError_cca_telemetry.js b/browser/base/content/test/about/browser_aboutCertError_cca_telemetry.js @@ -45,7 +45,10 @@ async function checkTelemetry(expectedIssuedByCCA) { ]); } -add_task(async function test_cca_site() { +async function test_cca_site(useFeltPrivacyV1) { + await SpecialPowers.pushPrefEnv({ + set: [["security.certerrors.felt-privacy-v1", useFeltPrivacyV1]], + }); await resetTelemetry(); let browser; let pageLoaded; @@ -66,9 +69,12 @@ add_task(async function test_cca_site() { // Check that telemetry indicates this was issued by CCA. await checkTelemetry("true"); BrowserTestUtils.removeTab(gBrowser.selectedTab); -}); +} -add_task(async function test_non_cca_site() { +async function test_non_cca_site(useFeltPrivacyV1) { + await SpecialPowers.pushPrefEnv({ + set: [["security.certerrors.felt-privacy-v1", useFeltPrivacyV1]], + }); await resetTelemetry(); let browser; let pageLoaded; @@ -89,4 +95,16 @@ add_task(async function test_non_cca_site() { // Check that telemetry indicates this was not issued by CCA. await checkTelemetry("false"); BrowserTestUtils.removeTab(gBrowser.selectedTab); +} + +add_task(async function test_cca_sites() { + for (const useFeltPrivacyV1 of [true, false]) { + await test_cca_site(useFeltPrivacyV1); + } +}); + +add_task(async function test_non_cca_sites() { + for (const useFeltPrivacyV1 of [true, false]) { + await test_non_cca_site(useFeltPrivacyV1); + } }); diff --git a/browser/base/content/test/about/browser_aboutCertError_coep.js b/browser/base/content/test/about/browser_aboutCertError_coep.js @@ -10,6 +10,8 @@ add_task(async function test_coepError() { let browser; let pageLoaded; + await setSecurityCertErrorsFeltPrivacyToFalse(); + const uri = `${AUTH_ROUTE}?error=coep`; await BrowserTestUtils.openNewForegroundTab( diff --git a/browser/base/content/test/about/browser_aboutCertError_coop.js b/browser/base/content/test/about/browser_aboutCertError_coop.js @@ -29,6 +29,7 @@ function waitForNewTabAndErrorPage() { } add_task(async function test_coopError() { + await setSecurityCertErrorsFeltPrivacyToFalse(); let iframeTab = await BrowserTestUtils.openNewForegroundTab( gBrowser, `${AUTH_ROUTE}?error=coop` diff --git a/browser/base/content/test/about/browser_aboutCertError_mitm.js b/browser/base/content/test/about/browser_aboutCertError_mitm.js @@ -9,16 +9,17 @@ const PREF_MITM_CANARY_ISSUER = "security.pki.mitm_canary_issuer"; const PREF_MITM_AUTO_ENABLE_ENTERPRISE_ROOTS = "security.certerrors.mitm.auto_enable_enterprise_roots"; const PREF_ENTERPRISE_ROOTS = "security.enterprise_roots.enabled"; +const PREF_FELT_PRIV_V1 = "security.certerrors.felt-privacy-v1"; const UNKNOWN_ISSUER = "https://untrusted.example.com"; -// Check that basic MitM priming works and the MitM error page is displayed successfully. -add_task(async function checkMitmPriming() { +async function checkMitmPriming(useFelt) { await SpecialPowers.pushPrefEnv({ set: [ [PREF_MITM_PRIMING, true], [PREF_MITM_PRIMING_ENDPOINT, UNKNOWN_ISSUER], [PREF_ENTERPRISE_ROOTS, false], + [PREF_FELT_PRIV_V1, useFelt], ], }); @@ -88,18 +89,18 @@ add_task(async function checkMitmPriming() { }); BrowserTestUtils.removeTab(gBrowser.selectedTab); + await SpecialPowers.clearUserPref(PREF_MITM_CANARY_ISSUER); + await SpecialPowers.flushPrefEnv(); +} - Services.prefs.clearUserPref(PREF_MITM_CANARY_ISSUER); -}); - -// Check that we set the enterprise roots pref correctly on MitM -add_task(async function checkMitmAutoEnableEnterpriseRoots() { +async function checkMitmAutoEnableEnterpriseRoots(useFelt) { await SpecialPowers.pushPrefEnv({ set: [ [PREF_MITM_PRIMING, true], [PREF_MITM_PRIMING_ENDPOINT, UNKNOWN_ISSUER], [PREF_MITM_AUTO_ENABLE_ENTERPRISE_ROOTS, true], [PREF_ENTERPRISE_ROOTS, false], + [PREF_FELT_PRIV_V1, !!useFelt], ], }); @@ -154,6 +155,19 @@ add_task(async function checkMitmAutoEnableEnterpriseRoots() { ); BrowserTestUtils.removeTab(gBrowser.selectedTab); + await SpecialPowers.clearUserPref(PREF_MITM_CANARY_ISSUER); + await SpecialPowers.flushPrefEnv(); +} +// Check that basic MitM priming works and the MitM error page is displayed successfully. +add_task(async function runCheckMitmPriming() { + for (const useFelt of [true, false]) { + await checkMitmPriming(useFelt); + } +}); - Services.prefs.clearUserPref(PREF_MITM_CANARY_ISSUER); +// Check that we set the enterprise roots pref correctly on MitM +add_task(async function runCheckMitmAutoEnableEnterpriseRoots() { + for (const useFelt of [true, false]) { + await checkMitmAutoEnableEnterpriseRoots(useFelt); + } }); diff --git a/browser/base/content/test/about/browser_aboutCertError_noSubjectAltName.js b/browser/base/content/test/about/browser_aboutCertError_noSubjectAltName.js @@ -8,34 +8,55 @@ const BROWSER_NAME = document .getString("brandShortName"); const UNKNOWN_ISSUER = "https://no-subject-alt-name.example.com:443"; -const checkAdvancedAndGetTechnicalInfoText = async () => { +const checkAdvancedAndGetTechnicalInfoText = async useFelt => { let doc = content.document; + let badCertTechnicalInfo; + const netErrorCard = doc.querySelector("net-error-card")?.wrappedJSObject; - let advancedButton = doc.getElementById("advancedButton"); + let advancedButton = useFelt + ? netErrorCard.advancedButton + : doc.getElementById("advancedButton"); ok(advancedButton, "advancedButton found"); is( advancedButton.hasAttribute("disabled"), false, "advancedButton should be clickable" ); - advancedButton.click(); - let badCertAdvancedPanel = doc.getElementById("badCertAdvancedPanel"); - ok(badCertAdvancedPanel, "badCertAdvancedPanel found"); + if (useFelt) { + advancedButton.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content); - let badCertTechnicalInfo = doc.getElementById("badCertTechnicalInfo"); - ok(badCertTechnicalInfo, "badCertTechnicalInfo found"); + await ContentTaskUtils.waitForCondition( + () => netErrorCard.advancedContainer, + "Advanced section should be rendered for revoked certificate" + ); + ok(netErrorCard.advancedContainer, "advancedContainer found"); + } else { + advancedButton.click(); + let badCertAdvancedPanel = doc.getElementById("badCertAdvancedPanel"); + ok(badCertAdvancedPanel, "badCertAdvancedPanel found"); + + badCertTechnicalInfo = doc.getElementById("badCertTechnicalInfo"); + ok(badCertTechnicalInfo, "badCertTechnicalInfo found"); + } // Wait until fluent sets the errorCode inner text. await ContentTaskUtils.waitForCondition(() => { - let errorCode = doc.getElementById("errorCode"); - return errorCode.textContent == "SSL_ERROR_BAD_CERT_DOMAIN"; + let errorCode = useFelt + ? netErrorCard.errorCode + : doc.getElementById("errorCode"); + return errorCode.textContent.includes("SSL_ERROR_BAD_CERT_DOMAIN"); }, "correct error code has been set inside the advanced button panel"); - let viewCertificate = doc.getElementById("viewCertificate"); + let viewCertificate = useFelt + ? netErrorCard.viewCertificate + : doc.getElementById("viewCertificate"); ok(viewCertificate, "viewCertificate found"); - return badCertTechnicalInfo.innerHTML; + return useFelt + ? netErrorCard.advancedContainer.innerHTML + : badCertTechnicalInfo.innerHTML; }; const checkCorrectMessages = message => { @@ -50,7 +71,30 @@ const checkCorrectMessages = message => { is(isWrongMessage, false, "That message shouldn't appear"); }; -add_task(async function checkUntrustedCertError() { +const checkFeltCopy = () => { + const netErrorCard = + content.document.querySelector("net-error-card")?.wrappedJSObject; + Assert.equal( + netErrorCard.whyDangerous.dataset.l10nId, + "fp-certerror-bad-domain-why-dangerous-body", + "Using the 'bad domain' variant of the 'Why Dangerous' copy." + ); + Assert.equal( + netErrorCard.whatCanYouDo.dataset.l10nId, + "fp-certerror-bad-domain-what-can-you-do-body", + "Using the 'bad domain' variant of the 'What can you do' copy." + ); + Assert.equal( + netErrorCard.learnMoreLink.getAttribute("support-page"), + "connection-not-secure", + "'Learn more' link points to the standard support page." + ); +}; + +async function checkUntrustedCertError(useFelt) { + await SpecialPowers.pushPrefEnv({ + set: [["security.certerrors.felt-privacy-v1", useFelt]], + }); info( `Loading ${UNKNOWN_ISSUER} which does not have a subject specified in the certificate` ); @@ -59,9 +103,19 @@ add_task(async function checkUntrustedCertError() { info("Clicking the exceptionDialogButton in advanced panel"); let badCertTechnicalInfoText = await SpecialPowers.spawn( browser, - [], + [useFelt], checkAdvancedAndGetTechnicalInfoText ); - checkCorrectMessages(badCertTechnicalInfoText, browser); + if (useFelt) { + await SpecialPowers.spawn(browser, [], checkFeltCopy); + } else { + checkCorrectMessages(badCertTechnicalInfoText, browser); + } BrowserTestUtils.removeTab(gBrowser.selectedTab); +} + +add_task(async function runCheckUntrustedCertError() { + for (const useFelt of [true, false]) { + await checkUntrustedCertError(useFelt); + } }); diff --git a/browser/base/content/test/about/browser_aboutCertError_telemetry.js b/browser/base/content/test/about/browser_aboutCertError_telemetry.js @@ -3,14 +3,17 @@ "use strict"; -requestLongerTimeout(2); +requestLongerTimeout(4); const BAD_CERT = "https://expired.example.com/"; const BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443"; -add_task(async function checkTelemetryClickEvents() { +async function checkTelemetryClickEvents(useFelt) { info("Loading a bad cert page and verifying telemetry click events arrive."); + await SpecialPowers.pushPrefEnv({ + set: [["security.certerrors.felt-privacy-v1", useFelt]], + }); let oldCanRecord = Services.telemetry.canRecordExtended; Services.telemetry.canRecordExtended = true; @@ -47,13 +50,25 @@ add_task(async function checkTelemetryClickEvents() { "error_code_link", "clipboard_button_top", "clipboard_button_bot", - "return_button_top", ]; + const mapRecordObjectsFelt = { + advanced_button: "advancedButton", + learn_more_link: "learnMoreLink", + error_code_link: "errorCode", + clipboard_button_top: "copyButtonTop", + clipboard_button_bot: "copyButtonBot", + return_button_adv: "returnButton", + exception_button: "exceptionButton", + }; + recordedObjects.push("return_button_adv"); if (!useFrame) { recordedObjects.push("exception_button"); } + if (!useFelt) { + recordedObjects.push("return_button_top"); + } for (let object of recordedObjects) { let tab = await openErrorPage(BAD_CERT, useFrame); @@ -89,17 +104,62 @@ add_task(async function checkTelemetryClickEvents() { bc = bc.children[0]; } - await SpecialPowers.spawn(bc, [object], async function (objectId) { - let doc = content.document; - - await ContentTaskUtils.waitForCondition( - () => doc.body.classList.contains("certerror"), - "Wait for certerror to be loaded" - ); - - let domElement = doc.querySelector(`[data-telemetry-id='${objectId}']`); - domElement.click(); - }); + await SpecialPowers.spawn( + bc, + [object, useFelt, mapRecordObjectsFelt], + async function (objectId, use_felt, mapFelt) { + let doc = content.document; + + if (use_felt) { + const netErrorCard = + doc.querySelector("net-error-card").wrappedJSObject; + const advancedButton = netErrorCard.advancedButton; + if ( + !netErrorCard.advancedContainer && + objectId !== "advanced_button" + ) { + advancedButton.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content); + + await ContentTaskUtils.waitForCondition( + () => netErrorCard.advancedContainer, + "Advanced section should be rendered for revoked certificate" + ); + } + if ( + ["clipboard_button_top", "clipboard_button_bot"].includes( + objectId + ) + ) { + netErrorCard.errorCode.click(); + await ContentTaskUtils.waitForCondition( + () => netErrorCard[mapFelt[objectId]], + "Wait for component to render." + ); + } + if (objectId === "exception_button") { + await ContentTaskUtils.waitForCondition( + () => + netErrorCard.exceptionButton && + !netErrorCard.exceptionButton.disabled, + "Wait for the exception button to be created." + ); + } + const el = netErrorCard[mapFelt[objectId]]; + el.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter(el, {}, content); + } else { + await ContentTaskUtils.waitForCondition( + () => doc.body.classList.contains("certerror"), + "Wait for certerror to be loaded" + ); + let domElement = doc.querySelector( + `[data-telemetry-id='${objectId}']` + ); + domElement.click(); + } + } + ); let clickEvents = await TestUtils.waitForCondition(() => { let events = Services.telemetry.snapshotEvents( @@ -148,4 +208,10 @@ add_task(async function checkTelemetryClickEvents() { BrowserTestUtils.removeTab(gBrowser.selectedTab); } } +} + +add_task(async function runCheckTelemetryClickEvents() { + for (const useFelt of [true, false]) { + await checkTelemetryClickEvents(useFelt); + } }); diff --git a/browser/base/content/test/about/browser_aboutNetError_basicHttpAuth.js b/browser/base/content/test/about/browser_aboutNetError_basicHttpAuth.js @@ -19,6 +19,7 @@ add_task(async function test_basicHttpAuth() { ["network.http.basic_http_auth.enabled", false], // blank page with error is priortized ["browser.http.blank_page_with_error_response.enabled", true], + ["security.certerrors.felt-privacy-v1", false], ], }); diff --git a/browser/base/content/test/about/browser_aboutNetError_internet_connection_offline.js b/browser/base/content/test/about/browser_aboutNetError_internet_connection_offline.js @@ -6,6 +6,7 @@ async function checkErrorForInvalidUriLoad(l10nId) { let browser; let pageLoaded; + await setSecurityCertErrorsFeltPrivacyToFalse(); await BrowserTestUtils.openNewForegroundTab( gBrowser, () => { diff --git a/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbar.js b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbar.js @@ -3,6 +3,12 @@ "use strict"; +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["test.wait300msAfterTabSwitch", true]], + }); +}); + add_task(async function bookmarks_toolbar_shown_on_newtab() { let newtab = await BrowserTestUtils.openNewForegroundTab({ gBrowser, diff --git a/browser/base/content/test/about/browser_bug435325.js b/browser/base/content/test/about/browser_bug435325.js @@ -3,7 +3,7 @@ /* Ensure that clicking the button in the Offline mode neterror page makes the browser go online. See bug 435325. */ -add_task(async function checkSwitchPageToOnlineMode() { +async function checkSwitchPageToOnlineMode(useFelt) { // Go offline and disable the proxy and cache, then try to load the test URL. Services.io.offline = true; @@ -15,6 +15,7 @@ add_task(async function checkSwitchPageToOnlineMode() { ["network.proxy.type", 0], ["browser.cache.disk.enable", false], ["browser.cache.memory.enable", false], + ["security.certerrors.felt-privacy-v1", useFelt], ], }); @@ -35,14 +36,18 @@ add_task(async function checkSwitchPageToOnlineMode() { ); // Click on the 'Try again' button. - await SpecialPowers.spawn(browser, [], async function () { + await SpecialPowers.spawn(browser, [useFelt], async function (use_felt) { ok( content.document.documentURI.startsWith("about:neterror?e=netOffline"), "Should be showing error page" ); - content.document - .querySelector("#netErrorButtonContainer > .try-again") - .click(); + const button = use_felt + ? content.document.querySelector("net-error-card").wrappedJSObject + .tryAgainButton + : content.document.querySelector( + "#netErrorButtonContainer > .try-again" + ); + button.click(); }); await changeObserved; @@ -51,6 +56,11 @@ add_task(async function checkSwitchPageToOnlineMode() { "After clicking the 'Try Again' button, we're back online." ); }); +} +add_task(async function runCheckSwitchPageToOnlineMode() { + for (const useFelt of [true, false]) { + await checkSwitchPageToOnlineMode(useFelt); + } }); registerCleanupFunction(function () { diff --git a/browser/base/content/test/about/browser_bug633691.js b/browser/base/content/test/about/browser_bug633691.js @@ -2,7 +2,15 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -add_task(async function test() { +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["test.wait300msAfterTabSwitch", true]], + }); +}); + +add_task(async function testFalse() { + await setSecurityCertErrorsFeltPrivacyToFalse(); + const URL = "data:text/html,<iframe width='700' height='700'></iframe>"; await BrowserTestUtils.withNewTab( { gBrowser, url: URL }, @@ -30,3 +38,36 @@ add_task(async function test() { } ); }); + +add_task(async function testTrue() { + await setSecurityCertErrorsFeltPrivacyToTrue(); + const URL = "data:text/html,<iframe width='700' height='700'></iframe>"; + await BrowserTestUtils.withNewTab( + { gBrowser, url: URL }, + async function (browser) { + let context = await SpecialPowers.spawn(browser, [], function () { + const iframe = content.document.querySelector("iframe"); + iframe.src = "https://expired.example.com/"; + return BrowsingContext.getFromWindow(iframe.contentWindow); + }); + await TestUtils.waitForCondition(() => { + let frame = context.currentWindowGlobal; + return frame && frame.documentURI.spec.startsWith("about:certerror"); + }); + await SpecialPowers.spawn(context, [], async function () { + await ContentTaskUtils.waitForCondition( + () => content.document.readyState == "interactive" + ); + const netErrorCardElement = await ContentTaskUtils.waitForCondition( + () => content.document.querySelector("net-error-card") + ); + const netErrorCard = netErrorCardElement.wrappedJSObject; + Assert.ok(netErrorCard.advancedButton, "Advanced content should exist"); + Assert.ok( + !netErrorCard.advancedContainer, + "Advanced content should not be visible by default" + ); + }); + } + ); +}); diff --git a/devtools/client/inspector/test/browser_inspector_navigate_to_errors.js b/devtools/client/inspector/test/browser_inspector_navigate_to_errors.js @@ -11,6 +11,10 @@ const TEST_URL_2 = "http://127.0.0.1:36325/"; const TEST_URL_3 = "https://www.wronguri.wronguri/"; const TEST_URL_4 = "data:text/html,<html><body>test-doc-4</body></html>"; +const { ContentTaskUtils } = SpecialPowers.ChromeUtils.importESModule( + "resource://testing-common/ContentTaskUtils.sys.mjs" +); + add_task(async function () { // Open the inspector on a valid URL const { inspector } = await openInspectorForURL(TEST_URL_1); @@ -41,6 +45,9 @@ add_task(async function () { ); let domain = TEST_URL_2.match(/^http:\/\/(.*)\/$/)[1]; let errorMsg = bundle.formatStringFromName("connectionFailure", [domain]); + await ContentTaskUtils.waitForCondition(() => + getDisplayedNodeTextContent("#errorShortDesc", inspector) + ); is( await getDisplayedNodeTextContent("#errorShortDesc", inspector), errorMsg, @@ -52,6 +59,9 @@ add_task(async function () { domain = TEST_URL_3.match(/^https:\/\/(.*)\/$/)[1]; errorMsg = bundle.formatStringFromName("dnsNotFound2", [domain]); + await ContentTaskUtils.waitForCondition(() => + getDisplayedNodeTextContent("#errorShortDesc", inspector) + ); is( await getDisplayedNodeTextContent("#errorShortDesc", inspector), errorMsg, diff --git a/devtools/client/inspector/test/head.js b/devtools/client/inspector/test/head.js @@ -1184,7 +1184,7 @@ async function getDisplayedNodeTextContent(selector, inspector) { await waitForMultipleChildrenUpdates(inspector); if (container) { const textContainer = container.elt.querySelector("pre"); - return textContainer.textContent; + return textContainer?.textContent; } return null; } diff --git a/dom/security/test/general/browser_test_framing_error_pages.js b/dom/security/test/general/browser_test_framing_error_pages.js @@ -24,9 +24,16 @@ add_task(async function open_test_xfo_error_page() { await loaded; await SpecialPowers.spawn(browser, [], async function () { - const iframeDoc = - content.document.getElementById("testframe").contentDocument; - let errorPage = iframeDoc.body.innerHTML; + const getErrorPage = async () => + ContentTaskUtils.waitForCondition( + () => + content.document.getElementById("testframe").contentDocument?.body + .innerHTML + ); + await ContentTaskUtils.waitForCondition(() => + getErrorPage()?.then(p => p.includes("csp-xfo-error-title")) + ); + const errorPage = await getErrorPage(); ok(errorPage.includes("csp-xfo-error-title"), "xfo error page correct"); }); }); @@ -44,9 +51,16 @@ add_task(async function open_test_csp_frame_ancestor_error_page() { await loaded; await SpecialPowers.spawn(browser, [], async function () { - const iframeDoc = - content.document.getElementById("testframe").contentDocument; - let errorPage = iframeDoc.body.innerHTML; + const getErrorPage = async () => + ContentTaskUtils.waitForCondition( + () => + content.document.getElementById("testframe").contentDocument?.body + .innerHTML + ); + await ContentTaskUtils.waitForCondition(() => + getErrorPage()?.then(p => p.includes("csp-xfo-error-title")) + ); + const errorPage = await getErrorPage(); ok(errorPage.includes("csp-xfo-error-title"), "csp error page correct"); }); }); diff --git a/dom/security/test/general/test_xfo_error_page.html b/dom/security/test/general/test_xfo_error_page.html @@ -12,12 +12,19 @@ SimpleTest.waitForExplicitFinish(); +const { ContentTaskUtils } = SpecialPowers.ChromeUtils.importESModule( + "resource://testing-common/ContentTaskUtils.sys.mjs" +); + const XFO_ERROR_PAGE_MSG = "This page has an X-Frame-Options policy that prevents it from being loaded in this context"; let xfo_testframe = document.getElementById("xfo_testframe"); -xfo_testframe.onload = function() { +xfo_testframe.onload = async function() { let wrappedXFOFrame = SpecialPowers.wrap(xfo_testframe.contentWindow); + await ContentTaskUtils.waitForCondition( + () => wrappedXFOFrame.document.body.innerHTML.includes(XFO_ERROR_PAGE_MSG) + ); let frameContentXFO = wrappedXFOFrame.document.body.innerHTML; ok(frameContentXFO.includes(XFO_ERROR_PAGE_MSG), "xfo error page correct"); SimpleTest.finish(); diff --git a/toolkit/components/certviewer/tests/browser/browser_openTabAndSendCertInfo.js b/toolkit/components/certviewer/tests/browser/browser_openTabAndSendCertInfo.js @@ -76,6 +76,9 @@ function openCertDownloadDialog(cert) { add_task(async function openFromPopUp() { info("Testing openFromPopUp"); + await SpecialPowers.pushPrefEnv({ + set: [["security.certerrors.felt-privacy-v1", false]], + }); const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( Ci.nsIX509CertDB @@ -217,6 +220,9 @@ add_task(async function testBadCertIframe_feltPrivacyToTrue() { add_task(async function testBadCert() { info("Testing bad cert"); + await SpecialPowers.pushPrefEnv({ + set: [["security.certerrors.felt-privacy-v1", false]], + }); let tab = await openErrorPage(); let tabsCount = gBrowser.tabs.length; @@ -248,6 +254,9 @@ add_task(async function testBadCert() { add_task(async function testBadCertIframe() { info("Testing bad cert in an iframe"); + await SpecialPowers.pushPrefEnv({ + set: [["security.certerrors.felt-privacy-v1", false]], + }); let tab = await openErrorPage(true); let tabsCount = gBrowser.tabs.length; @@ -280,6 +289,9 @@ add_task(async function testBadCertIframe() { add_task(async function testGoodCert() { info("Testing page info"); + await SpecialPowers.pushPrefEnv({ + set: [["security.certerrors.felt-privacy-v1", false]], + }); let url = "https://example.com/"; let tabsCount = gBrowser.tabs.length; @@ -316,6 +328,9 @@ add_task(async function testGoodCert() { add_task(async function testPreferencesCert() { info("Testing preferences cert"); + await SpecialPowers.pushPrefEnv({ + set: [["security.certerrors.felt-privacy-v1", false]], + }); let url = "about:preferences#privacy"; let tabsCount; diff --git a/toolkit/components/places/tests/browser/browser_bug680727.js b/toolkit/components/places/tests/browser/browser_bug680727.js @@ -82,14 +82,19 @@ function errorAsyncListener(aURI, aIsVisited) { reloadListener ); - SpecialPowers.spawn(ourTab.linkedBrowser, [], function () { + SpecialPowers.spawn(ourTab.linkedBrowser, [], async function () { + const netErrorCard = await ContentTaskUtils.waitForCondition( + () => content.document.querySelector("net-error-card")?.wrappedJSObject + ); Assert.ok( - content.document.querySelector("#netErrorButtonContainer > .try-again"), + netErrorCard.tryAgainButton, "The error page has got a .try-again element" ); - content.document - .querySelector("#netErrorButtonContainer > .try-again") - .click(); + EventUtils.synthesizeMouseAtCenter( + netErrorCard.tryAgainButton, + {}, + content + ); }); } diff --git a/toolkit/content/aboutNetError.mjs b/toolkit/content/aboutNetError.mjs @@ -1420,21 +1420,54 @@ function setFocus(selector, position = "afterbegin") { } } -if (!NetErrorCard.isSupported()) { - for (let button of document.querySelectorAll(".try-again")) { - button.addEventListener("click", function () { - retryThis(this); - }); +async function getErrorCode() { + try { + const errorInfo = gIsCertError + ? document.getFailedCertSecurityInfo() + : document.getNetErrorInfo(); + return errorInfo.errorCodeString; + } catch (e) { + return undefined; } +} - initPage(); +async function retryErrorCode() { + return new Promise(res => { + setTimeout(() => { + res(getErrorCode()); + }, 100); + }); +} - // Dispatch this event so tests can detect that we finished loading the error page. - document.dispatchEvent( - new CustomEvent("AboutNetErrorLoad", { bubbles: true }) - ); -} else { - customElements.define("net-error-card", NetErrorCard); - document.body.classList.add("felt-privacy-body"); - document.body.replaceChildren(document.createElement("net-error-card")); +async function init() { + let errorCode = await getErrorCode(); + let i = 0; + while (!errorCode && i < 3) { + i++; + errorCode = await retryErrorCode(); + } } + +async function main() { + await init(); + if (!NetErrorCard.isSupported()) { + for (let button of document.querySelectorAll(".try-again")) { + button.addEventListener("click", function () { + retryThis(this); + }); + } + + initPage(); + + // Dispatch this event so tests can detect that we finished loading the error page. + document.dispatchEvent( + new CustomEvent("AboutNetErrorLoad", { bubbles: true }) + ); + } else { + customElements.define("net-error-card", NetErrorCard); + document.body.classList.add("felt-privacy-body"); + document.body.replaceChildren(document.createElement("net-error-card")); + } +} + +main(); diff --git a/toolkit/content/net-error-card.mjs b/toolkit/content/net-error-card.mjs @@ -44,6 +44,7 @@ export class NetErrorCard extends MozLitElement { static queries = { copyButtonTop: "#copyToClipboardTop", + copyButtonBot: "#copyToClipboardBot", exceptionButton: "#exception-button", errorCode: "#errorCode", advancedContainer: ".advanced-container", @@ -60,6 +61,7 @@ export class NetErrorCard extends MozLitElement { netErrorTitleText: "#neterror-title-text", netErrorLearnMoreLink: "#neterror-learn-more-link", httpAuthIntroText: "#fp-http-auth-disabled-intro-text", + tryAgainButton: "#tryAgainButton", }; static ERROR_CODES = new Set([ @@ -70,7 +72,7 @@ export class NetErrorCard extends MozLitElement { "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT", "SEC_ERROR_EXPIRED_CERTIFICATE", "SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE", - "SSL_ERROR_NO_CYPHER_OVERLAP", + // Bug #2006790 - Temporarily disabling SSL_ERROR_NO_CYPHER_OVERLAP until we create a pref reset button. "MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY", "NS_ERROR_OFFLINE", "NS_ERROR_DOM_COOP_FAILED", @@ -88,7 +90,16 @@ export class NetErrorCard extends MozLitElement { static getCustomErrorCode(defaultCode) { return gOffline ? "NS_ERROR_OFFLINE" - : (NetErrorCard.CUSTOM_ERROR_CODES[defaultCode] ?? defaultCode); + : NetErrorCard.CUSTOM_ERROR_CODES[defaultCode]; + } + + static findSupportedErrorCode(errorInfo) { + let defaultErrorCode = errorInfo.errorCodeString + ? errorInfo.errorCodeString + : gErrorCode; + return NetErrorCard.ERROR_CODES.has(defaultErrorCode) + ? defaultErrorCode + : NetErrorCard.getCustomErrorCode(defaultErrorCode); } static isSupported() { @@ -96,15 +107,17 @@ export class NetErrorCard extends MozLitElement { return false; } - const errorInfo = gIsCertError - ? document.getFailedCertSecurityInfo() - : document.getNetErrorInfo(); - const defaultErrorCode = errorInfo.errorCodeString - ? errorInfo.errorCodeString - : gErrorCode; - const errorCode = NetErrorCard.getCustomErrorCode(defaultErrorCode); + let errorInfo; + try { + errorInfo = gIsCertError + ? document.getFailedCertSecurityInfo() + : document.getNetErrorInfo(); + } catch { + return false; + } - return NetErrorCard.ERROR_CODES.has(errorCode); + const supportedErrorCode = NetErrorCard.findSupportedErrorCode(errorInfo); + return !!supportedErrorCode; } constructor() { @@ -146,17 +159,6 @@ export class NetErrorCard extends MozLitElement { document.dispatchEvent( new CustomEvent("AboutNetErrorLoad", { bubbles: true }) ); - - // Record telemetry when the error page loads - if (gIsCertError && !isCaptive()) { - if (this.failedCertInfo) { - recordSecurityUITelemetry( - "securityUiCerterror", - "loadAboutcerterror", - this.failedCertInfo - ); - } - } } shouldHideExceptionButton() { @@ -180,6 +182,31 @@ export class NetErrorCard extends MozLitElement { this.errorInfo = this.getErrorInfo(); this.hideExceptionButton = this.shouldHideExceptionButton(); + + // Record telemetry when the error page loads + if (gIsCertError && !isCaptive()) { + recordSecurityUITelemetry( + "securityUiCerterror", + "loadAboutcerterror", + this.errorInfo + ); + } + + // Check if the connection is being man-in-the-middled. When the parent + // detects an intercepted connection, the page may be reloaded with a new + // error code (MOZILLA_PKIX_ERROR_MITM_DETECTED). + const mitmPrimingEnabled = RPMGetBoolPref( + "security.certerrors.mitm.priming.enabled" + ); + if ( + mitmPrimingEnabled && + this.errorInfo.errorCodeString == "SEC_ERROR_UNKNOWN_ISSUER" && + // Only do this check for top-level failures. + window.parent == window + ) { + RPMSendAsyncMessage("Browser:PrimeMitm"); + } + this.hostname = HOST_NAME; const { port } = document.location; if (port && port != 443) { @@ -773,6 +800,7 @@ export class NetErrorCard extends MozLitElement { ></moz-button> <div id="certificateErrorText">${this.certificateErrorText}</div> <moz-button + id="copyToClipboardBot" data-telemetry-id="clipboard_button_bot" data-l10n-id="neterror-copy-to-clipboard-button" @click=${this.copyCertErrorTextToClipboard}