tor-browser

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

browser_aboutCertError.js (43740B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // This is testing the aboutCertError page (Bug 1207107).
      7 
      8 const GOOD_PAGE = "https://example.com/";
      9 const GOOD_PAGE_2 = "https://example.org/";
     10 const BAD_CERT = "https://expired.example.com/";
     11 const UNKNOWN_ISSUER = "https://self-signed.example.com";
     12 const BAD_STS_CERT =
     13  "https://badchain.include-subdomains.pinning.example.com:443";
     14 const { TabStateFlusher } = ChromeUtils.importESModule(
     15  "resource:///modules/sessionstore/TabStateFlusher.sys.mjs"
     16 );
     17 
     18 // Security CertError Felt Privacy set to false & true
     19 // checked in one checkReturnToAboutHome to avoid duplicating code
     20 add_task(async function checkReturnToAboutHome() {
     21  info(
     22    "Loading a bad cert page directly and making sure 'return to previous page' goes to about:home"
     23  );
     24  for (let toggleFeltPrivacy of [
     25    setSecurityCertErrorsFeltPrivacyToFalse,
     26    setSecurityCertErrorsFeltPrivacyToTrue,
     27  ]) {
     28    await toggleFeltPrivacy();
     29 
     30    for (let useFrame of [false, true]) {
     31      let tab = await openErrorPage(BAD_CERT, useFrame);
     32      let browser = tab.linkedBrowser;
     33      await SpecialPowers.spawn(browser, [], () => {
     34        content.document.notifyUserGestureActivation();
     35      });
     36 
     37      is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack");
     38      is(
     39        browser.webNavigation.canGoForward,
     40        false,
     41        "!webNavigation.canGoForward"
     42      );
     43 
     44      // Populate the shistory entries manually, since it happens asynchronously
     45      // and the following tests will be too soon otherwise.
     46      await TabStateFlusher.flush(browser);
     47      let { entries } = JSON.parse(SessionStore.getTabState(tab));
     48      is(entries.length, 1, "there is one shistory entry");
     49 
     50      info("Clicking the go back button on about:certerror");
     51      let bc = browser.browsingContext;
     52      if (useFrame) {
     53        bc = bc.children[0];
     54      }
     55      let locationChangePromise = BrowserTestUtils.waitForLocationChange(
     56        gBrowser,
     57        "about:home"
     58      );
     59 
     60      if (Services.prefs.getBoolPref("security.certerrors.felt-privacy-v1")) {
     61        info("Felt Privacy enabled - using net-error-card");
     62 
     63        await SpecialPowers.spawn(bc, [useFrame], async function (subFrame) {
     64          const netErrorCard =
     65            content.document.querySelector("net-error-card").wrappedJSObject;
     66          await netErrorCard.getUpdateComplete();
     67          const returnButton = netErrorCard.returnButton;
     68 
     69          if (!subFrame) {
     70            if (!Services.focus.focusedElement == returnButton) {
     71              await ContentTaskUtils.waitForEvent(returnButton, "focus");
     72            }
     73            Assert.ok(true, "returnButton has focus");
     74          }
     75          // Note that going back to about:newtab might cause a process flip, if
     76          // the browser is configured to run about:newtab in its own special
     77          // content process.
     78          returnButton.scrollIntoView(true);
     79          EventUtils.synthesizeMouseAtCenter(returnButton, {}, content);
     80        });
     81      } else {
     82        info("Felt Privacy disabled - using aboutNetError");
     83        await SpecialPowers.spawn(bc, [useFrame], async function (subFrame) {
     84          let returnButton = content.document.getElementById("returnButton");
     85          if (!subFrame) {
     86            if (!Services.focus.focusedElement == returnButton) {
     87              await ContentTaskUtils.waitForEvent(returnButton, "focus");
     88            }
     89            Assert.ok(true, "returnButton has focus");
     90          }
     91          // Note that going back to about:newtab might cause a process flip, if
     92          // the browser is configured to run about:newtab in its own special
     93          // content process.
     94          returnButton.click();
     95        });
     96      }
     97 
     98      await locationChangePromise;
     99 
    100      is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack");
    101      is(
    102        browser.webNavigation.canGoForward,
    103        false,
    104        "!webNavigation.canGoForward"
    105      );
    106      is(gBrowser.currentURI.spec, "about:home", "Went back");
    107 
    108      BrowserTestUtils.removeTab(gBrowser.selectedTab);
    109    }
    110    await SpecialPowers.popPrefEnv();
    111  }
    112 });
    113 
    114 // Security CertError Felt Privacy set to false only
    115 add_task(async function checkReturnToPreviousPage_feltPrivacyToFalse() {
    116  await setSecurityCertErrorsFeltPrivacyToFalse();
    117  info(
    118    "Loading a bad cert page and making sure 'return to previous page' goes back"
    119  );
    120  for (let useFrame of [false, true]) {
    121    let tab;
    122    let browser;
    123    if (useFrame) {
    124      tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE);
    125      browser = tab.linkedBrowser;
    126      await SpecialPowers.spawn(browser, [], () => {
    127        content.document.notifyUserGestureActivation();
    128      });
    129 
    130      BrowserTestUtils.startLoadingURIString(browser, GOOD_PAGE_2);
    131      await BrowserTestUtils.browserLoaded(browser, false, GOOD_PAGE_2);
    132      await injectErrorPageFrame(tab, BAD_CERT);
    133    } else {
    134      tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE);
    135      browser = gBrowser.selectedBrowser;
    136      await SpecialPowers.spawn(browser, [], () => {
    137        content.document.notifyUserGestureActivation();
    138      });
    139 
    140      info("Loading and waiting for the cert error");
    141      let certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser);
    142      BrowserTestUtils.startLoadingURIString(browser, BAD_CERT);
    143      await certErrorLoaded;
    144    }
    145 
    146    is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack");
    147    is(
    148      browser.webNavigation.canGoForward,
    149      false,
    150      "!webNavigation.canGoForward"
    151    );
    152 
    153    // Populate the shistory entries manually, since it happens asynchronously
    154    // and the following tests will be too soon otherwise.
    155    await TabStateFlusher.flush(browser);
    156    let { entries } = JSON.parse(SessionStore.getTabState(tab));
    157    is(entries.length, 2, "there are two shistory entries");
    158 
    159    info("Clicking the go back button on about:certerror");
    160    let bc = browser.browsingContext;
    161    if (useFrame) {
    162      bc = bc.children[0];
    163    }
    164 
    165    let pageShownPromise = BrowserTestUtils.waitForContentEvent(
    166      browser,
    167      "pageshow",
    168      true
    169    );
    170    await SpecialPowers.spawn(bc, [useFrame], async function () {
    171      let returnButton = content.document.getElementById("returnButton");
    172      returnButton.click();
    173    });
    174    await pageShownPromise;
    175 
    176    is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack");
    177    is(browser.webNavigation.canGoForward, true, "webNavigation.canGoForward");
    178    is(gBrowser.currentURI.spec, GOOD_PAGE, "Went back");
    179 
    180    BrowserTestUtils.removeTab(gBrowser.selectedTab);
    181  }
    182 });
    183 
    184 // Works for both Security CertError Felt Privacy set to true and false
    185 // This checks that the appinfo.appBuildID starts with a date string,
    186 // which is required for the misconfigured system time check.
    187 add_task(async function checkAppBuildIDIsDate() {
    188  let appBuildID = Services.appinfo.appBuildID;
    189  let year = parseInt(appBuildID.substr(0, 4), 10);
    190  let month = parseInt(appBuildID.substr(4, 2), 10);
    191  let day = parseInt(appBuildID.substr(6, 2), 10);
    192 
    193  Assert.ok(year >= 2016 && year <= 2100, "appBuildID contains a valid year");
    194  Assert.ok(month >= 1 && month <= 12, "appBuildID contains a valid month");
    195  Assert.ok(day >= 1 && day <= 31, "appBuildID contains a valid day");
    196 });
    197 
    198 add_task(async function checkAdvancedDetails_feltPrivacyToFalse() {
    199  await setSecurityCertErrorsFeltPrivacyToFalse();
    200  info(
    201    "Loading a bad cert page and verifying the main error and advanced details section"
    202  );
    203  for (let useFrame of [false, true]) {
    204    let tab = await openErrorPage(BAD_CERT, useFrame);
    205    let browser = tab.linkedBrowser;
    206 
    207    let bc = browser.browsingContext;
    208    if (useFrame) {
    209      bc = bc.children[0];
    210    }
    211 
    212    let message = await SpecialPowers.spawn(bc, [], async function () {
    213      let doc = content.document;
    214 
    215      const shortDesc = doc.getElementById("errorShortDesc");
    216      const sdArgs = JSON.parse(shortDesc.dataset.l10nArgs);
    217      is(
    218        sdArgs.hostname,
    219        "expired.example.com",
    220        "Should list hostname in error message."
    221      );
    222 
    223      Assert.ok(
    224        doc.getElementById("certificateErrorDebugInformation").hidden,
    225        "Debug info is initially hidden"
    226      );
    227 
    228      let exceptionButton = doc.getElementById("exceptionDialogButton");
    229      Assert.ok(
    230        !exceptionButton.disabled,
    231        "Exception button is not disabled by default."
    232      );
    233 
    234      let advancedButton = doc.getElementById("advancedButton");
    235      advancedButton.click();
    236 
    237      // Wait until fluent sets the errorCode inner text.
    238      let errorCode;
    239      await ContentTaskUtils.waitForCondition(() => {
    240        errorCode = doc.getElementById("errorCode");
    241        return errorCode && errorCode.textContent != "";
    242      }, "error code has been set inside the advanced button panel");
    243 
    244      return {
    245        textContent: errorCode.textContent,
    246        tagName: errorCode.tagName.toLowerCase(),
    247      };
    248    });
    249    is(
    250      message.textContent,
    251      "SEC_ERROR_EXPIRED_CERTIFICATE",
    252      "Correct error message found"
    253    );
    254    is(message.tagName, "a", "Error message is a link");
    255 
    256    message = await SpecialPowers.spawn(bc, [], async function () {
    257      let doc = content.document;
    258      let errorCode = doc.getElementById("errorCode");
    259      errorCode.click();
    260      let div = doc.getElementById("certificateErrorDebugInformation");
    261      let text = doc.getElementById("certificateErrorText");
    262      Assert.notStrictEqual(
    263        content.getComputedStyle(div).display,
    264        "none",
    265        "Debug information is visible"
    266      );
    267      let handshakeCertificates =
    268        content.docShell.failedChannel.securityInfo.handshakeCertificates.map(
    269          cert => cert.getBase64DERString()
    270        );
    271      return {
    272        divDisplay: content.getComputedStyle(div).display,
    273        text: text.textContent,
    274        handshakeCertificates,
    275      };
    276    });
    277    isnot(message.divDisplay, "none", "Debug information is visible");
    278    ok(message.text.includes(BAD_CERT), "Correct URL found");
    279    ok(
    280      message.text.includes("Certificate has expired"),
    281      "Correct error message found"
    282    );
    283    ok(
    284      message.text.includes("HTTP Strict Transport Security: false"),
    285      "Correct HSTS value found"
    286    );
    287    ok(
    288      message.text.includes("HTTP Public Key Pinning: false"),
    289      "Correct HPKP value found"
    290    );
    291    let certChain = getCertChainAsString(message.handshakeCertificates);
    292    ok(message.text.includes(certChain), "Found certificate chain");
    293 
    294    BrowserTestUtils.removeTab(gBrowser.selectedTab);
    295  }
    296 });
    297 
    298 add_task(async function checkAdvancedDetailsForHSTS_feltPrivacyToFalse() {
    299  await setSecurityCertErrorsFeltPrivacyToFalse();
    300  info(
    301    "Loading a bad STS cert page and verifying the advanced details section"
    302  );
    303  for (let useFrame of [false, true]) {
    304    let tab = await openErrorPage(BAD_STS_CERT, useFrame);
    305    let browser = tab.linkedBrowser;
    306 
    307    let bc = browser.browsingContext;
    308    if (useFrame) {
    309      bc = bc.children[0];
    310    }
    311 
    312    let message = await SpecialPowers.spawn(bc, [], async function () {
    313      let doc = content.document;
    314      let advancedButton = doc.getElementById("advancedButton");
    315      advancedButton.click();
    316 
    317      // Wait until fluent sets the errorCode inner text.
    318      let ec;
    319      await ContentTaskUtils.waitForCondition(() => {
    320        ec = doc.getElementById("errorCode");
    321        return ec.textContent != "";
    322      }, "error code has been set inside the advanced button panel");
    323 
    324      let cdl = doc.getElementById("cert_domain_link");
    325      return {
    326        ecTextContent: ec.textContent,
    327        ecTagName: ec.tagName.toLowerCase(),
    328        cdlTextContent: cdl.textContent,
    329        cdlTagName: cdl.tagName.toLowerCase(),
    330      };
    331    });
    332 
    333    const badStsUri = Services.io.newURI(BAD_STS_CERT);
    334    is(
    335      message.ecTextContent,
    336      "SSL_ERROR_BAD_CERT_DOMAIN",
    337      "Correct error message found"
    338    );
    339    is(message.ecTagName, "a", "Error message is a link");
    340    const url = badStsUri.prePath.slice(badStsUri.prePath.indexOf(".") + 1);
    341    is(message.cdlTextContent, url, "Correct cert_domain_link contents found");
    342    is(message.cdlTagName, "a", "cert_domain_link is a link");
    343 
    344    message = await SpecialPowers.spawn(bc, [], async function () {
    345      let doc = content.document;
    346      let errorCode = doc.getElementById("errorCode");
    347      errorCode.click();
    348      let div = doc.getElementById("certificateErrorDebugInformation");
    349      let text = doc.getElementById("certificateErrorText");
    350      let handshakeCertificates =
    351        content.docShell.failedChannel.securityInfo.handshakeCertificates.map(
    352          cert => cert.getBase64DERString()
    353        );
    354      return {
    355        divDisplay: content.getComputedStyle(div).display,
    356        text: text.textContent,
    357        handshakeCertificates,
    358      };
    359    });
    360    isnot(message.divDisplay, "none", "Debug information is visible");
    361    ok(message.text.includes(badStsUri.spec), "Correct URL found");
    362    ok(
    363      message.text.includes(
    364        "requested domain name does not match the server\u2019s certificate"
    365      ),
    366      "Correct error message found"
    367    );
    368    ok(
    369      message.text.includes("HTTP Strict Transport Security: false"),
    370      "Correct HSTS value found"
    371    );
    372    ok(
    373      message.text.includes("HTTP Public Key Pinning: true"),
    374      "Correct HPKP value found"
    375    );
    376    let certChain = getCertChainAsString(message.handshakeCertificates);
    377    ok(message.text.includes(certChain), "Found certificate chain");
    378 
    379    BrowserTestUtils.removeTab(gBrowser.selectedTab);
    380  }
    381 });
    382 
    383 add_task(async function checkUnknownIssuerLearnMoreLink_feltPrivacyToFalse() {
    384  await setSecurityCertErrorsFeltPrivacyToFalse();
    385  info(
    386    "Loading a cert error for self-signed pages and checking the correct link is shown"
    387  );
    388  for (let useFrame of [false, true]) {
    389    let tab = await openErrorPage(UNKNOWN_ISSUER, useFrame);
    390    let browser = tab.linkedBrowser;
    391 
    392    let bc = browser.browsingContext;
    393    if (useFrame) {
    394      bc = bc.children[0];
    395    }
    396 
    397    let href = await SpecialPowers.spawn(bc, [], async function () {
    398      let learnMoreLink = content.document.getElementById("learnMoreLink");
    399      return learnMoreLink.href;
    400    });
    401    ok(href.endsWith("security-error"), "security-error in the Learn More URL");
    402 
    403    BrowserTestUtils.removeTab(gBrowser.selectedTab);
    404  }
    405 });
    406 
    407 add_task(async function checkViewCertificate_feltPrivacyToFalse() {
    408  await setSecurityCertErrorsFeltPrivacyToFalse();
    409  info("Loading a cert error and checking that the certificate can be shown.");
    410  for (let useFrame of [true, false]) {
    411    if (useFrame) {
    412      // Bug #1573502
    413      continue;
    414    }
    415    let tab = await openErrorPage(UNKNOWN_ISSUER, useFrame);
    416    let browser = tab.linkedBrowser;
    417 
    418    let bc = browser.browsingContext;
    419    if (useFrame) {
    420      bc = bc.children[0];
    421    }
    422 
    423    let loaded = BrowserTestUtils.waitForNewTab(gBrowser, null, true);
    424    await SpecialPowers.spawn(bc, [], async function () {
    425      let viewCertificate = content.document.getElementById("viewCertificate");
    426      viewCertificate.click();
    427    });
    428    await loaded;
    429 
    430    let spec = gBrowser.currentURI.spec;
    431    Assert.ok(
    432      spec.startsWith("about:certificate"),
    433      "about:certificate is the new opened tab"
    434    );
    435 
    436    await SpecialPowers.spawn(
    437      gBrowser.selectedTab.linkedBrowser,
    438      [],
    439      async function () {
    440        let doc = content.document;
    441        let certificateSection = await ContentTaskUtils.waitForCondition(() => {
    442          return doc.querySelector("certificate-section");
    443        }, "Certificate section found");
    444 
    445        let infoGroup =
    446          certificateSection.shadowRoot.querySelector("info-group");
    447        Assert.ok(infoGroup, "infoGroup found");
    448 
    449        let items = infoGroup.shadowRoot.querySelectorAll("info-item");
    450        let commonnameID = items[items.length - 1].shadowRoot
    451          .querySelector("label")
    452          .getAttribute("data-l10n-id");
    453        Assert.equal(
    454          commonnameID,
    455          "certificate-viewer-common-name",
    456          "The correct item was selected"
    457        );
    458 
    459        let commonnameValue =
    460          items[items.length - 1].shadowRoot.querySelector(".info").textContent;
    461        Assert.equal(
    462          commonnameValue,
    463          "self-signed.example.com",
    464          "Shows the correct certificate in the page"
    465        );
    466      }
    467    );
    468    BrowserTestUtils.removeTab(gBrowser.selectedTab); // closes about:certificate
    469    BrowserTestUtils.removeTab(gBrowser.selectedTab);
    470  }
    471 });
    472 
    473 add_task(async function checkBadStsCertHeadline_feltPrivacyToFalse() {
    474  await setSecurityCertErrorsFeltPrivacyToFalse();
    475  info(
    476    "Loading a bad sts cert error page and checking that the correct headline is shown"
    477  );
    478  for (let useFrame of [false, true]) {
    479    let tab = await openErrorPage(BAD_CERT, useFrame);
    480    let browser = tab.linkedBrowser;
    481 
    482    let bc = browser.browsingContext;
    483    if (useFrame) {
    484      bc = bc.children[0];
    485    }
    486 
    487    await SpecialPowers.spawn(bc, [useFrame], async _useFrame => {
    488      const titleText = content.document.querySelector(".title-text");
    489      is(
    490        titleText.dataset.l10nId,
    491        _useFrame ? "nssBadCert-sts-title" : "nssBadCert-title",
    492        "Error page title is set"
    493      );
    494    });
    495    BrowserTestUtils.removeTab(gBrowser.selectedTab);
    496  }
    497 });
    498 
    499 add_task(async function checkSandboxedIframe_feltPrivacyToFalse() {
    500  await setSecurityCertErrorsFeltPrivacyToFalse();
    501  info(
    502    "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown"
    503  );
    504  let useFrame = true;
    505  let sandboxed = true;
    506  let tab = await openErrorPage(BAD_CERT, useFrame, sandboxed);
    507  let browser = tab.linkedBrowser;
    508 
    509  let bc = browser.browsingContext.children[0];
    510  await SpecialPowers.spawn(bc, [], async function () {
    511    let doc = content.document;
    512 
    513    const titleText = doc.querySelector(".title-text");
    514    is(
    515      titleText.dataset.l10nId,
    516      "nssBadCert-sts-title",
    517      "Title shows Did Not Connect: Potential Security Issue"
    518    );
    519 
    520    const errorLabel = doc.querySelector(
    521      '[data-l10n-id="cert-error-code-prefix-link"]'
    522    );
    523    const elArgs = JSON.parse(errorLabel.dataset.l10nArgs);
    524    is(
    525      elArgs.error,
    526      "SEC_ERROR_EXPIRED_CERTIFICATE",
    527      "Correct error message found"
    528    );
    529    is(
    530      doc.getElementById("errorCode").tagName.toLowerCase(),
    531      "a",
    532      "Error message contains a link"
    533    );
    534  });
    535  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    536 });
    537 
    538 add_task(async function checkViewSource_feltPrivacyToFalse() {
    539  await setSecurityCertErrorsFeltPrivacyToFalse();
    540  info(
    541    "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown"
    542  );
    543  let uri = "view-source:" + BAD_CERT;
    544  let tab = await openErrorPage(uri);
    545  let browser = tab.linkedBrowser;
    546 
    547  await SpecialPowers.spawn(browser, [], async function () {
    548    let doc = content.document;
    549 
    550    const errorLabel = doc.querySelector(
    551      '[data-l10n-id="cert-error-code-prefix-link"]'
    552    );
    553    const elArgs = JSON.parse(errorLabel.dataset.l10nArgs);
    554    is(
    555      elArgs.error,
    556      "SEC_ERROR_EXPIRED_CERTIFICATE",
    557      "Correct error message found"
    558    );
    559    is(
    560      doc.getElementById("errorCode").tagName.toLowerCase(),
    561      "a",
    562      "Error message contains a link"
    563    );
    564 
    565    const titleText = doc.querySelector(".title-text");
    566    is(titleText.dataset.l10nId, "nssBadCert-title", "Error page title is set");
    567 
    568    const shortDesc = doc.getElementById("errorShortDesc");
    569    const sdArgs = JSON.parse(shortDesc.dataset.l10nArgs);
    570    is(
    571      sdArgs.hostname,
    572      "expired.example.com",
    573      "Should list hostname in error message."
    574    );
    575  });
    576 
    577  let loaded = BrowserTestUtils.browserLoaded(browser, false, uri);
    578  info("Clicking the exceptionDialogButton in advanced panel");
    579  await SpecialPowers.spawn(browser, [], async function () {
    580    let doc = content.document;
    581    let exceptionButton = doc.getElementById("exceptionDialogButton");
    582    exceptionButton.click();
    583  });
    584 
    585  info("Loading the url after adding exception");
    586  await loaded;
    587 
    588  await SpecialPowers.spawn(browser, [], async function () {
    589    let doc = content.document;
    590    ok(
    591      !doc.documentURI.startsWith("about:certerror"),
    592      "Exception has been added"
    593    );
    594  });
    595 
    596  let certOverrideService = Cc[
    597    "@mozilla.org/security/certoverride;1"
    598  ].getService(Ci.nsICertOverrideService);
    599  certOverrideService.clearValidityOverride("expired.example.com", -1, {});
    600 
    601  loaded = BrowserTestUtils.waitForErrorPage(browser);
    602  BrowserCommands.reloadSkipCache();
    603  await loaded;
    604 
    605  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    606 });
    607 
    608 add_task(async function testCertificateTransparency() {
    609  info(
    610    "Test that when certificate transparency is enforced, the right error page is shown."
    611  );
    612  // Enforce certificate transparency for certificates issued by our test root.
    613  // This is only possible in debug builds, hence skipping this test in
    614  // non-debug builds (see below).
    615  await SpecialPowers.pushPrefEnv({
    616    set: [
    617      ["security.pki.certificate_transparency.mode", 2],
    618      [
    619        "security.test.built_in_root_hash",
    620        "8D:9D:57:09:E5:7D:D5:C6:4B:BE:24:70:E9:E5:BF:FF:16:F6:F2:C2:49:4E:0F:B9:37:1C:DD:3A:0E:10:45:F4",
    621      ],
    622    ],
    623  });
    624 
    625  for (let useFrame of [false, true]) {
    626    let tab;
    627    let browser;
    628    if (useFrame) {
    629      tab = await BrowserTestUtils.openNewForegroundTab(
    630        gBrowser,
    631        "about:blank"
    632      );
    633      browser = tab.linkedBrowser;
    634 
    635      // injectErrorPageFrame is a helper from head.js for this purpose
    636      await injectErrorPageFrame(tab, GOOD_PAGE, useFrame);
    637    } else {
    638      tab = await openErrorPage(GOOD_PAGE, useFrame);
    639      browser = tab.linkedBrowser;
    640    }
    641 
    642    let bc = browser.browsingContext;
    643    if (useFrame) {
    644      bc = bc.children[0];
    645    }
    646 
    647    let message = await SpecialPowers.spawn(bc, [], async function () {
    648      const doc = content.document;
    649      const shortDesc = doc.getElementById("errorShortDesc");
    650      const sdArgs = JSON.parse(shortDesc.dataset.l10nArgs);
    651      Assert.equal(
    652        sdArgs.hostname,
    653        "example.com",
    654        "Should list hostname in error message."
    655      );
    656      const advancedButton = doc.getElementById("advancedButton");
    657      advancedButton.scrollIntoView(true);
    658      EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content);
    659 
    660      // Wait until fluent sets the errorCode inner text.
    661      let errorCode;
    662      await ContentTaskUtils.waitForCondition(() => {
    663        errorCode = doc.getElementById("errorCode");
    664        return errorCode && errorCode.textContent != "";
    665      }, "error code has been set inside the advanced button panel");
    666 
    667      return {
    668        textContent: errorCode.textContent,
    669        tagName: errorCode.tagName.toLowerCase(),
    670      };
    671    });
    672 
    673    Assert.equal(
    674      message.textContent,
    675      "MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY",
    676      "Correct error message found"
    677    );
    678    Assert.equal(message.tagName, "a", "Error message is a link");
    679 
    680    message = await SpecialPowers.spawn(bc, [], async function () {
    681      const doc = content.document;
    682      const errorCode = doc.getElementById("errorCode");
    683      errorCode.scrollIntoView(true);
    684      EventUtils.synthesizeMouseAtCenter(errorCode, {}, content);
    685      const text = doc.getElementById("certificateErrorText");
    686      return {
    687        text: text.textContent,
    688      };
    689    });
    690    Assert.ok(message.text.includes(GOOD_PAGE), "Correct URL found");
    691 
    692    BrowserTestUtils.removeTab(gBrowser.selectedTab);
    693    BrowserTestUtils.removeTab(tab);
    694  }
    695 
    696  await SpecialPowers.popPrefEnv();
    697 
    698  // Certificate transparency can only be enforced for our test certificates in
    699  // debug builds.
    700 }).skip(!AppConstants.DEBUG);
    701 
    702 add_task(async function testCertificateTransparency_feltPrivacyTrue() {
    703  info(
    704    "Test that when certificate transparency is enforced, the right error page is shown."
    705  );
    706  // Enforce certificate transparency for certificates issued by our test root.
    707  // This is only possible in debug builds, hence skipping this test in
    708  // non-debug builds (see below).
    709  await SpecialPowers.pushPrefEnv({
    710    set: [
    711      ["security.pki.certificate_transparency.mode", 2],
    712      [
    713        "security.test.built_in_root_hash",
    714        "8D:9D:57:09:E5:7D:D5:C6:4B:BE:24:70:E9:E5:BF:FF:16:F6:F2:C2:49:4E:0F:B9:37:1C:DD:3A:0E:10:45:F4",
    715      ],
    716      ["security.certerrors.felt-privacy-v1", true],
    717    ],
    718  });
    719 
    720  for (let useFrame of [false, true]) {
    721    const tab = await openErrorPage(GOOD_PAGE, useFrame);
    722    const browser = tab.linkedBrowser;
    723 
    724    let bc = browser.browsingContext;
    725    if (useFrame) {
    726      bc = bc.children[0];
    727    }
    728 
    729    const message = await SpecialPowers.spawn(bc, [], async function () {
    730      const doc = content.document;
    731 
    732      const netErrorCard = doc.querySelector("net-error-card").wrappedJSObject;
    733      await netErrorCard.getUpdateComplete();
    734 
    735      netErrorCard.advancedButton.scrollIntoView();
    736      EventUtils.synthesizeMouseAtCenter(
    737        netErrorCard.advancedButton,
    738        {},
    739        content
    740      );
    741 
    742      await ContentTaskUtils.waitForCondition(() => {
    743        return (
    744          netErrorCard.whyDangerous &&
    745          netErrorCard.whyDangerous.textContent != ""
    746        );
    747      }, "Waiting for why dangerous text");
    748      const args = JSON.parse(netErrorCard.whyDangerous.dataset.l10nArgs);
    749      Assert.strictEqual(
    750        args.hostname,
    751        "example.com",
    752        "Should list hostname in error message."
    753      );
    754 
    755      // Wait until fluent sets the errorCode inner text.
    756      await ContentTaskUtils.waitForCondition(() => {
    757        return (
    758          netErrorCard.errorCode && netErrorCard.errorCode.textContent != ""
    759        );
    760      }, "error code has been set inside the advanced button panel");
    761 
    762      netErrorCard.errorCode.scrollIntoView();
    763      EventUtils.synthesizeMouseAtCenter(netErrorCard.errorCode, {}, content);
    764      await ContentTaskUtils.waitForCondition(() => {
    765        return (
    766          netErrorCard.certErrorText &&
    767          netErrorCard.certErrorText.textContent != ""
    768        );
    769      }, "Certificate Error is showing");
    770 
    771      return {
    772        certText: netErrorCard.certErrorText.textContent,
    773        errorCode: netErrorCard.errorCode.textContent,
    774        tagName: netErrorCard.errorCode.tagName.toLowerCase(),
    775      };
    776    });
    777 
    778    Assert.ok(
    779      message.errorCode.includes(
    780        "MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY"
    781      ),
    782      "Correct error message found"
    783    );
    784    Assert.strictEqual(message.tagName, "a", "Error message is a link");
    785    Assert.ok(message.certText.includes(GOOD_PAGE), "Correct URL found");
    786 
    787    BrowserTestUtils.removeTab(gBrowser.selectedTab);
    788  }
    789 
    790  await SpecialPowers.popPrefEnv();
    791 
    792  // Certificate transparency can only be enforced for our test certificates in
    793  // debug builds.
    794 }).skip(!AppConstants.DEBUG);
    795 
    796 /**
    797 * A reusable helper that runs assertions on a network error page.
    798 * It encapsulates the SpecialPowers.spawn call to be CSP-compliant.
    799 *
    800 * @param {object} params - Parameters for the assertion.
    801 * @param {string} params.expectedUrl - The URL to load and check.
    802 * @param {string} params.expectedHostname - The expected hostname to assert in the error page.
    803 * @param {string} params.expectedErrorCode - The expected error code to assert.
    804 * @param {string} params.expectedInfo - Info string for logging.
    805 * @param {string} params.expectedErrorMessage - Error message to assert in the error text.
    806 * @param {boolean} params.expectedHpkp - HPKP value to assert in the error text.
    807 */
    808 async function assertNetErrorPage({
    809  expectedUrl,
    810  expectedHostname,
    811  expectedErrorCode,
    812  expectedInfo,
    813  expectedErrorMessage,
    814  expectedHpkp,
    815 }) {
    816  await setSecurityCertErrorsFeltPrivacyToTrue();
    817  info(`${expectedInfo}`);
    818 
    819  for (let useFrame of [false, true]) {
    820    let tab = await openErrorPage(expectedUrl, useFrame);
    821    let browser = tab.linkedBrowser;
    822    let bc = browser.browsingContext;
    823    if (useFrame) {
    824      bc = bc.children[0];
    825    }
    826 
    827    const newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true);
    828    const contentData = await SpecialPowers.spawn(
    829      bc,
    830      [expectedHostname, expectedErrorCode],
    831      async function (hostname, errorCode) {
    832        const netErrorCard =
    833          content.document.querySelector("net-error-card").wrappedJSObject;
    834        await netErrorCard.getUpdateComplete();
    835 
    836        // Assert Error Card Basics
    837        Assert.ok(
    838          netErrorCard.certErrorBodyTitle,
    839          "The error page title should exist."
    840        );
    841 
    842        const shortDesc = netErrorCard.certErrorIntro;
    843        const shortDescArgs = JSON.parse(shortDesc.dataset.l10nArgs);
    844        Assert.equal(
    845          shortDescArgs.hostname,
    846          hostname,
    847          "Should list hostname in error message."
    848        );
    849 
    850        // Assert Advanced button
    851        Assert.ok(
    852          !netErrorCard.advancedContainer,
    853          "The Advanced container should NOT be found in shadow DOM before click."
    854        );
    855        const advancedButton = netErrorCard.advancedButton;
    856        Assert.ok(advancedButton, "The advanced button should exist.");
    857        Assert.equal(
    858          advancedButton.dataset.l10nId,
    859          "fp-certerror-advanced-button",
    860          "Button should have the 'advanced' l10n ID."
    861        );
    862 
    863        advancedButton.scrollIntoView(true);
    864        EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content);
    865        await ContentTaskUtils.waitForCondition(
    866          () => netErrorCard.advancedContainer,
    867          "Wait for the advanced container."
    868        );
    869 
    870        const hideExceptionButton = netErrorCard.shouldHideExceptionButton();
    871        if (!hideExceptionButton) {
    872          await ContentTaskUtils.waitForCondition(
    873            () =>
    874              netErrorCard.exceptionButton &&
    875              !netErrorCard.exceptionButton.disabled,
    876            "Wait for the exception button to be created."
    877          );
    878 
    879          Assert.ok(
    880            !netErrorCard.exceptionButton.disabled,
    881            "The exception button is now enabled."
    882          );
    883        }
    884 
    885        Assert.equal(
    886          advancedButton.dataset.l10nId,
    887          "fp-certerror-hide-advanced-button",
    888          "Button should have the 'hide-advanced' l10n ID."
    889        );
    890        Assert.ok(
    891          netErrorCard.advancedShowing,
    892          "Advanced showing attribute should be true"
    893        );
    894 
    895        // Assert Error Code
    896        const certErrorCodeLink = netErrorCard.errorCode;
    897        Assert.equal(
    898          certErrorCodeLink.textContent,
    899          `Error Code: ${errorCode}`,
    900          "Error code text is as expected"
    901        );
    902        Assert.equal(
    903          certErrorCodeLink.tagName.toLowerCase(),
    904          "a",
    905          "Error code is a link"
    906        );
    907 
    908        certErrorCodeLink.scrollIntoView(true);
    909        EventUtils.synthesizeMouseAtCenter(certErrorCodeLink, {}, content);
    910        await ContentTaskUtils.waitForMutationCondition(
    911          netErrorCard,
    912          { attributeFilter: ["certErrorDebugInfoShowing"] },
    913          () => netErrorCard.certErrorDebugInfoShowing
    914        );
    915        Assert.ok(
    916          netErrorCard.certErrorDebugInfoShowing,
    917          "The 'certErrorDebugInfoShowing' boolean should be toggled (to true) after Advance button click on assertAdvancedButton."
    918        );
    919        Assert.ok(netErrorCard.certErrorText, "Error Code Detail should exist");
    920 
    921        // Assert Site Certificate
    922        info("Clicking the View Certificate button in advanced panel");
    923        netErrorCard.viewCertificate.scrollIntoView(true);
    924        EventUtils.synthesizeMouseAtCenter(
    925          netErrorCard.viewCertificate,
    926          {},
    927          content
    928        );
    929 
    930        // Extract data needed by the parent process
    931        const failedCertChain =
    932          content.docShell.failedChannel.securityInfo.handshakeCertificates.map(
    933            cert => cert.getBase64DERString()
    934          );
    935 
    936        return {
    937          errorText: netErrorCard.certErrorText.textContent,
    938          rawCertChain: failedCertChain,
    939        };
    940      }
    941    );
    942 
    943    Assert.ok(
    944      contentData.errorText.includes(expectedHostname),
    945      "Correct URL found"
    946    );
    947    Assert.ok(
    948      contentData.errorText.includes(expectedErrorMessage),
    949      "Correct error message exists"
    950    );
    951    Assert.ok(
    952      contentData.errorText.includes("HTTP Strict Transport Security: false"),
    953      "Correct HSTS value exists"
    954    );
    955    Assert.ok(
    956      contentData.errorText.includes(
    957        `HTTP Public Key Pinning: ${expectedHpkp}`
    958      ),
    959      "Correct HPKP value exists"
    960    );
    961 
    962    info("Loading the about:certificate page");
    963    let newTab = await newTabPromise;
    964 
    965    // Parent process checking if certificate viewer opened
    966    const spec = gBrowser.currentURI.spec;
    967    Assert.ok(
    968      spec.startsWith("about:certificate"),
    969      "about:certificate is the new opened tab"
    970    );
    971 
    972    await SpecialPowers.spawn(
    973      gBrowser.selectedTab.linkedBrowser,
    974      [expectedHostname],
    975      async function (hostname) {
    976        const doc = content.document;
    977        const certificateSection = await ContentTaskUtils.waitForCondition(
    978          () => {
    979            // Content process checking if we're no longer on the error page
    980            Assert.ok(
    981              !doc.documentURI.startsWith("about:certerror"),
    982              "We are now in a new tab with content including: about:certificate"
    983            );
    984            return doc.querySelector("certificate-section");
    985          },
    986          "Certificate section found"
    987        );
    988 
    989        const infoGroup =
    990          certificateSection.shadowRoot.querySelector("info-group");
    991        Assert.ok(infoGroup, "infoGroup found");
    992 
    993        const items = infoGroup.shadowRoot.querySelectorAll("info-item");
    994        const commonNameID = items[items.length - 1].shadowRoot
    995          .querySelector("label")
    996          .getAttribute("data-l10n-id");
    997        Assert.equal(
    998          commonNameID,
    999          "certificate-viewer-common-name",
   1000          "The correct item was selected"
   1001        );
   1002 
   1003        const commonNameValue =
   1004          items[items.length - 1].shadowRoot.querySelector(".info").textContent;
   1005 
   1006        // Bug 1992278
   1007        // Structuring the logic this way avoids issue to be addressed
   1008        // in most cases the values match, "self-signed.example.com", "self-signed.example.com"
   1009        // in the case of pinning, the commonNameValue is shorter, "pinning-test.example.com",
   1010        // than the hostname "badchain.include-subdomains.pinning.example.com"
   1011        // as a temporary measure I have reversed the logic such that hostname includes commonNameValue
   1012        Assert.ok(
   1013          hostname.includes(commonNameValue),
   1014          "Error text should include the expected hostname"
   1015        );
   1016      }
   1017    );
   1018 
   1019    BrowserTestUtils.removeTab(newTab);
   1020    BrowserTestUtils.removeTab(tab);
   1021  }
   1022 }
   1023 
   1024 /**
   1025 * A reusable helper that runs assertions on a view-source network error page.
   1026 * It encapsulates the SpecialPowers.spawn call to be CSP-compliant.
   1027 *
   1028 * @param {object} params - Parameters for the assertion.
   1029 * @param {string} params.expectedHostname - The expected hostname to assert in the error page.
   1030 * @param {string} params.expectedUrl - The URL to load and check.
   1031 */
   1032 async function assertViewSourceNetErrorPage({
   1033  expectedHostname,
   1034  expectedUrl,
   1035  expectedInfo,
   1036 }) {
   1037  await setSecurityCertErrorsFeltPrivacyToTrue();
   1038  info(`${expectedInfo}`);
   1039 
   1040  let tab = await openErrorPage(expectedUrl);
   1041  const browser = tab.linkedBrowser;
   1042  const loaded = BrowserTestUtils.browserLoaded(browser, false, expectedUrl);
   1043 
   1044  await SpecialPowers.spawn(browser, [], async function () {
   1045    const netErrorCard =
   1046      content.document.querySelector("net-error-card").wrappedJSObject;
   1047    await netErrorCard.getUpdateComplete();
   1048    // Advanced button
   1049    const advancedButton = netErrorCard.advancedButton;
   1050    advancedButton.scrollIntoView(true);
   1051    EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content);
   1052    await ContentTaskUtils.waitForCondition(
   1053      () =>
   1054        netErrorCard.exceptionButton && !netErrorCard.exceptionButton.disabled,
   1055      "Wait for the exception button to be created."
   1056    );
   1057 
   1058    info("Clicking the Proceed Risky button in advanced panel");
   1059    netErrorCard.exceptionButton.scrollIntoView(true);
   1060    EventUtils.synthesizeMouseAtCenter(
   1061      netErrorCard.exceptionButton,
   1062      {},
   1063      content
   1064    );
   1065  });
   1066 
   1067  info("Loading the url after proceeding with risk");
   1068  await loaded;
   1069 
   1070  // Clean up the cert override
   1071  const certOverrideService = Cc[
   1072    "@mozilla.org/security/certoverride;1"
   1073  ].getService(Ci.nsICertOverrideService);
   1074  certOverrideService.clearValidityOverride(expectedHostname, -1, {});
   1075 
   1076  // To ensure the state is reset, reload and wait for the error page to return.
   1077  info("Reloading to ensure the certificate override was cleared.");
   1078  const errorPageLoaded = BrowserTestUtils.waitForErrorPage(browser);
   1079 
   1080  BrowserCommands.reloadSkipCache();
   1081  await errorPageLoaded;
   1082 
   1083  info("Override cleared and error page is shown again.");
   1084  BrowserTestUtils.removeTab(tab);
   1085 }
   1086 
   1087 // Security CertError Felt Privacy set to true only
   1088 add_task(async function checkReturnToPreviousPage_feltPrivacyToTrue() {
   1089  await setSecurityCertErrorsFeltPrivacyToTrue();
   1090  info(
   1091    "Loading a bad cert page and making sure 'return to previous page' goes back"
   1092  );
   1093  for (let useFrame of [false, true]) {
   1094    let tab;
   1095    let browser;
   1096    if (useFrame) {
   1097      tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE);
   1098      browser = tab.linkedBrowser;
   1099      await SpecialPowers.spawn(browser, [], () => {
   1100        content.document.notifyUserGestureActivation();
   1101      });
   1102 
   1103      BrowserTestUtils.startLoadingURIString(browser, GOOD_PAGE_2);
   1104      await BrowserTestUtils.browserLoaded(browser, false, GOOD_PAGE_2);
   1105      await injectErrorPageFrame(tab, BAD_CERT);
   1106    } else {
   1107      tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE);
   1108      browser = gBrowser.selectedBrowser;
   1109      await SpecialPowers.spawn(browser, [], () => {
   1110        content.document.notifyUserGestureActivation();
   1111      });
   1112 
   1113      info("Loading and waiting for the cert error");
   1114      const certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser);
   1115      BrowserTestUtils.startLoadingURIString(browser, BAD_CERT);
   1116      await certErrorLoaded;
   1117    }
   1118 
   1119    Assert.ok(browser.webNavigation.canGoBack, "webNavigation.canGoBack");
   1120    Assert.ok(
   1121      !browser.webNavigation.canGoForward,
   1122      "!webNavigation.canGoForward"
   1123    );
   1124 
   1125    // Populate the shistory entries manually, since it happens asynchronously
   1126    // and the following tests will be too soon otherwise.
   1127    await TabStateFlusher.flush(browser);
   1128    const { entries } = JSON.parse(SessionStore.getTabState(tab));
   1129    Assert.equal(entries.length, 2, "there are two history entries");
   1130 
   1131    info("Clicking the go back button on about:certerror");
   1132    let bc = browser.browsingContext;
   1133    if (useFrame) {
   1134      bc = bc.children[0];
   1135    }
   1136 
   1137    const pageShownPromise = BrowserTestUtils.waitForContentEvent(
   1138      browser,
   1139      "pageshow",
   1140      true
   1141    );
   1142    await SpecialPowers.spawn(bc, [useFrame], async function () {
   1143      const netErrorCard =
   1144        content.document.querySelector("net-error-card").wrappedJSObject;
   1145      await netErrorCard.getUpdateComplete();
   1146      const returnButton = netErrorCard.returnButton;
   1147      returnButton.scrollIntoView(true);
   1148      EventUtils.synthesizeMouseAtCenter(returnButton, {}, content);
   1149    });
   1150    await pageShownPromise;
   1151 
   1152    Assert.ok(!browser.webNavigation.canGoBack, "!webNavigation.canGoBack");
   1153    Assert.ok(browser.webNavigation.canGoForward, "webNavigation.canGoForward");
   1154    Assert.equal(gBrowser.currentURI.spec, GOOD_PAGE, "Went back");
   1155 
   1156    BrowserTestUtils.removeTab(gBrowser.selectedTab);
   1157  }
   1158 });
   1159 
   1160 add_task(async function checkAdvancedDetails_feltPrivacyToTrue() {
   1161  // Helper function does all the content-side checks.
   1162  await assertNetErrorPage({
   1163    expectedUrl: BAD_CERT,
   1164    expectedHostname: new URL(BAD_CERT).hostname,
   1165    expectedErrorCode: "SEC_ERROR_EXPIRED_CERTIFICATE",
   1166    expectedInfo:
   1167      "Loading a bad cert page and verifying the main error and advanced details section",
   1168    expectedErrorMessage: "Certificate has expired",
   1169    expectedHpkp: false,
   1170  });
   1171 });
   1172 
   1173 add_task(async function checkAdvancedDetailsForHSTS_feltPrivacyToTrue() {
   1174  // Helper function does all the content-side checks.
   1175  await assertNetErrorPage({
   1176    expectedUrl: BAD_STS_CERT,
   1177    expectedHostname: new URL(BAD_STS_CERT).hostname,
   1178    expectedErrorCode: "SSL_ERROR_BAD_CERT_DOMAIN",
   1179    expectedInfo:
   1180      "Loading a bad STS cert page and verifying the advanced details section",
   1181    expectedErrorMessage:
   1182      "requested domain name does not match the server\u2019s certificate", // Expected error message
   1183    expectedHpkp: true,
   1184  });
   1185 });
   1186 
   1187 add_task(async function checkUnknownIssuerDetails_feltPrivacyToTrue() {
   1188  // Helper function does all the content-side checks.
   1189  await assertNetErrorPage({
   1190    expectedUrl: UNKNOWN_ISSUER,
   1191    expectedHostname: new URL(UNKNOWN_ISSUER).hostname,
   1192    expectedErrorCode: "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT",
   1193    expectedInfo:
   1194      "Loading a cert error for self-signed pages and checking the correct link is shown",
   1195    expectedErrorMessage:
   1196      "The certificate is not trusted because it is self-signed.",
   1197    expectedHpkp: false,
   1198  });
   1199 });
   1200 
   1201 add_task(async function checkViewSource_feltPrivacyToTrue() {
   1202  await assertViewSourceNetErrorPage({
   1203    expectedHostname: new URL(BAD_CERT).hostname,
   1204    expectedUrl: "view-source:" + BAD_CERT,
   1205    expectedInfo:
   1206      "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown",
   1207  });
   1208 });
   1209 
   1210 add_task(async function checkSandboxedIframe_feltPrivacyToTrue() {
   1211  await setSecurityCertErrorsFeltPrivacyToTrue();
   1212  info(
   1213    "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown"
   1214  );
   1215  let useFrame = true;
   1216  let sandboxed = true;
   1217  let tab = await openErrorPage(BAD_CERT, useFrame, sandboxed);
   1218  let browser = tab.linkedBrowser;
   1219 
   1220  let bc = browser.browsingContext.children[0];
   1221  await SpecialPowers.spawn(bc, [], async function () {
   1222    const netErrorCard =
   1223      content.document.querySelector("net-error-card").wrappedJSObject;
   1224    await netErrorCard.getUpdateComplete();
   1225 
   1226    // Assert Error Card Basics
   1227    Assert.ok(
   1228      netErrorCard.certErrorBodyTitle,
   1229      "The error page title should exist."
   1230    );
   1231    const advancedButton = netErrorCard.advancedButton;
   1232    advancedButton.scrollIntoView(true);
   1233    EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content);
   1234    await ContentTaskUtils.waitForCondition(
   1235      () => netErrorCard.advancedContainer,
   1236      "Wait for the advanced container."
   1237    );
   1238 
   1239    const hideExceptionButton = netErrorCard.shouldHideExceptionButton();
   1240    if (!hideExceptionButton) {
   1241      await ContentTaskUtils.waitForCondition(
   1242        () =>
   1243          netErrorCard.exceptionButton &&
   1244          !netErrorCard.exceptionButton.disabled,
   1245        "Wait for the exception button to be created."
   1246      );
   1247    }
   1248 
   1249    // Assert Error Code
   1250    const certErrorCodeLink = netErrorCard.errorCode;
   1251    Assert.equal(
   1252      certErrorCodeLink.textContent,
   1253      `Error Code: SEC_ERROR_EXPIRED_CERTIFICATE`,
   1254      "Error Code is as expected"
   1255    );
   1256    Assert.equal(
   1257      certErrorCodeLink.tagName.toLowerCase(),
   1258      "a",
   1259      "Error Code is a link"
   1260    );
   1261  });
   1262  BrowserTestUtils.removeTab(gBrowser.selectedTab);
   1263 });