tor-browser

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

commit a495db9a785c9fac27ee96a4ae9641fdc0f1faa0
parent e0aa985553334691cefff090c7024074164c03eb
Author: mailelucks <maile.lucks@gmail.com>
Date:   Thu,  4 Dec 2025 21:39:01 +0000

Bug 1992369 - NS_ERROR_DOM_COOP_FAILED, NS_ERROR_DOM_COEP_FAILED support for net-error-card - r=niklas,jaws,fluent-reviewers,bolsson

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

Diffstat:
Mbrowser/base/content/test/about/browser_aboutCertError_coep.js | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/base/content/test/about/browser_aboutCertError_coop.js | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/content/aboutNetError.mjs | 34+++-------------------------------
Mtoolkit/content/aboutNetErrorHelpers.mjs | 5++++-
Mtoolkit/content/net-error-card.mjs | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mtoolkit/locales/en-US/toolkit/neterror/netError.ftl | 6++++++
6 files changed, 287 insertions(+), 60 deletions(-)

diff --git a/browser/base/content/test/about/browser_aboutCertError_coep.js b/browser/base/content/test/about/browser_aboutCertError_coep.js @@ -52,3 +52,58 @@ add_task(async function test_coepError() { BrowserTestUtils.removeTab(gBrowser.selectedTab); }); + +add_task(async function test_coepError_feltPrivacyToTrue() { + let browser; + let pageLoaded; + + const uri = `${AUTH_ROUTE}?error=coep`; + await setSecurityCertErrorsFeltPrivacyToTrue(); + await BrowserTestUtils.openNewForegroundTab( + gBrowser, + () => { + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, uri); + browser = gBrowser.selectedBrowser; + pageLoaded = BrowserTestUtils.waitForErrorPage(browser); + }, + false + ); + + await pageLoaded; + + await SpecialPowers.spawn(browser, [], async function () { + // The error is displayed in the iframe for COEP + const doc = content.document.querySelector("iframe").contentDocument; + + Assert.ok( + doc.documentURI.startsWith("about:neterror"), + "Should be showing error page" + ); + + const netErrorCard = doc.querySelector("net-error-card").wrappedJSObject; + await netErrorCard.getUpdateComplete(); + + Assert.strictEqual( + netErrorCard.netErrorTitleText.dataset.l10nId, + "fp-certerror-body-title", + "Correct error link title (CORP) is set" + ); + + await ContentTaskUtils.waitForCondition(() => { + return ( + netErrorCard.netErrorLearnMoreLink && + netErrorCard.netErrorLearnMoreLink.textContent != "" && + netErrorCard.netErrorLearnMoreLink.tagName.toLowerCase() === "a" + ); + }, "learn more link is visible and is a link"); + + Assert.strictEqual( + netErrorCard.netErrorLearnMoreLink.dataset.l10nId, + "certerror-coep-learn-more", + "Learn more element is a link and has COEP text" + ); + }); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + await SpecialPowers.popPrefEnv(); +}); diff --git a/browser/base/content/test/about/browser_aboutCertError_coop.js b/browser/base/content/test/about/browser_aboutCertError_coop.js @@ -70,3 +70,55 @@ add_task(async function test_coopError() { BrowserTestUtils.removeTab(iframeTab); BrowserTestUtils.removeTab(popUpTab); }); + +add_task(async function test_coopError_feltPrivacyToTrue() { + await setSecurityCertErrorsFeltPrivacyToTrue(); + let iframeTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + `${AUTH_ROUTE}?error=coop` + ); + let popupTabLoaded = waitForNewTabAndErrorPage(); + + await SpecialPowers.spawn(iframeTab.linkedBrowser, [], async () => { + let button = content.document + .querySelector("iframe") + .contentDocument.querySelector("#openPopupButton"); + if (!button) { + Assert.ok(false, "Popup button not found!"); + } + EventUtils.synthesizeMouseAtCenter(button, {}, content); + }); + + let popUpTab = await popupTabLoaded; + let popUpBrowser = popUpTab.linkedBrowser; + + await SpecialPowers.spawn(popUpBrowser, [], async function () { + const doc = content.document; + + const netErrorCard = doc.querySelector("net-error-card").wrappedJSObject; + await netErrorCard.getUpdateComplete(); + + Assert.strictEqual( + netErrorCard.netErrorTitleText.dataset.l10nId, + "fp-certerror-body-title", + "Correct error link title (CORP) is set" + ); + + await ContentTaskUtils.waitForCondition(() => { + return ( + netErrorCard.netErrorLearnMoreLink && + netErrorCard.netErrorLearnMoreLink.textContent != "" && + netErrorCard.netErrorLearnMoreLink.tagName.toLowerCase() === "a" + ); + }, "learn more link is visible and is a link"); + + Assert.strictEqual( + netErrorCard.netErrorLearnMoreLink.dataset.l10nId, + "certerror-coop-learn-more", + "Learn more element is a link and has COOP text" + ); + }); + BrowserTestUtils.removeTab(iframeTab); + BrowserTestUtils.removeTab(popUpTab); + await SpecialPowers.popPrefEnv(); +}); diff --git a/toolkit/content/aboutNetError.mjs b/toolkit/content/aboutNetError.mjs @@ -17,20 +17,16 @@ import { recordSecurityUITelemetry, getCSSClass, gNoConnectivity, - gOffline, retryThis, errorHasNoUserFix, + COOP_MDN_DOCS, + COEP_MDN_DOCS, } from "chrome://global/content/aboutNetErrorHelpers.mjs"; const formatter = new Intl.DateTimeFormat(); const HOST_NAME = getHostName(); -const FELT_PRIVACY_REFRESH = RPMGetBoolPref( - "security.certerrors.felt-privacy-v1", - false -); - // Used to check if we have a specific localized message for an error. const KNOWN_ERROR_TITLE_IDS = new Set([ // Error titles: @@ -78,11 +74,6 @@ const KNOWN_ERROR_TITLE_IDS = new Set([ * aboutNetErrorCodes.js which is loaded before we are: */ /* global KNOWN_ERROR_MESSAGE_IDS */ const ERROR_MESSAGES_FTL = "toolkit/neterror/nsserrors.ftl"; - -const MDN_DOCS_HEADERS = - "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/"; -const COOP_MDN_DOCS = MDN_DOCS_HEADERS + "Cross-Origin-Opener-Policy"; -const COEP_MDN_DOCS = MDN_DOCS_HEADERS + "Cross-Origin-Embedder-Policy"; const HTTPS_UPGRADES_MDN_DOCS = "https://support.mozilla.org/kb/https-upgrades"; // If the location of the favicon changes, FAVICON_CERTERRORPAGE_URL and/or @@ -1429,26 +1420,7 @@ function setFocus(selector, position = "afterbegin") { } } -function shouldUseFeltPrivacyRefresh() { - if (!FELT_PRIVACY_REFRESH) { - return false; - } - - const errorInfo = gIsCertError - ? document.getFailedCertSecurityInfo() - : document.getNetErrorInfo(); - let errorCode = errorInfo.errorCodeString - ? errorInfo.errorCodeString - : gErrorCode; - - if (gOffline) { - errorCode = "NS_ERROR_OFFLINE"; - } - - return NetErrorCard.ERROR_CODES.has(errorCode); -} - -if (!shouldUseFeltPrivacyRefresh()) { +if (!NetErrorCard.isSupported()) { for (let button of document.querySelectorAll(".try-again")) { button.addEventListener("click", function () { retryThis(this); diff --git a/toolkit/content/aboutNetErrorHelpers.mjs b/toolkit/content/aboutNetErrorHelpers.mjs @@ -23,7 +23,10 @@ import { export let searchParams = new URLSearchParams( document.documentURI.split("?")[1] ); - +export const MDN_DOCS_HEADERS = + "https://developer.mozilla.org/docs/Web/HTTP/Reference/Headers/"; +export const COOP_MDN_DOCS = MDN_DOCS_HEADERS + "Cross-Origin-Opener-Policy"; +export const COEP_MDN_DOCS = MDN_DOCS_HEADERS + "Cross-Origin-Embedder-Policy"; export let gErrorCode = searchParams.get("e"); export let gIsCertError = gErrorCode == "nssBadCert"; export let gHasSts = gIsCertError && getCSSClass() === "badStsCert"; diff --git a/toolkit/content/net-error-card.mjs b/toolkit/content/net-error-card.mjs @@ -17,6 +17,8 @@ import { gOffline, retryThis, errorHasNoUserFix, + COOP_MDN_DOCS, + COEP_MDN_DOCS, } from "chrome://global/content/aboutNetErrorHelpers.mjs"; import { html } from "chrome://global/content/vendor/lit.all.mjs"; import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; @@ -25,6 +27,10 @@ import "chrome://global/content/elements/moz-button.mjs"; import "chrome://global/content/elements/moz-support-link.mjs"; const HOST_NAME = getHostName(); +const FELT_PRIVACY_REFRESH = RPMGetBoolPref( + "security.certerrors.felt-privacy-v1", + false +); export class NetErrorCard extends MozLitElement { static properties = { @@ -51,6 +57,7 @@ export class NetErrorCard extends MozLitElement { whatCanYouDo: "#whatCanYouDo", whyDangerous: "#fp-why-site-dangerous", netErrorTitleText: "#neterror-title-text", + netErrorLearnMoreLink: "#neterror-learn-more-link", }; static ERROR_CODES = new Set([ @@ -63,8 +70,33 @@ export class NetErrorCard extends MozLitElement { "SSL_ERROR_NO_CYPHER_OVERLAP", "MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY", "NS_ERROR_OFFLINE", + "NS_ERROR_DOM_COOP_FAILED", + "NS_ERROR_DOM_COEP_FAILED", ]); + static isSupported() { + if (!FELT_PRIVACY_REFRESH) { + return false; + } + + const errorInfo = gIsCertError + ? document.getFailedCertSecurityInfo() + : document.getNetErrorInfo(); + let errorCode = errorInfo.errorCodeString + ? errorInfo.errorCodeString + : gErrorCode; + + if (gOffline) { + errorCode = "NS_ERROR_OFFLINE"; + } else if (gErrorCode === "blockedByCOOP") { + errorCode = "NS_ERROR_DOM_COOP_FAILED"; + } else if (gErrorCode === "blockedByCOEP") { + errorCode = "NS_ERROR_DOM_COEP_FAILED"; + } + + return NetErrorCard.ERROR_CODES.has(errorCode); + } + constructor() { super(); @@ -74,6 +106,7 @@ export class NetErrorCard extends MozLitElement { this.certificateErrorText = null; this.domainMismatchNamesPromise = null; this.certificateErrorTextPromise = null; + this.showCustomNetErrorCard = false; } async getUpdateComplete() { @@ -169,10 +202,18 @@ export class NetErrorCard extends MozLitElement { ? document.getFailedCertSecurityInfo() : document.getNetErrorInfo(); - if (gOffline) { - errorInfo.errorCodeString = "NS_ERROR_OFFLINE"; - } + if (!errorInfo.errorCodeString) { + this.showCustomNetErrorCard = true; + if (gOffline) { + errorInfo.errorCodeString = "NS_ERROR_OFFLINE"; + } else if (gErrorCode === "blockedByCOOP") { + errorInfo.errorCodeString = "NS_ERROR_DOM_COOP_FAILED"; + } else if (gErrorCode === "blockedByCOEP") { + errorInfo.errorCodeString = "NS_ERROR_DOM_COEP_FAILED"; + } + errorInfo.errorCodeString = errorInfo.errorCodeString ?? gErrorCode; + } return errorInfo; } @@ -208,6 +249,9 @@ export class NetErrorCard extends MozLitElement { data-l10n-id="fp-neterror-offline-intro" data-l10n-args='{"hostname": "${this.hostname}"}' ></p>`; + case "NS_ERROR_DOM_COOP_FAILED": + case "NS_ERROR_DOM_COEP_FAILED": + return html`<p data-l10n-id="fp-neterror-coop-coep-intro"></p>`; } return null; @@ -451,6 +495,124 @@ export class NetErrorCard extends MozLitElement { : null} `; } + customNetErrorContainerTemplate() { + if (!this.showCustomNetErrorCard) { + return null; + } + + let content; + + switch (this.errorInfo.errorCodeString) { + case "NS_ERROR_OFFLINE": { + content = this.customNetErrorSectionTemplate({ + titleL10nId: "fp-neterror-offline-body-title", + whatCanYouDoL10nId: "fp-neterror-offline-what-can-you-do-body", + whatCanYouDoL10nArgs: { + hostname: this.hostname, + }, + buttons: { + tryAgain: true, + }, + }); + break; + } + case "NS_ERROR_DOM_COOP_FAILED": + case "NS_ERROR_DOM_COEP_FAILED": { + content = this.customNetErrorSectionTemplate({ + titleL10nId: "fp-certerror-body-title", + whyDidThisHappenL10nId: + "fp-neterror-coop-coep-why-did-this-happen-body", + whyDidThisHappenL10nArgs: { + hostname: this.hostname, + }, + learnMoreL10nId: + gErrorCode === "blockedByCOOP" + ? "certerror-coop-learn-more" + : "certerror-coep-learn-more", + learnMoreSupportPage: + gErrorCode === "blockedByCOOP" ? COOP_MDN_DOCS : COEP_MDN_DOCS, + buttons: { + goBack: window.self === window.top, + }, + }); + break; + } + } + + return html`<div class="custom-net-error-card">${content}</div>`; + } + + customNetErrorSectionTemplate(params) { + const { + titleL10nId, + whyDidThisHappenL10nId, + whyDidThisHappenL10nArgs, + whatCanYouDoL10nId, + whatCanYouDoL10nArgs, + learnMoreL10nId, + learnMoreSupportPage, + buttons = {}, + } = params; + + const { goBack = false, tryAgain = false } = buttons; + + return html`<h1 id="neterror-title-text" data-l10n-id=${titleL10nId}></h1> + ${this.introContentTemplate()} + ${whatCanYouDoL10nId + ? html`<p> + <strong data-l10n-id="fp-certerror-what-can-you-do"></strong> + <span + data-l10n-id=${whatCanYouDoL10nId} + data-l10n-args=${JSON.stringify(whatCanYouDoL10nArgs)} + ></span> + </p>` + : null} + ${whyDidThisHappenL10nId + ? html`<p> + <strong data-l10n-id="fp-certerror-what-can-you-do"></strong> + <span + data-l10n-id=${whyDidThisHappenL10nId} + data-l10n-args=${JSON.stringify(whyDidThisHappenL10nArgs)} + ></span> + </p>` + : null} + ${learnMoreL10nId + ? html`<p> + <a + href=${learnMoreSupportPage} + data-l10n-id=${learnMoreL10nId} + data-telemetry-id="learn_more_link" + id="neterror-learn-more-link" + @click=${this.handleTelemetryClick} + rel="noopener noreferrer" + target="_blank" + ></a> + </p>` + : null} + ${tryAgain + ? html`<moz-button-group + ><moz-button + id="tryAgainButton" + type="primary" + data-l10n-id="neterror-try-again-button" + data-telemetry-id="try_again_button" + @click=${this.handleTryAgain} + ></moz-button + ></moz-button-group>` + : null} + ${goBack + ? html`<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-group>` + : null}`; + } + async getDomainMismatchNames() { if (this.domainMismatchNamesPromise) { return; @@ -646,31 +808,8 @@ export class NetErrorCard extends MozLitElement { <img src="chrome://global/skin/illustrations/security-error.svg" /> </div> <div class="container"> - ${this.errorInfo.errorCodeString === "NS_ERROR_OFFLINE" - ? html`<h1 - id="neterror-title-text" - data-l10n-id="fp-neterror-offline-body-title" - ></h1> - ${this.introContentTemplate()} - <p> - <strong data-l10n-id="fp-certerror-what-can-you-do"></strong> - </p> - <p> - <span - data-l10n-id="fp-neterror-offline-what-can-you-do-body" - data-l10n-args='{"hostname": "${this.hostname}"}' - ></span> - </p> - <moz-button-group> - <moz-button - id="neterrorTryAgainButton" - class="try-again" - type="primary" - data-l10n-id="neterror-try-again-button" - data-telemetry-id="try_again_button" - @click=${this.handleTryAgain} - ></moz-button> - </moz-button-group>` + ${this.showCustomNetErrorCard + ? html`${this.customNetErrorContainerTemplate()}` : html`<h1 id="certErrorBodyTitle" data-l10n-id="fp-certerror-body-title" diff --git a/toolkit/locales/en-US/toolkit/neterror/netError.ftl b/toolkit/locales/en-US/toolkit/neterror/netError.ftl @@ -197,9 +197,15 @@ fp-neterror-offline-body-title = Looks like there’s a problem with your intern fp-neterror-connection-intro = { -brand-short-name } can’t create a secure connection to the server at { $hostname }. fp-neterror-offline-intro = { -brand-short-name } can’t connect to the server at <strong>{ $hostname }</strong> +fp-neterror-coop-coep-intro = { -brand-short-name } didn’t load this page because it looks like the security configuration doesn’t match the previous page. + +fp-neterror-why-did-this-happen = Why did this happen? # This string appears after the following string: "What makes the site look dangerous?" (fp-certerror-why-site-dangerous) fp-neterror-cypher-overlap-why-dangerous-body = It looks like this site is using old software with known security issues. # This string appears after the following string: "What can you do about it?" (fp-certerror-what-can-you-do) fp-neterror-cypher-overlap-what-can-you-do-body = Make sure you’re using the latest version of { -brand-short-name }. Go to Help > About { -brand-short-name } in the menu. If you’re using the latest { -brand-short-name }, the problem is most likely with the site itself. fp-neterror-offline-what-can-you-do-body = Try connecting on a different device. Check your modem or router. Disconnect and reconnect to Wi-Fi. + +# This string appears after the following string: "Why did this happen?" (fp-neterror-why-did-this-happen) +fp-neterror-coop-coep-why-did-this-happen-body = Sometimes websites set up protections for themselves from unwanted interactions with other sites.