commit 126b864d44986f1aaf357daa800d1389e616b70b
parent 2f8ce2d3d1e117ebda8d02d185565b981cfa53ce
Author: mailelucks <maile.lucks@gmail.com>
Date: Mon, 1 Dec 2025 19:19:13 +0000
Bug 1992253 - Add NS_ERROR_OFFLINE support to net-error-card component - r=jaws,niklas,fluent-reviewers,bolsson
Differential Revision: https://phabricator.services.mozilla.com/D269671
Diffstat:
5 files changed, 151 insertions(+), 44 deletions(-)
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
@@ -33,6 +33,44 @@ async function checkErrorForInvalidUriLoad(l10nId) {
BrowserTestUtils.removeTab(gBrowser.selectedTab);
}
+async function checkErrorForInvalidUriLoad_feltPrivacyToTrue(l10nId) {
+ let browser;
+ let pageLoaded;
+ await setSecurityCertErrorsFeltPrivacyToTrue(true);
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "https://does-not-exist.test"
+ );
+ browser = gBrowser.selectedBrowser;
+ pageLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ },
+ false
+ );
+ await pageLoaded;
+
+ await SpecialPowers.spawn(browser, [l10nId], async expectedl10nId => {
+ const doc = content.document;
+ const netErrorCard = doc.querySelector("net-error-card").wrappedJSObject;
+ await netErrorCard.getUpdateComplete();
+
+ Assert.ok(
+ doc.documentURI.startsWith("about:neterror"),
+ "Should be showing error page"
+ );
+
+ Assert.strictEqual(
+ netErrorCard.netErrorTitleText.dataset.l10nId,
+ expectedl10nId,
+ "Correct error page title is set"
+ );
+ });
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await SpecialPowers.popPrefEnv();
+}
+
registerCleanupFunction(function () {
Services.io.offline = false;
Services.io.setConnectivityForTesting(true);
@@ -41,10 +79,16 @@ registerCleanupFunction(function () {
add_task(async function test_offline_mode() {
Services.io.offline = true;
await checkErrorForInvalidUriLoad("netOffline-title");
+ await checkErrorForInvalidUriLoad_feltPrivacyToTrue(
+ "fp-neterror-offline-body-title"
+ );
});
add_task(async function test_internet_connection_offline() {
Services.io.offline = false;
Services.io.setConnectivityForTesting(false);
await checkErrorForInvalidUriLoad("internet-connection-offline-title");
+ await checkErrorForInvalidUriLoad_feltPrivacyToTrue(
+ "fp-neterror-offline-body-title"
+ );
});
diff --git a/toolkit/content/aboutNetError.mjs b/toolkit/content/aboutNetError.mjs
@@ -16,6 +16,9 @@ import {
getFailedCertificatesAsPEMString,
recordSecurityUITelemetry,
getCSSClass,
+ gNoConnectivity,
+ gOffline,
+ retryThis,
} from "chrome://global/content/aboutNetErrorHelpers.mjs";
const formatter = new Intl.DateTimeFormat();
@@ -104,11 +107,6 @@ function getMitmName(failedCertInfo) {
return failedCertInfo.issuerCommonName;
}
-function retryThis(buttonEl) {
- RPMSendAsyncMessage("Browser:EnableOnlineMode");
- buttonEl.disabled = true;
-}
-
function showPrefChangeContainer() {
const panel = document.getElementById("prefChangeContainer");
panel.hidden = false;
@@ -447,7 +445,6 @@ function initPage() {
}
const isTRROnlyFailure = gErrorCode == "dnsNotFound" && RPMIsTRROnlyFailure();
- let noConnectivity = gErrorCode == "dnsNotFound" && !RPMHasConnectivity();
const docTitle = document.querySelector("title");
const shortDesc = document.getElementById("errorShortDesc");
@@ -498,7 +495,7 @@ function initPage() {
);
// We can handle the offline page separately.
- if (noConnectivity) {
+ if (gNoConnectivity) {
pageTitleId = "neterror-dns-not-found-title";
bodyTitleId = "internet-connection-offline-title";
}
@@ -511,7 +508,7 @@ function initPage() {
// The TRR errors may present options that direct users to settings only available on Firefox Desktop
if (RPMIsFirefox()) {
- if (isTRROnlyFailure && !noConnectivity) {
+ if (isTRROnlyFailure && !gNoConnectivity) {
pageTitleId = "neterror-dns-not-found-title";
document.l10n.setAttributes(docTitle, pageTitleId);
if (bodyTitle) {
@@ -639,7 +636,7 @@ function initPage() {
setFocus("#netErrorButtonContainer > .try-again");
if (longDesc) {
- const parts = getNetErrorDescParts(noConnectivity);
+ const parts = getNetErrorDescParts(gNoConnectivity);
setNetErrorMessageFromParts(longDesc, parts);
}
@@ -1469,8 +1466,15 @@ function shouldUseFeltPrivacyRefresh() {
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(errorInfo.errorCodeString);
+ return NetErrorCard.ERROR_CODES.has(errorCode);
}
if (!shouldUseFeltPrivacyRefresh()) {
diff --git a/toolkit/content/aboutNetErrorHelpers.mjs b/toolkit/content/aboutNetErrorHelpers.mjs
@@ -27,6 +27,9 @@ export let searchParams = new URLSearchParams(
export let gErrorCode = searchParams.get("e");
export let gIsCertError = gErrorCode == "nssBadCert";
export let gHasSts = gIsCertError && getCSSClass() === "badStsCert";
+export let gNoConnectivity =
+ gErrorCode == "dnsNotFound" && !RPMHasConnectivity();
+export let gOffline = gErrorCode === "netOffline" || gNoConnectivity;
const HOST_NAME = getHostName();
export function isCaptive() {
@@ -46,6 +49,11 @@ export function getHostName() {
return "";
}
+export function retryThis(buttonEl) {
+ RPMSendAsyncMessage("Browser:EnableOnlineMode");
+ buttonEl.disabled = true;
+}
+
export async function getFailedCertificatesAsPEMString() {
let locationUrl = document.location.href;
let failedCertInfo = document.getFailedCertSecurityInfo();
diff --git a/toolkit/content/net-error-card.mjs b/toolkit/content/net-error-card.mjs
@@ -7,12 +7,15 @@
import {
gHasSts,
gIsCertError,
+ gErrorCode,
isCaptive,
getCSSClass,
getHostName,
getSubjectAltNames,
getFailedCertificatesAsPEMString,
recordSecurityUITelemetry,
+ gOffline,
+ retryThis,
} 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";
@@ -46,6 +49,7 @@ export class NetErrorCard extends MozLitElement {
learnMoreLink: "#learnMoreLink",
whatCanYouDo: "#whatCanYouDo",
whyDangerous: "#fp-why-site-dangerous",
+ netErrorTitleText: "#neterror-title-text",
};
static ERROR_CODES = new Set([
@@ -56,6 +60,7 @@ export class NetErrorCard extends MozLitElement {
"SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE",
"SSL_ERROR_NO_CYPHER_OVERLAP",
"MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY",
+ "NS_ERROR_OFFLINE",
]);
constructor() {
@@ -77,11 +82,8 @@ export class NetErrorCard extends MozLitElement {
}
await Promise.all([
- this.getDomainMismatchNames(),
- this.getCertificateErrorText(),
- ]);
-
- await Promise.all([
+ gErrorCode === "domain-mismatch" && this.getDomainMismatchNames(),
+ document.getFailedCertSecurityInfo && this.getCertificateErrorText(),
this.domainMismatchNamesPromise,
this.certificateErrorTextPromise,
]);
@@ -161,9 +163,15 @@ export class NetErrorCard extends MozLitElement {
}
getErrorInfo() {
- return gIsCertError
+ const errorInfo = gIsCertError
? document.getFailedCertSecurityInfo()
: document.getNetErrorInfo();
+
+ if (gOffline) {
+ errorInfo.errorCodeString = "NS_ERROR_OFFLINE";
+ }
+
+ return errorInfo;
}
introContentTemplate() {
@@ -192,6 +200,11 @@ export class NetErrorCard extends MozLitElement {
data-l10n-id="fp-certerror-transparency-intro"
data-l10n-args='{"hostname": "${this.hostname}"}'
></p>`;
+ case "NS_ERROR_OFFLINE":
+ return html`<p
+ data-l10n-id="fp-neterror-offline-intro"
+ data-l10n-args='{"hostname": "${this.hostname}"}'
+ ></p>`;
}
return null;
@@ -393,7 +406,7 @@ export class NetErrorCard extends MozLitElement {
</p>`
: null}
${gIsCertError
- ? html` <p>
+ ? html`<p>
<a
id="errorCode"
data-l10n-id="fp-cert-error-code"
@@ -488,6 +501,11 @@ export class NetErrorCard extends MozLitElement {
);
}
+ handleTryAgain(e) {
+ this.handleTelemetryClick(e);
+ retryThis(e);
+ }
+
toggleAdvancedShowing(e) {
if (e) {
this.handleTelemetryClick(e);
@@ -579,8 +597,11 @@ export class NetErrorCard extends MozLitElement {
target = target.getRootNode().host;
}
let telemetryId = target.dataset.telemetryId;
+ const category = gIsCertError
+ ? "securityUiCerterror"
+ : "securityUiNeterror";
void recordSecurityUITelemetry(
- "securityUiCerterror",
+ category,
"click" +
telemetryId
.split("_")
@@ -604,30 +625,55 @@ export class NetErrorCard extends MozLitElement {
<img src="chrome://global/skin/illustrations/security-error.svg" />
</div>
<div class="container">
- <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
- id="advanced-button"
- data-l10n-id=${this.advancedShowing
- ? "fp-certerror-hide-advanced-button"
- : "fp-certerror-advanced-button"}
- data-telemetry-id="advanced_button"
- @click=${this.toggleAdvancedShowing}
- ></moz-button
- ></moz-button-group>
- ${this.advancedContainerTemplate()}
- ${this.certErrorDebugInfoTemplate()}
+ ${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>`
+ : html`<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
+ id="advanced-button"
+ data-l10n-id=${this.advancedShowing
+ ? "fp-certerror-hide-advanced-button"
+ : "fp-certerror-advanced-button"}
+ data-telemetry-id="advanced_button"
+ @click=${this.toggleAdvancedShowing}
+ ></moz-button
+ ></moz-button-group>
+ ${this.advancedContainerTemplate()}
+ ${this.certErrorDebugInfoTemplate()}`}
</div>
</article>`;
}
diff --git a/toolkit/locales/en-US/toolkit/neterror/netError.ftl b/toolkit/locales/en-US/toolkit/neterror/netError.ftl
@@ -189,12 +189,17 @@ certerror-coep-learn-more = Learn more about Cross Origin Embedder Policies (COE
neterror-response-status-code = Error code: { $responsestatus } { $responsestatustext }
## Felt Privacy V1 Strings
+
+fp-neterror-offline-body-title = Looks like there’s a problem with your internet connection
+
## Variables:
## $hostname (String) - Hostname of the website to which the user was trying to connect.
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>
-# This string appears after the following string: "What makes the site look dangerous?" (fp-neterror-why-site-dangerous)
+# 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-neterror-what-can-you-do)
+# 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.