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:
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.