From 52ca4d0dbf04f32e92987ab6efcb1ed6657d2105 Mon Sep 17 00:00:00 2001 From: Sean Date: Tue, 14 Jan 2025 22:04:20 +0000 Subject: [PATCH] Bug 1927594 - Part 1: Introduce better CORP headers aboutCertError messages r=necko-reviewers,fluent-reviewers,kershaw,valentin,bolsson Differential Revision: https://phabricator.services.mozilla.com/D228072 --- .../chrome/overrides/appstrings.properties | 1 + docshell/base/nsDocShell.cpp | 10 +- toolkit/content/aboutNetError.mjs | 206 +++++++++++------- .../en-US/toolkit/neterror/certError.ftl | 2 + .../en-US/toolkit/neterror/netError.ftl | 4 + 5 files changed, 142 insertions(+), 81 deletions(-) diff --git a/browser/locales/en-US/chrome/overrides/appstrings.properties b/browser/locales/en-US/chrome/overrides/appstrings.properties index d572ac39c657..528a50606a03 100644 --- a/browser/locales/en-US/chrome/overrides/appstrings.properties +++ b/browser/locales/en-US/chrome/overrides/appstrings.properties @@ -43,4 +43,5 @@ corruptedContentErrorv2=The site at %S has experienced a network protocol violat sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol. inadequateSecurityError=The website tried to negotiate an inadequate level of security. blockedByPolicy=Your organization has blocked access to this page or website. +blockedByCORP=Firefox didn’t load this page because it looks like the security configuration doesn’t match the previous page. networkProtocolError=Firefox has experienced a network protocol violation that cannot be repaired. diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index f3069febc1bd..be90d72699a3 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -3613,11 +3613,17 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, addHostPort = true; break; case NS_ERROR_BLOCKED_BY_POLICY: - case NS_ERROR_DOM_COOP_FAILED: - case NS_ERROR_DOM_COEP_FAILED: // Page blocked by policy error = "blockedByPolicy"; break; + case NS_ERROR_DOM_COOP_FAILED: + error = "blockedByCOOP"; + errorDescriptionID = "blockedByCORP"; + break; + case NS_ERROR_DOM_COEP_FAILED: + error = "blockedByCOEP"; + errorDescriptionID = "blockedByCORP"; + break; case NS_ERROR_NET_HTTP2_SENT_GOAWAY: case NS_ERROR_NET_HTTP3_PROTOCOL_ERROR: // HTTP/2 or HTTP/3 stack detected a protocol error diff --git a/toolkit/content/aboutNetError.mjs b/toolkit/content/aboutNetError.mjs index 1fc190b51add..fdbd63c0bb9a 100644 --- a/toolkit/content/aboutNetError.mjs +++ b/toolkit/content/aboutNetError.mjs @@ -58,6 +58,7 @@ const KNOWN_ERROR_TITLE_IDS = new Set([ "sslv3Used-title", "inadequateSecurityError-title", "blockedByPolicy-title", + "blocked-by-corp-headers-title", "clockSkewError-title", "networkProtocolError-title", "nssBadCert-title", @@ -70,6 +71,10 @@ const KNOWN_ERROR_TITLE_IDS = new Set([ /* global KNOWN_ERROR_MESSAGE_IDS */ const ERROR_MESSAGES_FTL = "toolkit/neterror/nsserrors.ftl"; +const MDN_DOCS_HEADERS = "https://developer.mozilla.org/docs/Web/HTTP/Headers/"; +const COOP_MDN_DOCS = MDN_DOCS_HEADERS + "Cross-Origin-Opener-Policy"; +const COEP_MDN_DOCS = MDN_DOCS_HEADERS + "Cross-Origin-Embedder-Policy"; + // If the location of the favicon changes, FAVICON_CERTERRORPAGE_URL and/or // FAVICON_ERRORPAGE_URL in toolkit/components/places/nsFaviconService.idl // should also be updated. @@ -291,75 +296,9 @@ function setResponseStatus(shortDesc) { } } -function initPage() { - // We show an offline support page in case of a system-wide error, - // when a user cannot connect to the internet and access the SUMO website. - // For example, clock error, which causes certerrors across the web or - // a security software conflict where the user is unable to connect - // to the internet. - // The URL that prompts us to show an offline support page should have the following - // format: "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/supportPageSlug", - // so we can extract the support page slug. - let baseURL = RPMGetFormatURLPref("app.support.baseURL"); - if (document.location.href.startsWith(baseURL)) { - let supportPageSlug = document.location.pathname.split("/").pop(); - RPMSendAsyncMessage("DisplayOfflineSupportPage", { - supportPageSlug, - }); - } - - const className = getCSSClass(); - if (className) { - document.body.classList.add(className); - } - - const isTRROnlyFailure = gErrorCode == "dnsNotFound" && RPMIsTRROnlyFailure(); - - let isNativeFallbackWarning = false; - if (RPMGetBoolPref("network.trr.display_fallback_warning")) { - isNativeFallbackWarning = - gErrorCode == "dnsNotFound" && RPMIsNativeFallbackFailure(); - } - - const docTitle = document.querySelector("title"); - const bodyTitle = document.querySelector(".title-text"); - const shortDesc = document.getElementById("errorShortDesc"); - - if (gIsCertError) { - const isStsError = window !== window.top || gHasSts; - const errArgs = { hostname: HOST_NAME }; - if (isCaptive()) { - document.l10n.setAttributes( - docTitle, - "neterror-captive-portal-page-title" - ); - document.l10n.setAttributes(bodyTitle, "captivePortal-title"); - document.l10n.setAttributes( - shortDesc, - "neterror-captive-portal", - errArgs - ); - initPageCaptivePortal(); - } else { - if (isStsError) { - document.l10n.setAttributes(docTitle, "certerror-sts-page-title"); - document.l10n.setAttributes(bodyTitle, "nssBadCert-sts-title"); - document.l10n.setAttributes(shortDesc, "certerror-sts-intro", errArgs); - } else { - document.l10n.setAttributes(docTitle, "certerror-page-title"); - document.l10n.setAttributes(bodyTitle, "nssBadCert-title"); - document.l10n.setAttributes(shortDesc, "certerror-intro", errArgs); - } - initPageCertError(); - } - - initCertErrorPageActions(); - setTechnicalDetailsOnCertError(); - return; - } - - document.body.classList.add("neterror"); - +// Returns pageTitleId, bodyTitle, bodyTitleId, and longDesc as an object +function initTitleAndBodyIds(baseURL, isTRROnlyFailure) { + let bodyTitle = document.querySelector(".title-text"); let longDesc = document.getElementById("errorLongDesc"); const tryAgain = document.getElementById("netErrorButtonContainer"); tryAgain.hidden = false; @@ -379,7 +318,13 @@ function initPage() { // For pages blocked by policy, trying again won't help. tryAgain.hidden = true; break; - + case "blockedByCOOP": + case "blockedByCOEP": { + bodyTitleId = "blocked-by-corp-headers-title"; + document.body.classList.add("blocked"); + tryAgain.hidden = true; + break; + } case "cspBlocked": case "xfoBlocked": { bodyTitleId = "csp-xfo-error-title"; @@ -477,6 +422,88 @@ function initPage() { break; } + return { pageTitleId, bodyTitle, bodyTitleId, longDesc }; +} + +function initPage() { + // We show an offline support page in case of a system-wide error, + // when a user cannot connect to the internet and access the SUMO website. + // For example, clock error, which causes certerrors across the web or + // a security software conflict where the user is unable to connect + // to the internet. + // The URL that prompts us to show an offline support page should have the following + // format: "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/supportPageSlug", + // so we can extract the support page slug. + let baseURL = RPMGetFormatURLPref("app.support.baseURL"); + if (document.location.href.startsWith(baseURL)) { + let supportPageSlug = document.location.pathname.split("/").pop(); + RPMSendAsyncMessage("DisplayOfflineSupportPage", { + supportPageSlug, + }); + } + + const className = getCSSClass(); + if (className) { + document.body.classList.add(className); + } + + const isTRROnlyFailure = gErrorCode == "dnsNotFound" && RPMIsTRROnlyFailure(); + + let isNativeFallbackWarning = false; + if (RPMGetBoolPref("network.trr.display_fallback_warning")) { + isNativeFallbackWarning = + gErrorCode == "dnsNotFound" && RPMIsNativeFallbackFailure(); + } + + const docTitle = document.querySelector("title"); + const shortDesc = document.getElementById("errorShortDesc"); + + if (gIsCertError) { + const bodyTitle = document.querySelector(".title-text"); + const isStsError = window !== window.top || gHasSts; + const errArgs = { hostname: HOST_NAME }; + if (isCaptive()) { + document.l10n.setAttributes( + docTitle, + "neterror-captive-portal-page-title" + ); + document.l10n.setAttributes(bodyTitle, "captivePortal-title"); + document.l10n.setAttributes( + shortDesc, + "neterror-captive-portal", + errArgs + ); + initPageCaptivePortal(); + } else { + if (isStsError) { + document.l10n.setAttributes(docTitle, "certerror-sts-page-title"); + document.l10n.setAttributes(bodyTitle, "nssBadCert-sts-title"); + document.l10n.setAttributes(shortDesc, "certerror-sts-intro", errArgs); + } else { + document.l10n.setAttributes(docTitle, "certerror-page-title"); + document.l10n.setAttributes(bodyTitle, "nssBadCert-title"); + document.l10n.setAttributes(shortDesc, "certerror-intro", errArgs); + } + initPageCertError(); + } + + initCertErrorPageActions(); + setTechnicalDetailsOnCertError(); + return; + } + + document.body.classList.add("neterror"); + + const tryAgain = document.getElementById("netErrorButtonContainer"); + tryAgain.hidden = false; + const learnMoreLink = document.getElementById("learnMoreLink"); + learnMoreLink.setAttribute("href", baseURL + "connection-not-secure"); + let { pageTitleId, bodyTitle, bodyTitleId, longDesc } = initTitleAndBodyIds( + baseURL, + isTRROnlyFailure + ); + + // bodyTitle is set to null if it has already been set in initTitleAndBodyIds if (!KNOWN_ERROR_TITLE_IDS.has(bodyTitleId)) { console.error("No strings exist for error:", gErrorCode); bodyTitleId = "generic-title"; @@ -488,8 +515,10 @@ function initPage() { document.body.className = "certerror"; // Shows warning icon pageTitleId = "dns-not-found-trr-only-title2"; document.l10n.setAttributes(docTitle, pageTitleId); - bodyTitleId = "dns-not-found-trr-only-title2"; - document.l10n.setAttributes(bodyTitle, bodyTitleId); + if (bodyTitle) { + bodyTitleId = "dns-not-found-trr-only-title2"; + document.l10n.setAttributes(bodyTitle, bodyTitleId); + } shortDesc.textContent = ""; let skipReason = RPMGetTRRSkipReason(); @@ -611,7 +640,9 @@ function initPage() { } document.l10n.setAttributes(docTitle, pageTitleId); - document.l10n.setAttributes(bodyTitle, bodyTitleId); + if (bodyTitle) { + document.l10n.setAttributes(bodyTitle, bodyTitleId); + } shortDesc.textContent = getDescription(); setFocus("#netErrorButtonContainer > .try-again"); @@ -704,16 +735,20 @@ function showNativeFallbackWarning() { * Builds HTML elements from `parts` and appends them to `parentElement`. * * @param {HTMLElement} parentElement - * @param {Array<["li" | "p" | "span", string, Record | undefined]>} parts + * @param {Array<["li" | "p" | "span" | "a", string, Record | undefined]>} parts */ function setNetErrorMessageFromParts(parentElement, parts) { let list = null; - for (let [tag, l10nId, l10nArgs] of parts) { + for (let [tag, l10nId, l10nArgsOrHref] of parts) { const elem = document.createElement(tag); elem.dataset.l10nId = l10nId; - if (l10nArgs) { - elem.dataset.l10nArgs = JSON.stringify(l10nArgs); + if (l10nArgsOrHref) { + if (tag === "a") { + elem.href = l10nArgsOrHref; + } else { + elem.dataset.l10nArgs = JSON.stringify(l10nArgsOrHref); + } } if (tag === "li") { @@ -735,9 +770,10 @@ function setNetErrorMessageFromParts(parentElement, parts) { * Returns an array of tuples determining the parts of an error message: * - HTML tag name * - l10n id - * - l10n args (optional) + * - l10n args (if the tag is not "a", optional) + * - href (if the tag is "a", optional) * - * @returns { Array<["li" | "p" | "span", string, Record | undefined]> } + * @returns { Array<["li" | "p" | "span" | "a", string, Record | undefined]> } */ function getNetErrorDescParts() { switch (gErrorCode) { @@ -757,6 +793,18 @@ function getNetErrorDescParts() { return errorTags; } + case "blockedByCOOP": { + return [ + ["p", "certerror-blocked-by-corp-headers-description"], + ["a", "certerror-coop-learn-more", COOP_MDN_DOCS], + ]; + } + case "blockedByCOEP": { + return [ + ["p", "certerror-blocked-by-corp-headers-description"], + ["a", "certerror-coep-learn-more", COEP_MDN_DOCS], + ]; + } case "blockedByPolicy": case "deniedPortAccess": case "malformedURI": diff --git a/toolkit/locales/en-US/toolkit/neterror/certError.ftl b/toolkit/locales/en-US/toolkit/neterror/certError.ftl index 612d18ac3f79..8b0c0af52ca4 100644 --- a/toolkit/locales/en-US/toolkit/neterror/certError.ftl +++ b/toolkit/locales/en-US/toolkit/neterror/certError.ftl @@ -141,6 +141,8 @@ corruptedContentError-title = Corrupted Content Error sslv3Used-title = Unable to Connect Securely inadequateSecurityError-title = Your connection is not secure blockedByPolicy-title = Blocked Page + +blocked-by-corp-headers-title = Be careful. Something doesn’t look right. clockSkewError-title = Your Computer Clock is Wrong networkProtocolError-title = Network Protocol Error nssBadCert-title = Warning: Potential Security Risk Ahead diff --git a/toolkit/locales/en-US/toolkit/neterror/netError.ftl b/toolkit/locales/en-US/toolkit/neterror/netError.ftl index 416e91c63994..9027893f155a 100644 --- a/toolkit/locales/en-US/toolkit/neterror/netError.ftl +++ b/toolkit/locales/en-US/toolkit/neterror/netError.ftl @@ -179,6 +179,10 @@ certerror-what-should-i-do-bad-sts-cert-explanation = { $hostname } has a cert-error-trust-certificate-transparency-what-can-you-do-about-it = Probably nothing, since it’s likely there’s a problem with the site itself. +certerror-blocked-by-corp-headers-description = Sometimes websites set up protections for themselves and people like you from unwanted interactions with other sites. +certerror-coop-learn-more = Learn more about Cross Origin Opener Policies (COOP) +certerror-coep-learn-more = Learn more about Cross Origin Embedder Policies (COEP) + # Variables: # $responsestatus (string) - HTTP response status code (e.g., 500). # $responsestatustext (string) - HTTP response status text (e.g., "Internal Server Error").