tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit cf480d4cec629836555a8ef75a1bc1feb6558180
parent 0f4e2b6d02a87b1389f1753ad340b0eb7c530157
Author: Sandra Qu <squiles@mozilla.com>
Date:   Mon, 27 Oct 2025 15:23:18 +0000

Bug 1990920 Updated test for updated Firefox Desktop network error page appearance/content r=niklas

Differential Revision: https://phabricator.services.mozilla.com/D266228

Diffstat:
Mbrowser/base/content/test/about/browser_aboutCertError.js | 661++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mbrowser/base/content/test/about/head.js | 9+++++++++
Mtoolkit/content/net-error-card.mjs | 13++++++++++++-
3 files changed, 607 insertions(+), 76 deletions(-)

diff --git a/browser/base/content/test/about/browser_aboutCertError.js b/browser/base/content/test/about/browser_aboutCertError.js @@ -8,75 +8,112 @@ const GOOD_PAGE = "https://example.com/"; const GOOD_PAGE_2 = "https://example.org/"; const BAD_CERT = "https://expired.example.com/"; -const UNKNOWN_ISSUER = "https://self-signed.example.com "; +const UNKNOWN_ISSUER = "https://self-signed.example.com"; const BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443"; const { TabStateFlusher } = ChromeUtils.importESModule( "resource:///modules/sessionstore/TabStateFlusher.sys.mjs" ); +// Security CertError Felt Privacy set to false & true +// checked in one checkReturnToAboutHome to avoid duplicating code add_task(async function checkReturnToAboutHome() { info( "Loading a bad cert page directly and making sure 'return to previous page' goes to about:home" ); - for (let useFrame of [false, true]) { - let tab = await openErrorPage(BAD_CERT, useFrame); - let browser = tab.linkedBrowser; - await SpecialPowers.spawn(browser, [], () => { - content.document.notifyUserGestureActivation(); - }); + for (let toggleFeltPrivacy of [ + setSecurityCertErrorsFeltPrivacyToFalse, + setSecurityCertErrorsFeltPrivacyToTrue, + ]) { + await toggleFeltPrivacy(); + + for (let useFrame of [false, true]) { + let tab = await openErrorPage(BAD_CERT, useFrame); + let browser = tab.linkedBrowser; + await SpecialPowers.spawn(browser, [], () => { + content.document.notifyUserGestureActivation(); + }); - is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack"); - is( - browser.webNavigation.canGoForward, - false, - "!webNavigation.canGoForward" - ); + is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack"); + is( + browser.webNavigation.canGoForward, + false, + "!webNavigation.canGoForward" + ); - // Populate the shistory entries manually, since it happens asynchronously - // and the following tests will be too soon otherwise. - await TabStateFlusher.flush(browser); - let { entries } = JSON.parse(SessionStore.getTabState(tab)); - is(entries.length, 1, "there is one shistory entry"); + // Populate the shistory entries manually, since it happens asynchronously + // and the following tests will be too soon otherwise. + await TabStateFlusher.flush(browser); + let { entries } = JSON.parse(SessionStore.getTabState(tab)); + is(entries.length, 1, "there is one shistory entry"); - info("Clicking the go back button on about:certerror"); - let bc = browser.browsingContext; - if (useFrame) { - bc = bc.children[0]; - } - let locationChangePromise = BrowserTestUtils.waitForLocationChange( - gBrowser, - "about:home" - ); - await SpecialPowers.spawn(bc, [useFrame], async function (subFrame) { - let returnButton = content.document.getElementById("returnButton"); - if (!subFrame) { - if (!Services.focus.focusedElement == returnButton) { - await ContentTaskUtils.waitForEvent(returnButton, "focus"); - } - Assert.ok(true, "returnButton has focus"); + info("Clicking the go back button on about:certerror"); + let bc = browser.browsingContext; + if (useFrame) { + bc = bc.children[0]; } - // Note that going back to about:newtab might cause a process flip, if - // the browser is configured to run about:newtab in its own special - // content process. - returnButton.click(); - }); + let locationChangePromise = BrowserTestUtils.waitForLocationChange( + gBrowser, + "about:home" + ); - await locationChangePromise; + if (Services.prefs.getBoolPref("security.certerrors.felt-privacy-v1")) { + info("Felt Privacy enabled - using net-error-card"); + + await SpecialPowers.spawn(bc, [useFrame], async function (subFrame) { + const netErrorCard = + content.document.querySelector("net-error-card").wrappedJSObject; + await netErrorCard.getUpdateComplete(); + const returnButton = netErrorCard.returnButton; + + if (!subFrame) { + if (!Services.focus.focusedElement == returnButton) { + await ContentTaskUtils.waitForEvent(returnButton, "focus"); + } + Assert.ok(true, "returnButton has focus"); + } + // Note that going back to about:newtab might cause a process flip, if + // the browser is configured to run about:newtab in its own special + // content process. + returnButton.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter(returnButton, {}, content); + }); + } else { + info("Felt Privacy disabled - using aboutNetError"); + await SpecialPowers.spawn(bc, [useFrame], async function (subFrame) { + let returnButton = content.document.getElementById("returnButton"); + if (!subFrame) { + if (!Services.focus.focusedElement == returnButton) { + await ContentTaskUtils.waitForEvent(returnButton, "focus"); + } + Assert.ok(true, "returnButton has focus"); + } + // Note that going back to about:newtab might cause a process flip, if + // the browser is configured to run about:newtab in its own special + // content process. + returnButton.click(); + }); + } - is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack"); - is( - browser.webNavigation.canGoForward, - false, - "!webNavigation.canGoForward" - ); - is(gBrowser.currentURI.spec, "about:home", "Went back"); + await locationChangePromise; - BrowserTestUtils.removeTab(gBrowser.selectedTab); + is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack"); + is( + browser.webNavigation.canGoForward, + false, + "!webNavigation.canGoForward" + ); + is(gBrowser.currentURI.spec, "about:home", "Went back"); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + } + SpecialPowers.popPrefEnv(); } }); -add_task(async function checkReturnToPreviousPage() { +// Security CertError Felt Privacy set to false only +add_task(async function checkReturnToPreviousPage_feltPrivacyToFalse() { + await setSecurityCertErrorsFeltPrivacyToFalse(); info( "Loading a bad cert page and making sure 'return to previous page' goes back" ); @@ -144,6 +181,7 @@ add_task(async function checkReturnToPreviousPage() { } }); +// Works for both Security CertError Felt Privacy set to true and false // This checks that the appinfo.appBuildID starts with a date string, // which is required for the misconfigured system time check. add_task(async function checkAppBuildIDIsDate() { @@ -152,12 +190,13 @@ add_task(async function checkAppBuildIDIsDate() { let month = parseInt(appBuildID.substr(4, 2), 10); let day = parseInt(appBuildID.substr(6, 2), 10); - ok(year >= 2016 && year <= 2100, "appBuildID contains a valid year"); - ok(month >= 1 && month <= 12, "appBuildID contains a valid month"); - ok(day >= 1 && day <= 31, "appBuildID contains a valid day"); + Assert.ok(year >= 2016 && year <= 2100, "appBuildID contains a valid year"); + Assert.ok(month >= 1 && month <= 12, "appBuildID contains a valid month"); + Assert.ok(day >= 1 && day <= 31, "appBuildID contains a valid day"); }); -add_task(async function checkAdvancedDetails() { +add_task(async function checkAdvancedDetails_feltPrivacyToFalse() { + await setSecurityCertErrorsFeltPrivacyToFalse(); info( "Loading a bad cert page and verifying the main error and advanced details section" ); @@ -256,7 +295,8 @@ add_task(async function checkAdvancedDetails() { } }); -add_task(async function checkAdvancedDetailsForHSTS() { +add_task(async function checkAdvancedDetailsForHSTS_feltPrivacyToFalse() { + await setSecurityCertErrorsFeltPrivacyToFalse(); info( "Loading a bad STS cert page and verifying the advanced details section" ); @@ -340,7 +380,8 @@ add_task(async function checkAdvancedDetailsForHSTS() { } }); -add_task(async function checkUnknownIssuerLearnMoreLink() { +add_task(async function checkUnknownIssuerLearnMoreLink_feltPrivacyToFalse() { + await setSecurityCertErrorsFeltPrivacyToFalse(); info( "Loading a cert error for self-signed pages and checking the correct link is shown" ); @@ -363,7 +404,8 @@ add_task(async function checkUnknownIssuerLearnMoreLink() { } }); -add_task(async function checkViewCertificate() { +add_task(async function checkViewCertificate_feltPrivacyToFalse() { + await setSecurityCertErrorsFeltPrivacyToFalse(); info("Loading a cert error and checking that the certificate can be shown."); for (let useFrame of [true, false]) { if (useFrame) { @@ -385,7 +427,7 @@ add_task(async function checkViewCertificate() { }); await loaded; - let spec = gBrowser.selectedTab.linkedBrowser.documentURI.spec; + let spec = gBrowser.currentURI.spec; Assert.ok( spec.startsWith("about:certificate"), "about:certificate is the new opened tab" @@ -428,7 +470,8 @@ add_task(async function checkViewCertificate() { } }); -add_task(async function checkBadStsCertHeadline() { +add_task(async function checkBadStsCertHeadline_feltPrivacyToFalse() { + await setSecurityCertErrorsFeltPrivacyToFalse(); info( "Loading a bad sts cert error page and checking that the correct headline is shown" ); @@ -453,7 +496,8 @@ add_task(async function checkBadStsCertHeadline() { } }); -add_task(async function checkSandboxedIframe() { +add_task(async function checkSandboxedIframe_feltPrivacyToFalse() { + await setSecurityCertErrorsFeltPrivacyToFalse(); info( "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown" ); @@ -491,7 +535,8 @@ add_task(async function checkSandboxedIframe() { BrowserTestUtils.removeTab(gBrowser.selectedTab); }); -add_task(async function checkViewSource() { +add_task(async function checkViewSource_feltPrivacyToFalse() { + await setSecurityCertErrorsFeltPrivacyToFalse(); info( "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown" ); @@ -578,8 +623,21 @@ add_task(async function testCertificateTransparency() { }); for (let useFrame of [false, true]) { - let tab = await openErrorPage(GOOD_PAGE, useFrame); - let browser = tab.linkedBrowser; + let tab; + let browser; + if (useFrame) { + tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:blank" + ); + browser = tab.linkedBrowser; + + // injectErrorPageFrame is a helper from head.js for this purpose + await injectErrorPageFrame(tab, GOOD_PAGE, useFrame); + } else { + tab = await openErrorPage(GOOD_PAGE, useFrame); + browser = tab.linkedBrowser; + } let bc = browser.browsingContext; if (useFrame) { @@ -587,18 +645,17 @@ add_task(async function testCertificateTransparency() { } let message = await SpecialPowers.spawn(bc, [], async function () { - let doc = content.document; - + const doc = content.document; const shortDesc = doc.getElementById("errorShortDesc"); const sdArgs = JSON.parse(shortDesc.dataset.l10nArgs); - is( + Assert.equal( sdArgs.hostname, "example.com", "Should list hostname in error message." ); - - let advancedButton = doc.getElementById("advancedButton"); - advancedButton.click(); + const advancedButton = doc.getElementById("advancedButton"); + advancedButton.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content); // Wait until fluent sets the errorCode inner text. let errorCode; @@ -612,25 +669,28 @@ add_task(async function testCertificateTransparency() { tagName: errorCode.tagName.toLowerCase(), }; }); - is( + + Assert.equal( message.textContent, "MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY", "Correct error message found" ); - is(message.tagName, "a", "Error message is a link"); + Assert.equal(message.tagName, "a", "Error message is a link"); message = await SpecialPowers.spawn(bc, [], async function () { - let doc = content.document; - let errorCode = doc.getElementById("errorCode"); - errorCode.click(); - let text = doc.getElementById("certificateErrorText"); + const doc = content.document; + const errorCode = doc.getElementById("errorCode"); + errorCode.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter(errorCode, {}, content); + const text = doc.getElementById("certificateErrorText"); return { text: text.textContent, }; }); - ok(message.text.includes(GOOD_PAGE), "Correct URL found"); + Assert.ok(message.text.includes(GOOD_PAGE), "Correct URL found"); BrowserTestUtils.removeTab(gBrowser.selectedTab); + BrowserTestUtils.removeTab(tab); } await SpecialPowers.popPrefEnv(); @@ -638,3 +698,454 @@ add_task(async function testCertificateTransparency() { // Certificate transparency can only be enforced for our test certificates in // debug builds. }).skip(!AppConstants.DEBUG); + +/** + * A reusable helper that runs assertions on a network error page. + * It encapsulates the SpecialPowers.spawn call to be CSP-compliant. + * + * @param {object} params - Parameters for the assertion. + * @param {string} params.expectedUrl - The URL to load and check. + * @param {string} params.expectedHostname - The expected hostname to assert in the error page. + * @param {string} params.expectedErrorCode - The expected error code to assert. + * @param {string} params.expectedInfo - Info string for logging. + * @param {string} params.expectedErrorMessage - Error message to assert in the error text. + * @param {boolean} params.expectedHpkp - HPKP value to assert in the error text. + */ +async function assertNetErrorPage({ + expectedUrl, + expectedHostname, + expectedErrorCode, + expectedInfo, + expectedErrorMessage, + expectedHpkp, +}) { + await setSecurityCertErrorsFeltPrivacyToTrue(); + info(`${expectedInfo}`); + + for (let useFrame of [false, true]) { + let tab = await openErrorPage(expectedUrl, useFrame); + let browser = tab.linkedBrowser; + let bc = browser.browsingContext; + if (useFrame) { + bc = bc.children[0]; + } + + const newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true); + const contentData = await SpecialPowers.spawn( + bc, + [expectedHostname, expectedErrorCode], + async function (hostname, errorCode) { + const netErrorCard = + content.document.querySelector("net-error-card").wrappedJSObject; + await netErrorCard.getUpdateComplete(); + + // Assert Error Card Basics + Assert.ok( + netErrorCard.certErrorBodyTitle, + "The error page title should exist." + ); + + const shortDesc = netErrorCard.certErrorIntro; + const shortDescArgs = JSON.parse(shortDesc.dataset.l10nArgs); + Assert.equal( + shortDescArgs.hostname, + hostname, + "Should list hostname in error message." + ); + + // Assert Advanced button + Assert.ok( + !netErrorCard.advancedContainer, + "The Advanced container should NOT be found in shadow DOM before click." + ); + const advancedButton = netErrorCard.advancedButton; + Assert.ok(advancedButton, "The advanced button should exist."); + Assert.equal( + advancedButton.dataset.l10nId, + "fp-certerror-advanced-button", + "Button should have the 'advanced' l10n ID." + ); + + advancedButton.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content); + await ContentTaskUtils.waitForCondition( + () => + netErrorCard.exceptionButton && + !netErrorCard.exceptionButton.disabled, + "Wait for the exception button to be created." + ); + + Assert.ok( + !netErrorCard.exceptionButton.disabled, + "The exception button is now enabled." + ); + Assert.equal( + advancedButton.dataset.l10nId, + "fp-certerror-hide-advanced-button", + "Button should have the 'hide-advanced' l10n ID." + ); + Assert.ok( + netErrorCard.advancedShowing, + "Advanced showing attribute should be true" + ); + + // Assert Error Code + const certErrorCodeLink = netErrorCard.errorCode; + Assert.equal( + certErrorCodeLink.textContent, + `Error Code: ${errorCode}`, + "Error code text is as expected" + ); + Assert.equal( + certErrorCodeLink.tagName.toLowerCase(), + "a", + "Error code is a link" + ); + + certErrorCodeLink.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter(certErrorCodeLink, {}, content); + await ContentTaskUtils.waitForMutationCondition( + netErrorCard, + { attributeFilter: ["certErrorDebugInfoShowing"] }, + () => netErrorCard.certErrorDebugInfoShowing + ); + Assert.ok( + netErrorCard.certErrorDebugInfoShowing, + "The 'certErrorDebugInfoShowing' boolean should be toggled (to true) after Advance button click on assertAdvancedButton." + ); + Assert.ok(netErrorCard.certErrorText, "Error Code Detail should exist"); + + // Assert Site Certificate + info("Clicking the View Certificate button in advanced panel"); + netErrorCard.viewCertificate.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter( + netErrorCard.viewCertificate, + {}, + content + ); + + // Extract data needed by the parent process + const failedCertChain = + content.docShell.failedChannel.securityInfo.handshakeCertificates.map( + cert => cert.getBase64DERString() + ); + + return { + errorText: netErrorCard.certErrorText.textContent, + rawCertChain: failedCertChain, + }; + } + ); + + Assert.ok( + contentData.errorText.includes(expectedHostname), + "Correct URL found" + ); + Assert.ok( + contentData.errorText.includes(expectedErrorMessage), + "Correct error message exists" + ); + Assert.ok( + contentData.errorText.includes("HTTP Strict Transport Security: false"), + "Correct HSTS value exists" + ); + Assert.ok( + contentData.errorText.includes( + `HTTP Public Key Pinning: ${expectedHpkp}` + ), + "Correct HPKP value exists" + ); + + info("Loading the about:certificate page"); + let newTab = await newTabPromise; + + // Parent process checking if certificate viewer opened + const spec = gBrowser.currentURI.spec; + Assert.ok( + spec.startsWith("about:certificate"), + "about:certificate is the new opened tab" + ); + + await SpecialPowers.spawn( + gBrowser.selectedTab.linkedBrowser, + [expectedHostname], + async function (hostname) { + const doc = content.document; + const certificateSection = await ContentTaskUtils.waitForCondition( + () => { + // Content process checking if we're no longer on the error page + Assert.ok( + !doc.documentURI.startsWith("about:certerror"), + "We are now in a new tab with content including: about:certificate" + ); + return doc.querySelector("certificate-section"); + }, + "Certificate section found" + ); + + const infoGroup = + certificateSection.shadowRoot.querySelector("info-group"); + Assert.ok(infoGroup, "infoGroup found"); + + const items = infoGroup.shadowRoot.querySelectorAll("info-item"); + const commonNameID = items[items.length - 1].shadowRoot + .querySelector("label") + .getAttribute("data-l10n-id"); + Assert.equal( + commonNameID, + "certificate-viewer-common-name", + "The correct item was selected" + ); + + const commonNameValue = + items[items.length - 1].shadowRoot.querySelector(".info").textContent; + + // Bug 1992278 + // Structuring the logic this way avoids issue to be addressed + // in most cases the values match, "self-signed.example.com", "self-signed.example.com" + // in the case of pinning, the commonNameValue is shorter, "pinning-test.example.com", + // than the hostname "badchain.include-subdomains.pinning.example.com" + // as a temporary measure I have reversed the logic such that hostname includes commonNameValue + Assert.ok( + hostname.includes(commonNameValue), + "Error text should include the expected hostname" + ); + } + ); + + BrowserTestUtils.removeTab(newTab); + BrowserTestUtils.removeTab(tab); + } +} + +/** + * A reusable helper that runs assertions on a view-source network error page. + * It encapsulates the SpecialPowers.spawn call to be CSP-compliant. + * + * @param {object} params - Parameters for the assertion. + * @param {string} params.expectedHostname - The expected hostname to assert in the error page. + * @param {string} params.expectedUrl - The URL to load and check. + */ +async function assertViewSourceNetErrorPage({ + expectedHostname, + expectedUrl, + expectedInfo, +}) { + await setSecurityCertErrorsFeltPrivacyToTrue(); + info(`${expectedInfo}`); + + let tab = await openErrorPage(expectedUrl); + const browser = tab.linkedBrowser; + const loaded = BrowserTestUtils.browserLoaded(browser, false, expectedUrl); + + await SpecialPowers.spawn(browser, [], async function () { + const netErrorCard = + content.document.querySelector("net-error-card").wrappedJSObject; + await netErrorCard.getUpdateComplete(); + // Advanced button + const advancedButton = netErrorCard.advancedButton; + advancedButton.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content); + await ContentTaskUtils.waitForCondition( + () => + netErrorCard.exceptionButton && !netErrorCard.exceptionButton.disabled, + "Wait for the exception button to be created." + ); + + info("Clicking the Proceed Risky button in advanced panel"); + netErrorCard.exceptionButton.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter( + netErrorCard.exceptionButton, + {}, + content + ); + }); + + info("Loading the url after proceeding with risk"); + await loaded; + + // Clean up the cert override + const certOverrideService = Cc[ + "@mozilla.org/security/certoverride;1" + ].getService(Ci.nsICertOverrideService); + certOverrideService.clearValidityOverride(expectedHostname, -1, {}); + + // To ensure the state is reset, reload and wait for the error page to return. + info("Reloading to ensure the certificate override was cleared."); + const errorPageLoaded = BrowserTestUtils.waitForErrorPage(browser); + + BrowserCommands.reloadSkipCache(); + await errorPageLoaded; + + info("Override cleared and error page is shown again."); + BrowserTestUtils.removeTab(tab); +} + +// Security CertError Felt Privacy set to true only +add_task(async function checkReturnToPreviousPage_feltPrivacyToTrue() { + await setSecurityCertErrorsFeltPrivacyToTrue(); + info( + "Loading a bad cert page and making sure 'return to previous page' goes back" + ); + for (let useFrame of [false, true]) { + let tab; + let browser; + if (useFrame) { + tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE); + browser = tab.linkedBrowser; + await SpecialPowers.spawn(browser, [], () => { + content.document.notifyUserGestureActivation(); + }); + + BrowserTestUtils.startLoadingURIString(browser, GOOD_PAGE_2); + await BrowserTestUtils.browserLoaded(browser, false, GOOD_PAGE_2); + await injectErrorPageFrame(tab, BAD_CERT); + } else { + tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE); + browser = gBrowser.selectedBrowser; + await SpecialPowers.spawn(browser, [], () => { + content.document.notifyUserGestureActivation(); + }); + + info("Loading and waiting for the cert error"); + const certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser); + BrowserTestUtils.startLoadingURIString(browser, BAD_CERT); + await certErrorLoaded; + } + + Assert.ok(browser.webNavigation.canGoBack, "webNavigation.canGoBack"); + Assert.ok( + !browser.webNavigation.canGoForward, + "!webNavigation.canGoForward" + ); + + // Populate the shistory entries manually, since it happens asynchronously + // and the following tests will be too soon otherwise. + await TabStateFlusher.flush(browser); + const { entries } = JSON.parse(SessionStore.getTabState(tab)); + Assert.equal(entries.length, 2, "there are two history entries"); + + info("Clicking the go back button on about:certerror"); + let bc = browser.browsingContext; + if (useFrame) { + bc = bc.children[0]; + } + + const pageShownPromise = BrowserTestUtils.waitForContentEvent( + browser, + "pageshow", + true + ); + await SpecialPowers.spawn(bc, [useFrame], async function () { + const netErrorCard = + content.document.querySelector("net-error-card").wrappedJSObject; + await netErrorCard.getUpdateComplete(); + const returnButton = netErrorCard.returnButton; + returnButton.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter(returnButton, {}, content); + }); + await pageShownPromise; + + Assert.ok(!browser.webNavigation.canGoBack, "!webNavigation.canGoBack"); + Assert.ok(browser.webNavigation.canGoForward, "webNavigation.canGoForward"); + Assert.equal(gBrowser.currentURI.spec, GOOD_PAGE, "Went back"); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + } +}); + +add_task(async function checkAdvancedDetails_feltPrivacyToTrue() { + // Helper function does all the content-side checks. + await assertNetErrorPage({ + expectedUrl: BAD_CERT, + expectedHostname: new URL(BAD_CERT).hostname, + expectedErrorCode: "SEC_ERROR_EXPIRED_CERTIFICATE", + expectedInfo: + "Loading a bad cert page and verifying the main error and advanced details section", + expectedErrorMessage: "Certificate has expired", + expectedHpkp: false, + }); +}); + +add_task(async function checkAdvancedDetailsForHSTS_feltPrivacyToTrue() { + // Helper function does all the content-side checks. + await assertNetErrorPage({ + expectedUrl: BAD_STS_CERT, + expectedHostname: new URL(BAD_STS_CERT).hostname, + expectedErrorCode: "SSL_ERROR_BAD_CERT_DOMAIN", + expectedInfo: + "Loading a bad STS cert page and verifying the advanced details section", + expectedErrorMessage: + "requested domain name does not match the server\u2019s certificate", // Expected error message + expectedHpkp: true, + }); +}); + +add_task(async function checkUnknownIssuerDetails_feltPrivacyToTrue() { + // Helper function does all the content-side checks. + await assertNetErrorPage({ + expectedUrl: UNKNOWN_ISSUER, + expectedHostname: new URL(UNKNOWN_ISSUER).hostname, + expectedErrorCode: "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT", + expectedInfo: + "Loading a cert error for self-signed pages and checking the correct link is shown", + expectedErrorMessage: + "The certificate is not trusted because it is self-signed.", + expectedHpkp: false, + }); +}); + +add_task(async function checkViewSource_feltPrivacyToTrue() { + await assertViewSourceNetErrorPage({ + expectedHostname: new URL(BAD_CERT).hostname, + expectedUrl: "view-source:" + BAD_CERT, + expectedInfo: + "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown", + }); +}); + +add_task(async function checkSandboxedIframe_feltPrivacyToTrue() { + await setSecurityCertErrorsFeltPrivacyToTrue(); + info( + "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown" + ); + let useFrame = true; + let sandboxed = true; + let tab = await openErrorPage(BAD_CERT, useFrame, sandboxed); + let browser = tab.linkedBrowser; + + let bc = browser.browsingContext.children[0]; + await SpecialPowers.spawn(bc, [], async function () { + const netErrorCard = + content.document.querySelector("net-error-card").wrappedJSObject; + await netErrorCard.getUpdateComplete(); + + // Assert Error Card Basics + Assert.ok( + netErrorCard.certErrorBodyTitle, + "The error page title should exist." + ); + const advancedButton = netErrorCard.advancedButton; + advancedButton.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content); + await ContentTaskUtils.waitForCondition( + () => + netErrorCard.exceptionButton && !netErrorCard.exceptionButton.disabled, + "Wait for the exception button to be created." + ); + + // Assert Error Code + const certErrorCodeLink = netErrorCard.errorCode; + Assert.equal( + certErrorCodeLink.textContent, + `Error Code: SEC_ERROR_EXPIRED_CERTIFICATE`, + "Error Code is as expected" + ); + Assert.equal( + certErrorCodeLink.tagName.toLowerCase(), + "a", + "Error Code is a link" + ); + }); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/base/content/test/about/head.js b/browser/base/content/test/about/head.js @@ -169,3 +169,12 @@ function isBookmarksToolbarVisible(win = window) { let toolbar = win.document.getElementById("PersonalToolbar"); return !toolbar.collapsed; } + +const setSecurityCertErrorsFeltPrivacyToTrue = async () => + await SpecialPowers.pushPrefEnv({ + set: [["security.certerrors.felt-privacy-v1", true]], + }); +const setSecurityCertErrorsFeltPrivacyToFalse = async () => + await SpecialPowers.pushPrefEnv({ + set: [["security.certerrors.felt-privacy-v1", false]], + }); diff --git a/toolkit/content/net-error-card.mjs b/toolkit/content/net-error-card.mjs @@ -36,6 +36,12 @@ export class NetErrorCard extends MozLitElement { errorCode: "#errorCode", advancedContainer: ".advanced-container", advancedButton: "#advanced-button", + certErrorIntro: "#certErrorIntro", + certErrorDebugInfo: "#certificateErrorDebugInformation", + certErrorText: "#certificateErrorText", + viewCertificate: "#viewCertificate", + certErrorBodyTitle: "#certErrorBodyTitle", + returnButton: "#returnButton", }; static ERROR_CODES = new Set([ @@ -133,6 +139,7 @@ export class NetErrorCard extends MozLitElement { case "SEC_ERROR_EXPIRED_CERTIFICATE": case "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT": return html`<p + id="certErrorIntro" data-l10n-id="fp-certerror-intro" data-l10n-args='{"hostname": "${this.hostname}"}' ></p>`; @@ -546,13 +553,17 @@ export class NetErrorCard extends MozLitElement { <img src="chrome://global/skin/illustrations/security-error.svg" /> </div> <div class="container"> - <h1 data-l10n-id="fp-certerror-body-title"></h1> + <h1 + id="certErrorBodyTitle" + data-l10n-id="fp-certerror-body-title" + ></h1> ${this.introContentTemplate()} <moz-button-group ><moz-button type="primary" data-l10n-id="fp-certerror-return-to-previous-page-recommended-button" data-telemetry-id="return_button_adv" + id="returnButton" @click=${this.handleGoBackClick} ></moz-button ><moz-button