Backed out changeset 5a2ea27885f0 (bug 1850738) for causing build bustages on gfxUser...
[gecko.git] / toolkit / content / aboutNetError.mjs
blob1c92a2927409f77193b5800d371dbe7839fdb2fa
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /* eslint-env mozilla/remote-page */
6 /* eslint-disable import/no-unassigned-import */
8 import {
9   parse,
10   pemToDER,
11 } from "chrome://global/content/certviewer/certDecoder.mjs";
13 const formatter = new Intl.DateTimeFormat();
15 const HOST_NAME = getHostName();
17 function getHostName() {
18   try {
19     return new URL(RPMGetInnerMostURI(document.location.href)).hostname;
20   } catch (error) {
21     console.error("Could not parse URL", error);
22   }
23   return "";
26 // Used to check if we have a specific localized message for an error.
27 const KNOWN_ERROR_TITLE_IDS = new Set([
28   // Error titles:
29   "connectionFailure-title",
30   "deniedPortAccess-title",
31   "dnsNotFound-title",
32   "dns-not-found-trr-only-title2",
33   "fileNotFound-title",
34   "fileAccessDenied-title",
35   "generic-title",
36   "captivePortal-title",
37   "malformedURI-title",
38   "netInterrupt-title",
39   "notCached-title",
40   "netOffline-title",
41   "contentEncodingError-title",
42   "unsafeContentType-title",
43   "netReset-title",
44   "netTimeout-title",
45   "unknownProtocolFound-title",
46   "proxyConnectFailure-title",
47   "proxyResolveFailure-title",
48   "redirectLoop-title",
49   "unknownSocketType-title",
50   "nssFailure2-title",
51   "csp-xfo-error-title",
52   "corruptedContentError-title",
53   "sslv3Used-title",
54   "inadequateSecurityError-title",
55   "blockedByPolicy-title",
56   "clockSkewError-title",
57   "networkProtocolError-title",
58   "nssBadCert-title",
59   "nssBadCert-sts-title",
60   "certerror-mitm-title",
61 ]);
63 /* The error message IDs from nsserror.ftl get processed into
64  * aboutNetErrorCodes.js which is loaded before we are: */
65 /* global KNOWN_ERROR_MESSAGE_IDS */
66 const ERROR_MESSAGES_FTL = "toolkit/neterror/nsserrors.ftl";
68 // The following parameters are parsed from the error URL:
69 //   e - the error code
70 //   s - custom CSS class to allow alternate styling/favicons
71 //   d - error description
72 //   captive - "true" to indicate we're behind a captive portal.
73 //             Any other value is ignored.
75 // Note that this file uses document.documentURI to get
76 // the URL (with the format from above). This is because
77 // document.location.href gets the current URI off the docshell,
78 // which is the URL displayed in the location bar, i.e.
79 // the URI that the user attempted to load.
81 let searchParams = new URLSearchParams(document.documentURI.split("?")[1]);
83 let gErrorCode = searchParams.get("e");
84 let gIsCertError = gErrorCode == "nssBadCert";
85 let gHasSts = gIsCertError && getCSSClass() === "badStsCert";
87 // If the location of the favicon changes, FAVICON_CERTERRORPAGE_URL and/or
88 // FAVICON_ERRORPAGE_URL in toolkit/components/places/nsFaviconService.idl
89 // should also be updated.
90 document.getElementById("favicon").href =
91   gIsCertError || gErrorCode == "nssFailure2"
92     ? "chrome://global/skin/icons/warning.svg"
93     : "chrome://global/skin/icons/info.svg";
95 function getCSSClass() {
96   return searchParams.get("s");
99 function getDescription() {
100   return searchParams.get("d");
103 function isCaptive() {
104   return searchParams.get("captive") == "true";
108  * We don't actually know what the MitM is called (since we don't
109  * maintain a list), so we'll try and display the common name of the
110  * root issuer to the user. In the worst case they are as clueless as
111  * before, in the best case this gives them an actionable hint.
112  * This may be revised in the future.
113  */
114 function getMitmName(failedCertInfo) {
115   return failedCertInfo.issuerCommonName;
118 function retryThis(buttonEl) {
119   RPMSendAsyncMessage("Browser:EnableOnlineMode");
120   buttonEl.disabled = true;
123 function showPrefChangeContainer() {
124   const panel = document.getElementById("prefChangeContainer");
125   panel.hidden = false;
126   document.getElementById("netErrorButtonContainer").hidden = true;
127   document
128     .getElementById("prefResetButton")
129     .addEventListener("click", function resetPreferences() {
130       RPMSendAsyncMessage("Browser:ResetSSLPreferences");
131     });
132   setFocus("#prefResetButton", "beforeend");
135 function toggleCertErrorDebugInfoVisibility(shouldShow) {
136   let debugInfo = document.getElementById("certificateErrorDebugInformation");
137   let copyButton = document.getElementById("copyToClipboardTop");
139   if (shouldShow === undefined) {
140     shouldShow = debugInfo.hidden;
141   }
142   debugInfo.hidden = !shouldShow;
143   if (shouldShow) {
144     copyButton.scrollIntoView({ block: "start", behavior: "smooth" });
145     copyButton.focus();
146   }
149 function setupAdvancedButton() {
150   // Get the hostname and add it to the panel
151   var panel = document.getElementById("badCertAdvancedPanel");
153   // Register click handler for the weakCryptoAdvancedPanel
154   document
155     .getElementById("advancedButton")
156     .addEventListener("click", togglePanelVisibility);
158   function togglePanelVisibility() {
159     if (panel.hidden) {
160       // Reveal
161       revealAdvancedPanelSlowlyAsync();
163       // send event to trigger telemetry ping
164       document.dispatchEvent(
165         new CustomEvent("AboutNetErrorUIExpanded", { bubbles: true })
166       );
167     } else {
168       // Hide
169       panel.hidden = true;
170     }
171   }
173   if (getCSSClass() == "expertBadCert") {
174     revealAdvancedPanelSlowlyAsync();
175   }
178 async function revealAdvancedPanelSlowlyAsync() {
179   const badCertAdvancedPanel = document.getElementById("badCertAdvancedPanel");
180   const exceptionDialogButton = document.getElementById(
181     "exceptionDialogButton"
182   );
184   // Toggling the advanced panel must ensure that the debugging
185   // information panel is hidden as well, since it's opened by the
186   // error code link in the advanced panel.
187   toggleCertErrorDebugInfoVisibility(false);
189   // Reveal, but disabled (and grayed-out) for 3.0s.
190   badCertAdvancedPanel.hidden = false;
191   exceptionDialogButton.disabled = true;
193   // -
195   if (exceptionDialogButton.resetReveal) {
196     exceptionDialogButton.resetReveal(); // Reset if previous is pending.
197   }
198   let wasReset = false;
199   exceptionDialogButton.resetReveal = () => {
200     wasReset = true;
201   };
203   // Wait for 10 frames to ensure that the warning text is rendered
204   // and gets all the way to the screen for the user to read it.
205   // This is only ~0.160s at 60Hz, so it's not too much extra time that we're
206   // taking to ensure that we're caught up with rendering, on top of the
207   // (by default) whole second(s) we're going to wait based on the
208   // security.dialog_enable_delay pref.
209   // The catching-up to rendering is the important part, not the
210   // N-frame-delay here.
211   for (let i = 0; i < 10; i++) {
212     await new Promise(requestAnimationFrame);
213   }
215   // Wait another Nms (default: 1000) for the user to be very sure. (Sorry speed readers!)
216   const securityDelayMs = RPMGetIntPref("security.dialog_enable_delay", 1000);
217   await new Promise(go => setTimeout(go, securityDelayMs));
219   if (wasReset) {
220     return;
221   }
223   // Enable and un-gray-out.
224   exceptionDialogButton.disabled = false;
227 function disallowCertOverridesIfNeeded() {
228   // Disallow overrides if this is a Strict-Transport-Security
229   // host and the cert is bad (STS Spec section 7.3) or if the
230   // certerror is in a frame (bug 633691).
231   if (gHasSts || window != top) {
232     document.getElementById("exceptionDialogButton").hidden = true;
233   }
234   if (gHasSts) {
235     const stsExplanation = document.getElementById("badStsCertExplanation");
236     document.l10n.setAttributes(
237       stsExplanation,
238       "certerror-what-should-i-do-bad-sts-cert-explanation",
239       { hostname: HOST_NAME }
240     );
241     stsExplanation.hidden = false;
243     document.l10n.setAttributes(
244       document.getElementById("returnButton"),
245       "neterror-return-to-previous-page-button"
246     );
247     document.l10n.setAttributes(
248       document.getElementById("advancedPanelReturnButton"),
249       "neterror-return-to-previous-page-button"
250     );
251   }
254 function recordTRREventTelemetry(
255   warningPageType,
256   trrMode,
257   trrDomain,
258   skipReason
259 ) {
260   RPMRecordTelemetryEvent(
261     "security.doh.neterror",
262     "load",
263     "dohwarning",
264     warningPageType,
265     {
266       mode: trrMode,
267       provider_key: trrDomain,
268       skip_reason: skipReason,
269     }
270   );
272   const netErrorButtonDiv = document.getElementById("netErrorButtonContainer");
273   const buttons = netErrorButtonDiv.querySelectorAll("button");
274   for (let b of buttons) {
275     b.addEventListener("click", function (e) {
276       let target = e.originalTarget;
277       let telemetryId = target.dataset.telemetryId;
278       RPMRecordTelemetryEvent(
279         "security.doh.neterror",
280         "click",
281         telemetryId,
282         warningPageType,
283         {
284           mode: trrMode,
285           provider_key: trrDomain,
286           skip_reason: skipReason,
287         }
288       );
289     });
290   }
293 function initPage() {
294   // We show an offline support page in case of a system-wide error,
295   // when a user cannot connect to the internet and access the SUMO website.
296   // For example, clock error, which causes certerrors across the web or
297   // a security software conflict where the user is unable to connect
298   // to the internet.
299   // The URL that prompts us to show an offline support page should have the following
300   // format: "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/supportPageSlug",
301   // so we can extract the support page slug.
302   let baseURL = RPMGetFormatURLPref("app.support.baseURL");
303   if (document.location.href.startsWith(baseURL)) {
304     let supportPageSlug = document.location.pathname.split("/").pop();
305     RPMSendAsyncMessage("DisplayOfflineSupportPage", {
306       supportPageSlug,
307     });
308   }
310   const className = getCSSClass();
311   if (className) {
312     document.body.classList.add(className);
313   }
315   const isTRROnlyFailure = gErrorCode == "dnsNotFound" && RPMIsTRROnlyFailure();
317   let isNativeFallbackWarning = false;
318   if (RPMGetBoolPref("network.trr.display_fallback_warning")) {
319     isNativeFallbackWarning =
320       gErrorCode == "dnsNotFound" && RPMIsNativeFallbackFailure();
321   }
323   const docTitle = document.querySelector("title");
324   const bodyTitle = document.querySelector(".title-text");
325   const shortDesc = document.getElementById("errorShortDesc");
327   if (gIsCertError) {
328     const isStsError = window !== window.top || gHasSts;
329     const errArgs = { hostname: HOST_NAME };
330     if (isCaptive()) {
331       document.l10n.setAttributes(
332         docTitle,
333         "neterror-captive-portal-page-title"
334       );
335       document.l10n.setAttributes(bodyTitle, "captivePortal-title");
336       document.l10n.setAttributes(
337         shortDesc,
338         "neterror-captive-portal",
339         errArgs
340       );
341       initPageCaptivePortal();
342     } else {
343       if (isStsError) {
344         document.l10n.setAttributes(docTitle, "certerror-sts-page-title");
345         document.l10n.setAttributes(bodyTitle, "nssBadCert-sts-title");
346         document.l10n.setAttributes(shortDesc, "certerror-sts-intro", errArgs);
347       } else {
348         document.l10n.setAttributes(docTitle, "certerror-page-title");
349         document.l10n.setAttributes(bodyTitle, "nssBadCert-title");
350         document.l10n.setAttributes(shortDesc, "certerror-intro", errArgs);
351       }
352       initPageCertError();
353     }
355     initCertErrorPageActions();
356     setTechnicalDetailsOnCertError();
357     return;
358   }
360   document.body.classList.add("neterror");
362   let longDesc = document.getElementById("errorLongDesc");
363   const tryAgain = document.getElementById("netErrorButtonContainer");
364   tryAgain.hidden = false;
365   const learnMore = document.getElementById("learnMoreContainer");
366   const learnMoreLink = document.getElementById("learnMoreLink");
367   learnMoreLink.setAttribute("href", baseURL + "connection-not-secure");
369   let pageTitleId = "neterror-page-title";
370   let bodyTitleId = gErrorCode + "-title";
372   switch (gErrorCode) {
373     case "blockedByPolicy":
374       pageTitleId = "neterror-blocked-by-policy-page-title";
375       document.body.classList.add("blocked");
377       // Remove the "Try again" button from pages that don't need it.
378       // For pages blocked by policy, trying again won't help.
379       tryAgain.hidden = true;
380       break;
382     case "cspBlocked":
383     case "xfoBlocked": {
384       bodyTitleId = "csp-xfo-error-title";
386       // Remove the "Try again" button for XFO and CSP violations,
387       // since it's almost certainly useless. (Bug 553180)
388       tryAgain.hidden = true;
390       // Adding a button for opening websites blocked for CSP and XFO violations
391       // in a new window. (Bug 1461195)
392       document.getElementById("errorShortDesc").hidden = true;
394       document.l10n.setAttributes(longDesc, "csp-xfo-blocked-long-desc", {
395         hostname: HOST_NAME,
396       });
397       longDesc = null;
399       document.getElementById("openInNewWindowContainer").hidden = false;
401       const openInNewWindowButton = document.getElementById(
402         "openInNewWindowButton"
403       );
404       openInNewWindowButton.href = document.location.href;
406       // Add a learn more link
407       learnMore.hidden = false;
408       learnMoreLink.setAttribute("href", baseURL + "xframe-neterror-page");
410       setupBlockingReportingUI();
411       break;
412     }
414     case "dnsNotFound":
415       pageTitleId = "neterror-dns-not-found-title";
416       if (!isTRROnlyFailure) {
417         RPMCheckAlternateHostAvailable();
418       }
420       break;
421     case "inadequateSecurityError":
422       // Remove the "Try again" button from pages that don't need it.
423       // For HTTP/2 inadequate security, trying again won't help.
424       tryAgain.hidden = true;
425       break;
427     case "malformedURI":
428       pageTitleId = "neterror-malformed-uri-page-title";
429       // Remove the "Try again" button from pages that don't need it.
430       tryAgain.hidden = true;
431       break;
433     // Pinning errors are of type nssFailure2
434     case "nssFailure2": {
435       learnMore.hidden = false;
437       const errorCode = document.getNetErrorInfo().errorCodeString;
438       switch (errorCode) {
439         case "SSL_ERROR_UNSUPPORTED_VERSION":
440         case "SSL_ERROR_PROTOCOL_VERSION_ALERT": {
441           const tlsNotice = document.getElementById("tlsVersionNotice");
442           tlsNotice.hidden = false;
443           document.l10n.setAttributes(tlsNotice, "cert-error-old-tls-version");
444         }
445         // fallthrough
447         case "interrupted": // This happens with subresources that are above the max tls
448         case "SSL_ERROR_NO_CIPHERS_SUPPORTED":
449         case "SSL_ERROR_NO_CYPHER_OVERLAP":
450         case "SSL_ERROR_SSL_DISABLED":
451           RPMAddMessageListener("HasChangedCertPrefs", msg => {
452             if (msg.data.hasChangedCertPrefs) {
453               // Configuration overrides might have caused this; offer to reset.
454               showPrefChangeContainer();
455             }
456           });
457           RPMSendAsyncMessage("GetChangedCertPrefs");
458       }
460       break;
461     }
463     case "sslv3Used":
464       learnMore.hidden = false;
465       document.body.className = "certerror";
466       break;
467   }
469   if (!KNOWN_ERROR_TITLE_IDS.has(bodyTitleId)) {
470     console.error("No strings exist for error:", gErrorCode);
471     bodyTitleId = "generic-title";
472   }
474   // The TRR errors may present options that direct users to settings only available on Firefox Desktop
475   if (RPMIsFirefox()) {
476     if (isTRROnlyFailure) {
477       document.body.className = "certerror"; // Shows warning icon
478       pageTitleId = "dns-not-found-trr-only-title2";
479       document.l10n.setAttributes(docTitle, pageTitleId);
480       bodyTitleId = "dns-not-found-trr-only-title2";
481       document.l10n.setAttributes(bodyTitle, bodyTitleId);
483       shortDesc.textContent = "";
484       let skipReason = RPMGetTRRSkipReason();
486       // enable buttons
487       let trrExceptionButton = document.getElementById("trrExceptionButton");
488       trrExceptionButton.addEventListener("click", () => {
489         RPMSendQuery("Browser:AddTRRExcludedDomain", {
490           hostname: HOST_NAME,
491         }).then(msg => {
492           retryThis(trrExceptionButton);
493         });
494       });
496       let isTrrServerError = true;
497       if (RPMIsSiteSpecificTRRError()) {
498         // Only show the exclude button if the failure is specific to this
499         // domain. If the TRR server is inaccessible we don't want to allow
500         // the user to add an exception just for this domain.
501         trrExceptionButton.hidden = false;
502         isTrrServerError = false;
503       }
504       let trrSettingsButton = document.getElementById("trrSettingsButton");
505       trrSettingsButton.addEventListener("click", () => {
506         RPMSendAsyncMessage("OpenTRRPreferences");
507       });
508       trrSettingsButton.hidden = false;
509       let message = document.getElementById("trrOnlyMessage");
510       document.l10n.setAttributes(
511         message,
512         "neterror-dns-not-found-trr-only-reason",
513         {
514           hostname: HOST_NAME,
515         }
516       );
518       let descriptionTag = "neterror-dns-not-found-trr-unknown-problem";
519       let args = { trrDomain: RPMGetTRRDomain() };
520       if (
521         skipReason == "TRR_FAILED" ||
522         skipReason == "TRR_CHANNEL_DNS_FAIL" ||
523         skipReason == "TRR_UNKNOWN_CHANNEL_FAILURE" ||
524         skipReason == "TRR_NET_REFUSED" ||
525         skipReason == "TRR_NET_INTERRUPT" ||
526         skipReason == "TRR_NET_INADEQ_SEQURITY"
527       ) {
528         descriptionTag = "neterror-dns-not-found-trr-only-could-not-connect";
529       } else if (skipReason == "TRR_TIMEOUT") {
530         descriptionTag = "neterror-dns-not-found-trr-only-timeout";
531       } else if (
532         skipReason == "TRR_IS_OFFLINE" ||
533         skipReason == "TRR_NO_CONNECTIVITY"
534       ) {
535         descriptionTag = "neterror-dns-not-found-trr-offline";
536       } else if (
537         skipReason == "TRR_NO_ANSWERS" ||
538         skipReason == "TRR_NXDOMAIN" ||
539         skipReason == "TRR_RCODE_FAIL"
540       ) {
541         descriptionTag = "neterror-dns-not-found-trr-unknown-host2";
542       } else if (
543         skipReason == "TRR_DECODE_FAILED" ||
544         skipReason == "TRR_SERVER_RESPONSE_ERR"
545       ) {
546         descriptionTag = "neterror-dns-not-found-trr-server-problem";
547       } else if (skipReason == "TRR_BAD_URL") {
548         descriptionTag = "neterror-dns-not-found-bad-trr-url";
549       }
551       let trrMode = RPMGetIntPref("network.trr.mode").toString();
552       recordTRREventTelemetry(
553         "TRROnlyFailure",
554         trrMode,
555         args.trrDomain,
556         skipReason
557       );
559       let description = document.getElementById("trrOnlyDescription");
560       document.l10n.setAttributes(description, descriptionTag, args);
562       const trrLearnMoreContainer = document.getElementById(
563         "trrLearnMoreContainer"
564       );
565       trrLearnMoreContainer.hidden = false;
566       let trrOnlyLearnMoreLink = document.getElementById(
567         "trrOnlylearnMoreLink"
568       );
569       if (isTrrServerError) {
570         // Go to DoH settings page
571         trrOnlyLearnMoreLink.href = "about:preferences#privacy-doh";
572         trrOnlyLearnMoreLink.addEventListener("click", event => {
573           event.preventDefault();
574           RPMSendAsyncMessage("OpenTRRPreferences");
575           RPMRecordTelemetryEvent(
576             "security.doh.neterror",
577             "click",
578             "settings_button",
579             "TRROnlyFailure",
580             {
581               mode: trrMode,
582               provider_key: args.trrDomain,
583               skip_reason: skipReason,
584             }
585           );
586         });
587       } else {
588         // This will be replaced at a later point with a link to an offline support page
589         // https://bugzilla.mozilla.org/show_bug.cgi?id=1806257
590         trrOnlyLearnMoreLink.href =
591           RPMGetFormatURLPref("network.trr_ui.skip_reason_learn_more_url") +
592           skipReason.toLowerCase().replaceAll("_", "-");
593       }
595       let div = document.getElementById("trrOnlyContainer");
596       div.hidden = false;
598       return;
599     } else if (isNativeFallbackWarning) {
600       showNativeFallbackWarning();
601       return;
602     }
603   }
605   document.l10n.setAttributes(docTitle, pageTitleId);
606   document.l10n.setAttributes(bodyTitle, bodyTitleId);
608   shortDesc.textContent = getDescription();
609   setFocus("#netErrorButtonContainer > .try-again");
611   if (longDesc) {
612     const parts = getNetErrorDescParts();
613     setNetErrorMessageFromParts(longDesc, parts);
614   }
616   setNetErrorMessageFromCode();
619 function showNativeFallbackWarning() {
620   const docTitle = document.querySelector("title");
621   const bodyTitle = document.querySelector(".title-text");
622   const shortDesc = document.getElementById("errorShortDesc");
624   let pageTitleId = "neterror-page-title";
625   let bodyTitleId = gErrorCode + "-title";
627   document.body.className = "certerror"; // Shows warning icon
628   pageTitleId = "dns-not-found-native-fallback-title2";
629   document.l10n.setAttributes(docTitle, pageTitleId);
631   bodyTitleId = "dns-not-found-native-fallback-title2";
632   document.l10n.setAttributes(bodyTitle, bodyTitleId);
634   shortDesc.textContent = "";
635   let nativeFallbackIgnoreButton = document.getElementById(
636     "nativeFallbackIgnoreButton"
637   );
638   nativeFallbackIgnoreButton.addEventListener("click", () => {
639     RPMSetPref("network.trr.display_fallback_warning", false);
640     retryThis(nativeFallbackIgnoreButton);
641   });
643   let continueThisTimeButton = document.getElementById(
644     "nativeFallbackContinueThisTimeButton"
645   );
646   continueThisTimeButton.addEventListener("click", () => {
647     RPMSetTRRDisabledLoadFlags();
648     document.location.reload();
649   });
650   continueThisTimeButton.hidden = false;
652   nativeFallbackIgnoreButton.hidden = false;
653   let message = document.getElementById("nativeFallbackMessage");
654   document.l10n.setAttributes(
655     message,
656     "neterror-dns-not-found-native-fallback-reason",
657     {
658       hostname: HOST_NAME,
659     }
660   );
661   let skipReason = RPMGetTRRSkipReason();
662   let descriptionTag = "neterror-dns-not-found-trr-unknown-problem";
663   let args = { trrDomain: RPMGetTRRDomain() };
665   if (skipReason.includes("HEURISTIC_TRIPPED")) {
666     descriptionTag = "neterror-dns-not-found-native-fallback-heuristic";
667   } else if (skipReason == "TRR_NOT_CONFIRMED") {
668     descriptionTag = "neterror-dns-not-found-native-fallback-not-confirmed2";
669   }
671   let description = document.getElementById("nativeFallbackDescription");
672   document.l10n.setAttributes(description, descriptionTag, args);
674   let learnMoreContainer = document.getElementById(
675     "nativeFallbackLearnMoreContainer"
676   );
677   learnMoreContainer.hidden = false;
679   let learnMoreLink = document.getElementById("nativeFallbackLearnMoreLink");
680   learnMoreLink.href =
681     RPMGetFormatURLPref("network.trr_ui.skip_reason_learn_more_url") +
682     skipReason.toLowerCase().replaceAll("_", "-");
684   let div = document.getElementById("nativeFallbackContainer");
685   div.hidden = false;
687   recordTRREventTelemetry(
688     "NativeFallbackWarning",
689     RPMGetIntPref("network.trr.mode").toString(),
690     args.trrDomain,
691     skipReason
692   );
695  * Builds HTML elements from `parts` and appends them to `parentElement`.
697  * @param {HTMLElement} parentElement
698  * @param {Array<["li" | "p" | "span", string, Record<string, string> | undefined]>} parts
699  */
700 function setNetErrorMessageFromParts(parentElement, parts) {
701   let list = null;
703   for (let [tag, l10nId, l10nArgs] of parts) {
704     const elem = document.createElement(tag);
705     elem.dataset.l10nId = l10nId;
706     if (l10nArgs) {
707       elem.dataset.l10nArgs = JSON.stringify(l10nArgs);
708     }
710     if (tag === "li") {
711       if (!list) {
712         list = document.createElement("ul");
713         parentElement.appendChild(list);
714       }
715       list.appendChild(elem);
716     } else {
717       if (list) {
718         list = null;
719       }
720       parentElement.appendChild(elem);
721     }
722   }
726  * Returns an array of tuples determining the parts of an error message:
727  * - HTML tag name
728  * - l10n id
729  * - l10n args (optional)
731  * @returns { Array<["li" | "p" | "span", string, Record<string, string> | undefined]> }
732  */
733 function getNetErrorDescParts() {
734   switch (gErrorCode) {
735     case "connectionFailure":
736     case "netInterrupt":
737     case "netReset":
738     case "netTimeout":
739       return [
740         ["li", "neterror-load-error-try-again"],
741         ["li", "neterror-load-error-connection"],
742         ["li", "neterror-load-error-firewall"],
743       ];
745     case "blockedByPolicy":
746     case "deniedPortAccess":
747     case "malformedURI":
748       return [];
750     case "captivePortal":
751       return [["p", ""]];
752     case "contentEncodingError":
753       return [["li", "neterror-content-encoding-error"]];
754     case "corruptedContentErrorv2":
755       return [
756         ["p", "neterror-corrupted-content-intro"],
757         ["li", "neterror-corrupted-content-contact-website"],
758       ];
759     case "dnsNotFound":
760       return [
761         ["span", "neterror-dns-not-found-hint-header"],
762         ["li", "neterror-dns-not-found-hint-try-again"],
763         ["li", "neterror-dns-not-found-hint-check-network"],
764         ["li", "neterror-dns-not-found-hint-firewall"],
765       ];
766     case "fileAccessDenied":
767       return [["li", "neterror-access-denied"]];
768     case "fileNotFound":
769       return [
770         ["li", "neterror-file-not-found-filename"],
771         ["li", "neterror-file-not-found-moved"],
772       ];
773     case "inadequateSecurityError":
774       return [
775         ["p", "neterror-inadequate-security-intro", { hostname: HOST_NAME }],
776         ["p", "neterror-inadequate-security-code"],
777       ];
778     case "mitm": {
779       const failedCertInfo = document.getFailedCertSecurityInfo();
780       const errArgs = {
781         hostname: HOST_NAME,
782         mitm: getMitmName(failedCertInfo),
783       };
784       return [["span", "certerror-mitm", errArgs]];
785     }
786     case "netOffline":
787       return [["li", "neterror-net-offline"]];
788     case "networkProtocolError":
789       return [
790         ["p", "neterror-network-protocol-error-intro"],
791         ["li", "neterror-network-protocol-error-contact-website"],
792       ];
793     case "notCached":
794       return [
795         ["p", "neterror-not-cached-intro"],
796         ["li", "neterror-not-cached-sensitive"],
797         ["li", "neterror-not-cached-try-again"],
798       ];
799     case "nssFailure2":
800       return [
801         ["li", "neterror-nss-failure-not-verified"],
802         ["li", "neterror-nss-failure-contact-website"],
803       ];
804     case "proxyConnectFailure":
805       return [
806         ["li", "neterror-proxy-connect-failure-settings"],
807         ["li", "neterror-proxy-connect-failure-contact-admin"],
808       ];
809     case "proxyResolveFailure":
810       return [
811         ["li", "neterror-proxy-resolve-failure-settings"],
812         ["li", "neterror-proxy-resolve-failure-connection"],
813         ["li", "neterror-proxy-resolve-failure-firewall"],
814       ];
815     case "redirectLoop":
816       return [["li", "neterror-redirect-loop"]];
817     case "sslv3Used":
818       return [["span", "neterror-sslv3-used"]];
819     case "unknownProtocolFound":
820       return [["li", "neterror-unknown-protocol"]];
821     case "unknownSocketType":
822       return [
823         ["li", "neterror-unknown-socket-type-psm-installed"],
824         ["li", "neterror-unknown-socket-type-server-config"],
825       ];
826     case "unsafeContentType":
827       return [["li", "neterror-unsafe-content-type"]];
829     default:
830       return [["p", "neterror-generic-error"]];
831   }
834 function setNetErrorMessageFromCode() {
835   let errorCode;
836   try {
837     errorCode = document.getNetErrorInfo().errorCodeString;
838   } catch (ex) {
839     // We don't have a securityInfo when this is for example a DNS error.
840     return;
841   }
843   let errorMessage;
844   if (errorCode) {
845     const l10nId = errorCode.replace(/_/g, "-").toLowerCase();
846     if (KNOWN_ERROR_MESSAGE_IDS.has(l10nId)) {
847       const l10n = new Localization([ERROR_MESSAGES_FTL], true);
848       errorMessage = l10n.formatValueSync(l10nId);
849     }
851     const shortDesc2 = document.getElementById("errorShortDesc2");
852     document.l10n.setAttributes(shortDesc2, "cert-error-code-prefix", {
853       error: errorCode,
854     });
855   } else {
856     console.warn("This error page has no error code in its security info");
857   }
859   let hostname = HOST_NAME;
860   const { port } = document.location;
861   if (port && port != 443) {
862     hostname += ":" + port;
863   }
865   const shortDesc = document.getElementById("errorShortDesc");
866   document.l10n.setAttributes(shortDesc, "cert-error-ssl-connection-error", {
867     errorMessage: errorMessage ?? errorCode ?? "",
868     hostname,
869   });
872 function setupBlockingReportingUI() {
873   let checkbox = document.getElementById("automaticallyReportBlockingInFuture");
875   let reportingAutomatic = RPMGetBoolPref(
876     "security.xfocsp.errorReporting.automatic"
877   );
878   checkbox.checked = !!reportingAutomatic;
880   checkbox.addEventListener("change", function ({ target: { checked } }) {
881     RPMSetPref("security.xfocsp.errorReporting.automatic", checked);
883     // If we're enabling reports, send a report for this failure.
884     if (checked) {
885       reportBlockingError();
886     }
887   });
889   let reportingEnabled = RPMGetBoolPref(
890     "security.xfocsp.errorReporting.enabled"
891   );
893   if (reportingEnabled) {
894     // Display blocking error reporting UI for XFO error and CSP error.
895     document.getElementById("blockingErrorReporting").hidden = false;
897     if (reportingAutomatic) {
898       reportBlockingError();
899     }
900   }
903 function reportBlockingError() {
904   // We only report if we are in a frame.
905   if (window === window.top) {
906     return;
907   }
909   let err = gErrorCode;
910   // Ensure we only deal with XFO and CSP here.
911   if (!["xfoBlocked", "cspBlocked"].includes(err)) {
912     return;
913   }
915   let xfo_header = RPMGetHttpResponseHeader("X-Frame-Options");
916   let csp_header = RPMGetHttpResponseHeader("Content-Security-Policy");
918   // Extract the 'CSP: frame-ancestors' from the CSP header.
919   let reg = /(?:^|\s)frame-ancestors\s([^;]*)[$]*/i;
920   let match = reg.exec(csp_header);
921   csp_header = match ? match[1] : "";
923   // If it's the csp error page without the CSP: frame-ancestors, this means
924   // this error page is not triggered by CSP: frame-ancestors. So, we bail out
925   // early.
926   if (err === "cspBlocked" && !csp_header) {
927     return;
928   }
930   let xfoAndCspInfo = {
931     error_type: err === "xfoBlocked" ? "xfo" : "csp",
932     xfo_header,
933     csp_header,
934   };
936   // Trimming the tail colon symbol.
937   let scheme = document.location.protocol.slice(0, -1);
939   RPMSendAsyncMessage("ReportBlockingError", {
940     scheme,
941     host: document.location.host,
942     port: parseInt(document.location.port) || -1,
943     path: document.location.pathname,
944     xfoAndCspInfo,
945   });
948 function initPageCaptivePortal() {
949   document.body.className = "captiveportal";
950   document.getElementById("returnButton").hidden = true;
951   const openButton = document.getElementById("openPortalLoginPageButton");
952   openButton.hidden = false;
953   openButton.addEventListener("click", () => {
954     RPMSendAsyncMessage("Browser:OpenCaptivePortalPage");
955   });
957   setFocus("#openPortalLoginPageButton");
958   setupAdvancedButton();
959   disallowCertOverridesIfNeeded();
961   // When the portal is freed, an event is sent by the parent process
962   // that we can pick up and attempt to reload the original page.
963   RPMAddMessageListener("AboutNetErrorCaptivePortalFreed", () => {
964     document.location.reload();
965   });
968 function initPageCertError() {
969   document.body.classList.add("certerror");
971   setFocus("#returnButton");
972   setupAdvancedButton();
973   disallowCertOverridesIfNeeded();
975   const hideAddExceptionButton = RPMGetBoolPref(
976     "security.certerror.hideAddException",
977     false
978   );
979   if (hideAddExceptionButton) {
980     document.getElementById("exceptionDialogButton").hidden = true;
981   }
983   const els = document.querySelectorAll("[data-telemetry-id]");
984   for (let el of els) {
985     el.addEventListener("click", recordClickTelemetry);
986   }
988   const failedCertInfo = document.getFailedCertSecurityInfo();
989   // Truncate the error code to avoid going over the allowed
990   // string size limit for telemetry events.
991   const errorCode = failedCertInfo.errorCodeString.substring(0, 40);
992   RPMRecordTelemetryEvent(
993     "security.ui.certerror",
994     "load",
995     "aboutcerterror",
996     errorCode,
997     {
998       has_sts: gHasSts.toString(),
999       is_frame: (window.parent != window).toString(),
1000     }
1001   );
1003   setCertErrorDetails();
1006 function recordClickTelemetry(e) {
1007   let target = e.originalTarget;
1008   let telemetryId = target.dataset.telemetryId;
1009   let failedCertInfo = document.getFailedCertSecurityInfo();
1010   // Truncate the error code to avoid going over the allowed
1011   // string size limit for telemetry events.
1012   let errorCode = failedCertInfo.errorCodeString.substring(0, 40);
1013   RPMRecordTelemetryEvent(
1014     "security.ui.certerror",
1015     "click",
1016     telemetryId,
1017     errorCode,
1018     {
1019       has_sts: gHasSts.toString(),
1020       is_frame: (window.parent != window).toString(),
1021     }
1022   );
1025 function initCertErrorPageActions() {
1026   document.getElementById(
1027     "certErrorAndCaptivePortalButtonContainer"
1028   ).hidden = false;
1029   document
1030     .getElementById("returnButton")
1031     .addEventListener("click", onReturnButtonClick);
1032   document
1033     .getElementById("advancedPanelReturnButton")
1034     .addEventListener("click", onReturnButtonClick);
1035   document
1036     .getElementById("copyToClipboardTop")
1037     .addEventListener("click", copyPEMToClipboard);
1038   document
1039     .getElementById("copyToClipboardBottom")
1040     .addEventListener("click", copyPEMToClipboard);
1041   document
1042     .getElementById("exceptionDialogButton")
1043     .addEventListener("click", addCertException);
1046 function addCertException() {
1047   const isPermanent =
1048     !RPMIsWindowPrivate() &&
1049     RPMGetBoolPref("security.certerrors.permanentOverride");
1050   document.addCertException(!isPermanent).then(
1051     () => {
1052       location.reload();
1053     },
1054     err => {}
1055   );
1058 function onReturnButtonClick(e) {
1059   RPMSendAsyncMessage("Browser:SSLErrorGoBack");
1062 function copyPEMToClipboard(e) {
1063   const errorText = document.getElementById("certificateErrorText");
1064   navigator.clipboard.writeText(errorText.textContent);
1067 async function getFailedCertificatesAsPEMString() {
1068   let locationUrl = document.location.href;
1069   let failedCertInfo = document.getFailedCertSecurityInfo();
1070   let errorMessage = failedCertInfo.errorMessage;
1071   let hasHSTS = failedCertInfo.hasHSTS.toString();
1072   let hasHPKP = failedCertInfo.hasHPKP.toString();
1073   let [hstsLabel, hpkpLabel, failedChainLabel] =
1074     await document.l10n.formatValues([
1075       { id: "cert-error-details-hsts-label", args: { hasHSTS } },
1076       { id: "cert-error-details-key-pinning-label", args: { hasHPKP } },
1077       { id: "cert-error-details-cert-chain-label" },
1078     ]);
1080   let certStrings = failedCertInfo.certChainStrings;
1081   let failedChainCertificates = "";
1082   for (let der64 of certStrings) {
1083     let wrapped = der64.replace(/(\S{64}(?!$))/g, "$1\r\n");
1084     failedChainCertificates +=
1085       "-----BEGIN CERTIFICATE-----\r\n" +
1086       wrapped +
1087       "\r\n-----END CERTIFICATE-----\r\n";
1088   }
1090   let details =
1091     locationUrl +
1092     "\r\n\r\n" +
1093     errorMessage +
1094     "\r\n\r\n" +
1095     hstsLabel +
1096     "\r\n" +
1097     hpkpLabel +
1098     "\r\n\r\n" +
1099     failedChainLabel +
1100     "\r\n\r\n" +
1101     failedChainCertificates;
1102   return details;
1105 function setCertErrorDetails() {
1106   // Check if the connection is being man-in-the-middled. When the parent
1107   // detects an intercepted connection, the page may be reloaded with a new
1108   // error code (MOZILLA_PKIX_ERROR_MITM_DETECTED).
1109   const failedCertInfo = document.getFailedCertSecurityInfo();
1110   const mitmPrimingEnabled = RPMGetBoolPref(
1111     "security.certerrors.mitm.priming.enabled"
1112   );
1113   if (
1114     mitmPrimingEnabled &&
1115     failedCertInfo.errorCodeString == "SEC_ERROR_UNKNOWN_ISSUER" &&
1116     // Only do this check for top-level failures.
1117     window.parent == window
1118   ) {
1119     RPMSendAsyncMessage("Browser:PrimeMitm");
1120   }
1122   document.body.setAttribute("code", failedCertInfo.errorCodeString);
1124   const learnMore = document.getElementById("learnMoreContainer");
1125   learnMore.hidden = false;
1126   const learnMoreLink = document.getElementById("learnMoreLink");
1127   const baseURL = RPMGetFormatURLPref("app.support.baseURL");
1128   learnMoreLink.href = baseURL + "connection-not-secure";
1130   const bodyTitle = document.querySelector(".title-text");
1131   const shortDesc = document.getElementById("errorShortDesc");
1132   const shortDesc2 = document.getElementById("errorShortDesc2");
1134   let whatToDoParts = null;
1136   switch (failedCertInfo.errorCodeString) {
1137     case "SSL_ERROR_BAD_CERT_DOMAIN":
1138       whatToDoParts = [
1139         ["p", "certerror-bad-cert-domain-what-can-you-do-about-it"],
1140       ];
1141       break;
1143     case "SEC_ERROR_OCSP_INVALID_SIGNING_CERT": // FIXME - this would have thrown?
1144       break;
1146     case "SEC_ERROR_UNKNOWN_ISSUER":
1147       whatToDoParts = [
1148         ["p", "certerror-unknown-issuer-what-can-you-do-about-it-website"],
1149         [
1150           "p",
1151           "certerror-unknown-issuer-what-can-you-do-about-it-contact-admin",
1152         ],
1153       ];
1154       break;
1156     // This error code currently only exists for the Symantec distrust
1157     // in Firefox 63, so we add copy explaining that to the user.
1158     // In case of future distrusts of that scale we might need to add
1159     // additional parameters that allow us to identify the affected party
1160     // without replicating the complex logic from certverifier code.
1161     case "MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED": {
1162       document.l10n.setAttributes(
1163         shortDesc2,
1164         "cert-error-symantec-distrust-description",
1165         { hostname: HOST_NAME }
1166       );
1168       // FIXME - this does nothing
1169       const adminDesc = document.createElement("p");
1170       document.l10n.setAttributes(
1171         adminDesc,
1172         "cert-error-symantec-distrust-admin"
1173       );
1175       learnMoreLink.href = baseURL + "symantec-warning";
1176       break;
1177     }
1179     case "MOZILLA_PKIX_ERROR_MITM_DETECTED": {
1180       const autoEnabledEnterpriseRoots = RPMGetBoolPref(
1181         "security.enterprise_roots.auto-enabled",
1182         false
1183       );
1184       if (mitmPrimingEnabled && autoEnabledEnterpriseRoots) {
1185         RPMSendAsyncMessage("Browser:ResetEnterpriseRootsPref");
1186       }
1188       learnMoreLink.href = baseURL + "security-error";
1190       document.l10n.setAttributes(bodyTitle, "certerror-mitm-title");
1192       document.l10n.setAttributes(shortDesc, "certerror-mitm", {
1193         hostname: HOST_NAME,
1194         mitm: getMitmName(failedCertInfo),
1195       });
1197       const id3 = gHasSts
1198         ? "certerror-mitm-what-can-you-do-about-it-attack-sts"
1199         : "certerror-mitm-what-can-you-do-about-it-attack";
1200       whatToDoParts = [
1201         ["li", "certerror-mitm-what-can-you-do-about-it-antivirus"],
1202         ["li", "certerror-mitm-what-can-you-do-about-it-corporate"],
1203         ["li", id3, { mitm: getMitmName(failedCertInfo) }],
1204       ];
1205       break;
1206     }
1208     case "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT":
1209       learnMoreLink.href = baseURL + "security-error";
1210       break;
1212     // In case the certificate expired we make sure the system clock
1213     // matches the remote-settings service (blocklist via Kinto) ping time
1214     // and is not before the build date.
1215     case "SEC_ERROR_EXPIRED_CERTIFICATE":
1216     case "SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE":
1217     case "MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE":
1218     case "MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE": {
1219       learnMoreLink.href = baseURL + "time-errors";
1221       // We check against the remote-settings server time first if available, because that allows us
1222       // to give the user an approximation of what the correct time is.
1223       const difference = RPMGetIntPref(
1224         "services.settings.clock_skew_seconds",
1225         0
1226       );
1227       const lastFetched =
1228         RPMGetIntPref("services.settings.last_update_seconds", 0) * 1000;
1230       // This is set to true later if the user's system clock is at fault for this error.
1231       let clockSkew = false;
1233       const now = Date.now();
1234       const certRange = {
1235         notBefore: failedCertInfo.certValidityRangeNotBefore,
1236         notAfter: failedCertInfo.certValidityRangeNotAfter,
1237       };
1238       const approximateDate = now - difference * 1000;
1239       // If the difference is more than a day, we last fetched the date in the last 5 days,
1240       // and adjusting the date per the interval would make the cert valid, warn the user:
1241       if (
1242         Math.abs(difference) > 60 * 60 * 24 &&
1243         now - lastFetched <= 60 * 60 * 24 * 5 * 1000 &&
1244         certRange.notBefore < approximateDate &&
1245         certRange.notAfter > approximateDate
1246       ) {
1247         clockSkew = true;
1248         // If there is no clock skew with Kinto servers, check against the build date.
1249         // (The Kinto ping could have happened when the time was still right, or not at all)
1250       } else {
1251         const appBuildID = RPMGetAppBuildID();
1252         const year = parseInt(appBuildID.substr(0, 4), 10);
1253         const month = parseInt(appBuildID.substr(4, 2), 10) - 1;
1254         const day = parseInt(appBuildID.substr(6, 2), 10);
1256         const buildDate = new Date(year, month, day);
1258         // We don't check the notBefore of the cert with the build date,
1259         // as it is of course almost certain that it is now later than the build date,
1260         // so we shouldn't exclude the possibility that the cert has become valid
1261         // since the build date.
1262         if (buildDate > now && new Date(certRange.notAfter) > buildDate) {
1263           clockSkew = true;
1264         }
1265       }
1267       if (clockSkew) {
1268         document.body.classList.add("clockSkewError");
1269         document.l10n.setAttributes(bodyTitle, "clockSkewError-title");
1270         document.l10n.setAttributes(shortDesc, "neterror-clock-skew-error", {
1271           hostname: HOST_NAME,
1272           now,
1273         });
1274         document.getElementById("returnButton").hidden = true;
1275         document.getElementById("certErrorTryAgainButton").hidden = false;
1276         document.getElementById("advancedButton").hidden = true;
1278         document.getElementById("advancedPanelReturnButton").hidden = true;
1279         document.getElementById("advancedPanelTryAgainButton").hidden = false;
1280         document.getElementById("exceptionDialogButton").hidden = true;
1281         break;
1282       }
1284       document.l10n.setAttributes(shortDesc, "certerror-expired-cert-intro", {
1285         hostname: HOST_NAME,
1286       });
1288       // The secondary description mentions expired certificates explicitly
1289       // and should only be shown if the certificate has actually expired
1290       // instead of being not yet valid.
1291       if (failedCertInfo.errorCodeString == "SEC_ERROR_EXPIRED_CERTIFICATE") {
1292         const sd2Id = gHasSts
1293           ? "certerror-expired-cert-sts-second-para"
1294           : "certerror-expired-cert-second-para";
1295         document.l10n.setAttributes(shortDesc2, sd2Id);
1296         if (
1297           Math.abs(difference) <= 60 * 60 * 24 &&
1298           now - lastFetched <= 60 * 60 * 24 * 5 * 1000
1299         ) {
1300           whatToDoParts = [
1301             ["p", "certerror-bad-cert-domain-what-can-you-do-about-it"],
1302           ];
1303         }
1304       }
1306       whatToDoParts ??= [
1307         [
1308           "p",
1309           "certerror-expired-cert-what-can-you-do-about-it-clock",
1310           { hostname: HOST_NAME, now },
1311         ],
1312         [
1313           "p",
1314           "certerror-expired-cert-what-can-you-do-about-it-contact-website",
1315         ],
1316       ];
1317       break;
1318     }
1319   }
1321   if (whatToDoParts) {
1322     setNetErrorMessageFromParts(
1323       document.getElementById("errorWhatToDoText"),
1324       whatToDoParts
1325     );
1326     document.getElementById("errorWhatToDo").hidden = false;
1327   }
1330 async function getSubjectAltNames(failedCertInfo) {
1331   const serverCertBase64 = failedCertInfo.certChainStrings[0];
1332   const parsed = await parse(pemToDER(serverCertBase64));
1333   const subjectAltNamesExtension = parsed.ext.san;
1334   const subjectAltNames = [];
1335   if (subjectAltNamesExtension) {
1336     for (let [key, value] of subjectAltNamesExtension.altNames) {
1337       if (key === "DNS Name" && value.length) {
1338         subjectAltNames.push(value);
1339       }
1340     }
1341   }
1342   return subjectAltNames;
1345 // The optional argument is only here for testing purposes.
1346 function setTechnicalDetailsOnCertError(
1347   failedCertInfo = document.getFailedCertSecurityInfo()
1348 ) {
1349   let technicalInfo = document.getElementById("badCertTechnicalInfo");
1350   technicalInfo.textContent = "";
1352   function addLabel(l10nId, args = null, attrs = null) {
1353     let elem = document.createElement("label");
1354     technicalInfo.appendChild(elem);
1356     let newLines = document.createTextNode("\n \n");
1357     technicalInfo.appendChild(newLines);
1359     if (attrs) {
1360       let link = document.createElement("a");
1361       for (let [attr, value] of Object.entries(attrs)) {
1362         link.setAttribute(attr, value);
1363       }
1364       elem.appendChild(link);
1365     }
1367     document.l10n.setAttributes(elem, l10nId, args);
1368   }
1370   function addErrorCodeLink() {
1371     addLabel(
1372       "cert-error-code-prefix-link",
1373       { error: failedCertInfo.errorCodeString },
1374       {
1375         title: failedCertInfo.errorCodeString,
1376         id: "errorCode",
1377         "data-l10n-name": "error-code-link",
1378         "data-telemetry-id": "error_code_link",
1379         href: "#certificateErrorDebugInformation",
1380       }
1381     );
1383     // We're attaching the event listener to the parent element and not on
1384     // the errorCodeLink itself because event listeners cannot be attached
1385     // to fluent DOM overlays.
1386     technicalInfo.addEventListener("click", event => {
1387       if (event.target.id === "errorCode") {
1388         event.preventDefault();
1389         toggleCertErrorDebugInfoVisibility();
1390         recordClickTelemetry(event);
1391       }
1392     });
1393   }
1395   let hostname = HOST_NAME;
1396   const { port } = document.location;
1397   if (port && port != 443) {
1398     hostname += ":" + port;
1399   }
1401   switch (failedCertInfo.overridableErrorCategory) {
1402     case "trust-error":
1403       switch (failedCertInfo.errorCodeString) {
1404         case "MOZILLA_PKIX_ERROR_MITM_DETECTED":
1405           addLabel("cert-error-mitm-intro");
1406           addLabel("cert-error-mitm-mozilla");
1407           addLabel("cert-error-mitm-connection");
1408           break;
1409         case "SEC_ERROR_UNKNOWN_ISSUER":
1410           addLabel("cert-error-trust-unknown-issuer-intro");
1411           addLabel("cert-error-trust-unknown-issuer", { hostname });
1412           break;
1413         case "SEC_ERROR_CA_CERT_INVALID":
1414           addLabel("cert-error-intro", { hostname });
1415           addLabel("cert-error-trust-cert-invalid");
1416           break;
1417         case "SEC_ERROR_UNTRUSTED_ISSUER":
1418           addLabel("cert-error-intro", { hostname });
1419           addLabel("cert-error-trust-untrusted-issuer");
1420           break;
1421         case "SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED":
1422           addLabel("cert-error-intro", { hostname });
1423           addLabel("cert-error-trust-signature-algorithm-disabled");
1424           break;
1425         case "SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE":
1426           addLabel("cert-error-intro", { hostname });
1427           addLabel("cert-error-trust-expired-issuer");
1428           break;
1429         case "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT":
1430           addLabel("cert-error-intro", { hostname });
1431           addLabel("cert-error-trust-self-signed");
1432           break;
1433         case "MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED":
1434           addLabel("cert-error-intro", { hostname });
1435           addLabel("cert-error-trust-symantec");
1436           break;
1437         default:
1438           addLabel("cert-error-intro", { hostname });
1439           addLabel("cert-error-untrusted-default");
1440       }
1441       addErrorCodeLink();
1442       break;
1444     case "expired-or-not-yet-valid": {
1445       const notBefore = failedCertInfo.validNotBefore;
1446       const notAfter = failedCertInfo.validNotAfter;
1447       if (notBefore && Date.now() < notAfter) {
1448         addLabel("cert-error-not-yet-valid-now", {
1449           hostname,
1450           "not-before-local-time": formatter.format(new Date(notBefore)),
1451         });
1452       } else {
1453         addLabel("cert-error-expired-now", {
1454           hostname,
1455           "not-after-local-time": formatter.format(new Date(notAfter)),
1456         });
1457       }
1458       addErrorCodeLink();
1459       break;
1460     }
1462     case "domain-mismatch":
1463       getSubjectAltNames(failedCertInfo).then(subjectAltNames => {
1464         if (!subjectAltNames.length) {
1465           addLabel("cert-error-domain-mismatch", { hostname });
1466         } else if (subjectAltNames.length > 1) {
1467           const names = subjectAltNames.join(", ");
1468           addLabel("cert-error-domain-mismatch-multiple", {
1469             hostname,
1470             "subject-alt-names": names,
1471           });
1472         } else {
1473           const altName = subjectAltNames[0];
1475           // If the alt name is a wildcard domain ("*.example.com")
1476           // let's use "www" instead.  "*.example.com" isn't going to
1477           // get anyone anywhere useful. bug 432491
1478           const okHost = altName.replace(/^\*\./, "www.");
1480           // Let's check if we want to make this a link.
1481           const showLink =
1482             /* case #1:
1483              * example.com uses an invalid security certificate.
1484              *
1485              * The certificate is only valid for www.example.com
1486              *
1487              * Make sure to include the "." ahead of thisHost so that a
1488              * MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
1489              *
1490              * We'd normally just use a RegExp here except that we lack a
1491              * library function to escape them properly (bug 248062), and
1492              * domain names are famous for having '.' characters in them,
1493              * which would allow spurious and possibly hostile matches.
1494              */
1495             okHost.endsWith("." + HOST_NAME) ||
1496             /* case #2:
1497              * browser.garage.maemo.org uses an invalid security certificate.
1498              *
1499              * The certificate is only valid for garage.maemo.org
1500              */
1501             HOST_NAME.endsWith("." + okHost);
1503           const l10nArgs = { hostname, "alt-name": altName };
1504           if (showLink) {
1505             // Set the link if we want it.
1506             const proto = document.location.protocol + "//";
1507             addLabel("cert-error-domain-mismatch-single", l10nArgs, {
1508               href: proto + okHost,
1509               "data-l10n-name": "domain-mismatch-link",
1510               id: "cert_domain_link",
1511             });
1513             // If we set a link, meaning there's something helpful for
1514             // the user here, expand the section by default
1515             if (getCSSClass() != "expertBadCert") {
1516               revealAdvancedPanelSlowlyAsync();
1517             }
1518           } else {
1519             addLabel("cert-error-domain-mismatch-single-nolink", l10nArgs);
1520           }
1521         }
1522         addErrorCodeLink();
1523       });
1524       break;
1525   }
1527   getFailedCertificatesAsPEMString().then(pemString => {
1528     const errorText = document.getElementById("certificateErrorText");
1529     errorText.textContent = pemString;
1530   });
1533 /* Only focus if we're the toplevel frame; otherwise we
1534    don't want to call attention to ourselves!
1536 function setFocus(selector, position = "afterbegin") {
1537   if (window.top == window) {
1538     var button = document.querySelector(selector);
1539     button.parentNode.insertAdjacentElement(position, button);
1540     // It's possible setFocus was called via the DOMContentLoaded event
1541     // handler and that the button has no frame. Things without a frame cannot
1542     // be focused. We use a requestAnimationFrame to queue up the focus to occur
1543     // once the button has its frame.
1544     requestAnimationFrame(() => {
1545       button.focus({ focusVisible: false });
1546     });
1547   }
1550 for (let button of document.querySelectorAll(".try-again")) {
1551   button.addEventListener("click", function () {
1552     retryThis(this);
1553   });
1556 initPage();
1558 // Dispatch this event so tests can detect that we finished loading the error page.
1559 document.dispatchEvent(new CustomEvent("AboutNetErrorLoad", { bubbles: true }));